Solidity学习1
在线编辑器:remix https://remix.ethereum.org/
Solidity的基础知识
-
文件名后缀:sol
-
文件结构:
// SPDX-License-Identifier: MIT //声明 pragma solidity ^0.8.7; //声明solidity版本 contract Hello{ //合约名 string public name = "hello"; }
-
变更分为 本地变量(存函数内的,调用时才有),链上变量(公共变量),全局变量(比如链的时间戳,调用合约的地址变量)
-
常量:命名规范是全大写字母加下划线,是不能修改的。例: address public constant MY_ADDRESS = 123;
-
不可变变量,类似于常量,只允许在合约的构造函数中修改,命名规范是全大写字母加下划线。例:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.26; contract Immutable { address public immutable myAddr; uint256 public immutable myUint; constructor(uint256 _myUint) { myAddr = msg.sender; myUint = _myUint; } }
-
映射 mapping(_KeyType => _ValueType) public myMapping; 类似数组,_KeyType:键名(可以是任何基本数据类型(如 uint, address 等)或合约类型。),_ValueType:键值,myMapping:数组变量
-
访问效率:映射提供非常快速的键值对访问。在内部,它们使用哈希表实现,使得查找、插入和删除操作的时间复杂度接近O(1)。
-
内存使用:由于映射使用哈希表,它们可能会消耗较多的内存,特别是当存储大量的键值对时。因此,在设计智能合约时需要考虑内存使用。
-
不可迭代:与数组不同,映射不是可迭代的。这意味着你不能直接遍历一个映射来访问所有的键或值。要遍历所有条目,你需要使用额外的数据结构(如数组)来辅助存储键或值的列表。
-
默认值:如果尝试访问一个尚未设置的键,Solidity将返回该类型的默认值。例如,对于uint类型,默认值是0;对于address类型,默认值是0x0000000000000000000000000000000000000000。
-
-
以映射为KeyType,相当于二维数组。例子:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.26; contract Immutable { mapping (address => mapping(int => int)) public myAdd; function get_myAdd(address _address) public view returns(int){ return myAdd[_address][1]; } function set_MyAddress(int money) public{ myAdd[msg.sender][1] = money; } function del(address _address) public { delete myAdd[_address][1]; } }
-
数组操作,例子如下:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.26; contract Immutable { int[] public myNums; uint[] public myMoney = [1,2,3,4]; //设置初始值 int[4] public myArray; //固定长度的数组,不能使用push,pop function pusharr(int x) public{ myNums.push(x); //在数组尾添加 } function poparr() public{ myNums.pop(); //在数组尾删除一个 } function arrlength() public view returns (uint256){ return myNums.length; //获取数组长度 } function delarr(uint x) public{ delete myNums[x]; //并不删除指定数组下标,只是把值设置为默认值0 } }
-
有几种方法可以返回函数的输出。
// SPDX-License-Identifier: MIT pragma solidity ^0.8.26; contract Function { // 返回多个值 function returnMany() public pure returns (uint256, bool, uint256) { return (1, true, 2); } // 给多个返回值命名 function named() public pure returns (uint256 x, bool b, uint256 y) { return (1, true, 2); } // 给多个返回值命名 // 这种情况下可以不使用return function assigned() public pure returns (uint256 x, bool b, uint256 y) { x = 1; b = true; y = 2; } // 在调用返回多个值的另一个函数时,使用解构赋值。 function destructuringAssignments() public pure returns (uint256, bool, uint256, uint256, uint256) { (uint256 i, bool b, uint256 j) = returnMany(); // Values can be left out. (uint256 x,, uint256 y) = (4, 5, 6); return (i, b, j, x, y); } // 输入或输出均不能使用映射(map)。 // 可以使用数组作为输入 function arrayInput(uint256[] memory _arr) public {} // 可以使用数组进行输出 uint256[] public arr; function arrayOutput() public view returns (uint256[] memory) { return arr; } } // 使用键值输入调用函数 contract XYZ { function someFuncWithManyInputs( uint256 x, uint256 y, uint256 z, address a, bool b, string memory c ) public pure returns (uint256) {} function callFunc() external pure returns (uint256) { return someFuncWithManyInputs(1, 2, 3, address(0), true, "c"); } function callFuncWithKeyValue() external pure returns (uint256) { return someFuncWithManyInputs({ a: address(0), b: true, c: "c", x: 1, y: 2, z: 3 }); } }
-
Solidity智能合约的Pure 和 View
Pure, view 和 payable约束函数的行为,如果函数没有被特定的关键词约束,那么它是既可读又可写的,View函数只可读,不可以改变区块链数据的状态。pure函数更严格,既不可读也不可写。 payable函数则可以用来接收以太币。View函数是只读的,不能修改区块链的状态,Getter函数默认是只读的 ,View函数无法:
- 写入状态变量,更改区块链的状态
- 发起事件events
- 创建智能合约
- 发送以太币
Pure函数约束性更强,不能修改或读取区块链的状态变量,pure函数无法:
- 读取区块链上的数据
- 访问账户
- 调用非pure函数
-
Solidity:require()、assert()、revert()区别到底在哪?
Require()使用场景1. 验证用户输入,即: require(input<20); 2. 验证外部合约响应,即: require(external.send(amount)); 3. 执行合约前,验证状态条件,即: require(block.number > SOME_BLOCK_NUMBER) 或者 require(balance[msg.sender]>=amount) 4. require应该被最常使用到;一般用于函数的开头处。
Assert()使用场景
检查 overflow/underflow, 例:c = a+b; assert(c > b); 检查非变量(invariants), 例:assert(this.balance >= totalSupply); 验证改变后的状态,不应该发生的条件 一般地,尽量少使用 assert 调用,如要使用assert 应该在函数结尾处使用 基本上,require() 应该被用于函数中检查条件,assert() 用于预防不应该发生的情况,但不应该使条件错误。 另外,“除非认为之前的检查(用 if 或 require )会导致无法验证 overflow,否则不应该盲目使用 assert 来检查 overflow
Revert()特点
允许你返回一个值; 例:revert(‘Something bad happened’);等价于require(condition, ‘Something bad happened’); 它会把所有剩下的gas退回给caller
Require对比Revert,复杂时候用Revert
Gas消耗 两者一样都会向剩余的gas费返还给调用者 使用差异 revert()处理与 require() 同样的类型,但是需要更复杂处理逻辑的场景 如果有复杂的 if/else 逻辑流,那么应该考虑使用 revert() 函数而不是require()。记住,复杂逻辑意味着更多的代码。
总结:Assert就是内部判断直接中断,没有返回值;require就是相当只有一个IF判断时用的,revert就相当于复杂判断时使用if/elseif 时使用;
例子:function test(uint x) public pure{ //多用于判断函数传参的 require(x > 10, "message"); } function testrevert(uint x) public pure{ //用于多个判断的复杂逻辑判断的 if(x > 10){ revert("munt 10"); }else if(x > 20){ revert("munt 20"); } } function testassert(uint x) public pure{ //没有返回值,只能中断 assert(x < 8); }
-
函数修饰符
修饰符是可以在函数调用之前和/或之后运行的代码。
修饰符可用于:
• 限制访问
• 验证输入
• 防止重入黑客攻击// SPDX-License-Identifier: MIT pragma solidity ^0.8.26; contract FunctionModifier { address public owner; uint256 public x = 10; bool public locked; constructor() { // 将交易发送者设置为合约的所有者。 owner = msg.sender; } // 修饰器,用于检查调用者是否为合约的所有者。 modifier onlyOwner() { require(msg.sender == owner, "Not owner"); // 下划线是一个特殊字符,仅用于函数修饰符内部,它告诉 Solidity 执行其余代码。 _; } // 修饰器可以接收输入。此修饰器用于检查传入的地址是否不是零地址。 modifier validAddress(address _addr) { require(_addr != address(0), "Not valid address"); _; } function changeOwner(address _newOwner) public onlyOwner validAddress(_newOwner) { owner = _newOwner; } // 修饰符可以在函数调用之前和/或之后调用。 // 此修饰符用于防止函数在其仍在执行时被调用。 modifier noReentrancy() { require(!locked, "No reentrancy"); locked = true; _; locked = false; } function decrement(uint256 i) public noReentrancy { x -= i; if (i > 1) { decrement(i - 1); } } }
-
事件
Events允许记录到 Ethereum 区块链。事件的一些用例包括:
• 侦听事件和更新用户界面
• 一种廉价的存储形式// SPDX-License-Identifier: MIT pragma solidity ^0.8.26; contract Event { // 事件声明 // 最多可对3个参数进行索引。 // 索引参数可帮助你根据索引参数筛选日志 event Log(address indexed sender, string message); event AnotherLog(); function test() public { emit Log(msg.sender, "Hello World!"); emit Log(msg.sender, "Hello EVM!"); emit AnotherLog(); } }