solidity
版权声明
要不然会报错
// SPDX-License-Identifier: MIT MIT 的版权协议 // SPDX-License-Identifier: // SPDX-License-Identifier: SimPL-2.0
Hello World
声明版本号 pragma solidity ^0.8.14; contract HelloWorld { string public myString = "hello world"; }
类型和值
变量 // SPDX-License-Identifier: MIT pragma solidity ^0.8.14; // Data types -values and references contract ValueTypes { bool public b = true; unit public u = 123; // uint = unit256 0 to 2**256 - 1 // uint = unit8 0 to 2**8 - 1 // uint = unit16 0 to 2**16 - 1 int public i = -123; // int = int256 -2**255 to 2**255 - 1 // int = int128 -2**127 to 2**127 - 1 int public minInt = type(int).min; // int 有符号的整数的最小值 int public maxInt = type(int).max; // int 有符号的整数的最大值 // 特殊的变量类型 地址 十六进制数字 address public addr = 0x } bool:可以保存 true 或 false 作为其值的布尔值 uint:这是无符号整数,只能保存0和正值 int:这是可以保存负值和正值的有符号整数 address:这表示以太坊环境中的账户地址 byte:这表示固定大小的字节数组(byte1 到 bytes32)
函数
external 外部函数 一个只能在外部读取的函数,内部是不能调用他的 pure 纯函数 指的是这个函数 不能读不能写状态变量的值 只能拥有局部变量 view 只读方法 会读取一些变量的值 // SPDX-License-Identifier:MIT pragma solidity ^0.8.7; contract Function { function add(uint x,uint y) external pure returns (uint) { return x + y; } function sub(uint x,uint y) external pure returns (uint) { return x + y; } }
变量
状态变量和局部变量 // SPDX-License-Identifier:MIT pragma solidity ^0.8.7; contract StateVariables { uint public mjyUint = 123; // 这是状态变量,永远记录在链上 function foo() external { uint notState = 456; // 非状态变量 只能在调用foo这个函数时,才会产生 } } 全局变量 : pure 和 view 的区别 pure纯函数,也就是无论何时何地何种情况,只要输入一样,输出就一样,pure是不能读取变量的,如全局变量和状态变量 view 可以读取一些值,如状态变量或全局变量都可以通过view读取 全局变量: msg.sender; block.timestamp; block.number; 当时的区块号 // SPDX-License-Identifier:MIT pragma solidity ^0.8.7; contract GlobalVariables { function global() external view returns (address,uint,uint){ address sender = msg.sender; uint timestamp = block.timestamp; uint blockNum = block.number; return (sender,timestamp,blockNum); } }
只读函数
// SPDX-License-Identifier: SimPL-2.0 // SPDX-Lincense-Identifier:MIT pragma solidity ^0.8.7; contract ViewsAndPure{ uint public num; funciton viewFunc() external view returns (unit){ return num; } funciton prueFunc() external view returns (unit){ return 1; } funciton addToNum(uint x) external [view] returns (unit){ return num+x; // 因为读取了num这个状态变量 } funciton add(uint x,uint y) external [pure] returns (unit){ return x+y; // 因为他是个纯函数 } }
计数器合约
对以上学的进行总结 // SPDX-License-Identifier: SimPL-2.0 // SPDX-Lincense-Identifier:MIT pragma solidity ^0.8.7; contract Counter{ uint public count; function inc() external { count +=1 ; } function dec() external { count -=1 ; } }
变量的默认值
// SPDX-License-Identifiter:MIT pragma solidity ^0.8.7; contract DefaultValues { bool public b; // false uint public u; // 0 int public i; // 0 address public a; // 0地址 0x四十个0 二十位二进制的数 bytes32 public b32; // 0x 64个0 32位 }
常量
constant 可以把变量变成常量 这里的变量名称一般用大写中间用下划线做连接 注意:一般可以把不需要修改的值变成常量 pragma solidity ^0.8.7; contract Constants { address public constant MY_ADDRESS = 0x777788889999AaAAbBbbCcccddDdeeeEfFFfCcCc; uint public constant MY_UINT =123; } contract Var { address public MY_ADDRESS = 0x777788889999AaAAbBbbCcccddDdeeeEfFFfCcCc; }
结构控制
if -- else pragma solidity ^0.8.7; contract IFElse { function example(uint _x) external pure returns (uint) { if (_x<10){ return 1; } else if (_x < 20){ return 2; } else { return 3; } } function tenary(uint _x) external pure resturns(uint){ // if (_x <10) { // return 1; //} //return 2; 同下简单的三元运算 return _x <10 ? 1 : 2; } }
循环控制
for循环 pragma solidity ^0.8.7; contract ForAndWhileLoops { function loops() external pure { for (uint i = 0;i<10; i++){ //code if (i ==3){ continue; } // more code if (i ==5){ break; } } uint j = 0; while (j < 10) { j++ } } // 累加器 function sum(uint _n) external pure returns (uint){ uint s; for (uint i = 1;i <= _n;i++){ s += i; } return s; } }
报错控制
require revert 不能包含表达式 assert 断言的含义 // gas refund // custom 自定义错误 节约gas pragma solidity ^0.8.7; contract Error { function testRequire(uint _i) public pure{ require(_i <=10,"i>10"); // 只有前面的表达式为真才能执行后面的代码 满足条件才能正确运行,后面跟着报错信息 } function testRevert(uint _i) public pure{ if (_i >10){ revert("i>10") } } uint public num = 123; function testAssert() public view { assert(num == 123); // 断言判断,不包含报错信息 } function foo(uint _i) public { num +=1; require(_i <10); } error MyError(address caller,uint i); function testCustomError(uint _i) public view { // require(_i <=10," very long error message error error"); if (_i>10){ revert MyError(msg.sender,_i); } } }
函数修改器
modifier --关键词 _就相当于把所修饰的函数代码段复制到这里了,代码段中有return,那么就会返回,不会执行_;后面的代码段。 pragma solidity ^0.8.7; contract FunctionModifiter { bool public paused; uint public count; function setPaused(bool _paused) external { paused = _paused } modifiter whenPaused(){ //函数修改器 require(!paused,"paused"); _; } function inc() external whenPaused{ //先进入修改器中 // require(!paused,"paused"); count += 1; } function dec() external whenPaused{ // 这一块就可以省略-require(!paused,"paused"); count -= 1; } // 带参数的修改器 codifier cap(uint _x){ require(_x <100, "x>=100"); _; //下划线代表函数中的其他代码,让其他的代码在后面运行 } function incBy(uint _x) external whenPaused cap(_x) { count += _x; } // 三明治类型 modifier sandwith(){ //code here count += 10; _; // more code here count *=2; } function foo() external sandwith { count += 1; } }
构造函数
仅能在部署合约时被调用一次,一般用来初始化一些变量的值 关键字 constructor pragma solidity ^0.8.7; contract Constructor { adress public owner; uint public x; constructor(uint _x) { owner = msg.sender; // 合约的部署者就是msg.sender变量中的值 x = _x; } }
Ownable
// state varialbes // globle variables // function modifier // function // error handling // SPDX-License-Identifier: pragma solidity ^0.8.7; contract Ownable { address public owner; constructor(){ owner = msg.sender; } modifier onlyOwner(){ require(msg.sender == owner,"not owner"); // 管理员才能调用 _; } function setOwner(address _newOwner) external onlyOwner { // 函数是外部可视的 必须只能只有onlyOwner才能调用它 require(_newOwner != address(0), "invalid address"); // 新的地址不等于地址零 "报错信息 = invalid address" owner = _newOwner; } function onlyOwnerCancallThisFunc() external onlyOwner{ // code } function anyOwnerCancall() external{ // code } }
函数的返回值
// return multiple outputs // Named outputs // Destructuring Assignment contract FunctionOutputs { function returnMany() public pure returns (uint, bool){ //public 公开可视 外部内部都能调用函数 pure 纯函数 return (1,true); } // 并不知道此函数的含义 function named() public pure returns (uint x, bool b){ //public 公开可视 外部内部都能调用函数 pure 纯函数 return (1,true); } function assigned() public pure returns (uint x, bool b){ //public 公开可视 外部内部都能调用函数 pure 纯函数 x = 1; b = true; } function destructingAssigments() public prue{ (uint x,bool b) = returnMany(); (,bool b) = returnMany(); // 例如只需要bool类型的值 } }
数组
数组 有 动态数组和定长数组 数组的初始化 操作:插入 修改 删除 弹出 获取长度 在内存中创建数组 通过函数来返回数组 // SPDX-License-Identifier:SimPL-2.0 // SPDX-License-Identifier:MIT pragma solidity ^0.8.7; contract Arry { uint[3] public numFixed = [1,5,3]; // 定长数组 function examples() external { nums.push(); // 动态数组用push的方法向数组的尾部推入一个数据 [1,2,3,4] uint x = nums[1]; nums[2] = 777; // [1,2,777,4] delect nums[1]; // [1,0,777,4] 不能删除数组的长度,会使值变成默认值0 nums.pop(); // 弹出最后一个值,数组长度就会变短 uint len = num.length; // 获得数组的长度 // 在内存创建一个数组 是一个局部变量 关键词 memory uint[] memory a = new uint[](5); // 必须要定义长度,因为在内存中不能定义动态数组 // 在内存中 不能使用a.弹出pop()和推入push()这种方法,因为会改变长度,改变长度的都不能使用 a[1] = 123; // 赋值可以 } function returnArray() external view returns(uint[] memory) { // 如果希望数组中所有内容全部返回 --内存的存储类型 ruturn nums; } }
删除数组元素通过替换
// remove array element by shifting elements to left // [1,2,3,4,5,6] -- remove(2) --> [1,2,4,5,6,6] --> [1,2,,4,5,6] 比较浪费gas pragma solidity ^0.8.7; contract ArrayReplaceLast { uint[] public arr; // [1,2,3,4] -- remove(1) --> [1,4,3] // [1,4,3] -- remove(2) --> [1,4] 优点节约gas 缺点打乱数组的顺序 function remove(uint _index) public { arr[_index] = arr[arr.length -1]; // arr.length -1 数组中最后一个值 arr.pop(); } function text() external { arr = [1,2,3,4]; remove(1); // [1,4,3] assert(arr.length == 3); assert(arr[0] == 1); assert(arr[1] == 4); assert(arr[2] == 3); remove(2); // [1,4] assert(arr.length == 2); assert(arr[0] == 1); assert(arr[1] == 4); } }
映射
Mapping how to declare a mapping (simple and nested) Set , get, delete // ["alice","bob","charlie"] // {"alice":true,"bob":true,"charlie":true} pragma solidity ^0.8.7; contract Mapping{ mapping(address => uint) public balances; // 记账式的映射,查找一个地址,它的余额就会返回回来 一个地址对应了一个数字 mapping(address => mapping(address => bool)) public isFriend; // 某两个地址是不是朋友关系 function examples() external { balances[msg.sender] = 123; // 调用者有了余额 uint bal = balances[msg.sender]; // 获取数据 uint bal2 = balances[address(1)]; // 0 不存在的值,会返回默认值 这里是uint类型的默认值 balances[msg.sender] += 456; //这里是在原有账号的基础上和余额进行计算 123+456=579 delete balance[msg.sender]; // 删除数据, 恢复成默认值0 isFriend[msg.sender][address(this)] = true // 第一个是调用者的地址,第二个是当前合约的地址 } }
映射迭代
// SPDX-License-Identifier: MIT pragma solidity ^0.8.7; contract Mapping{ // 映射不能遍历 mapping(address => uint) public balances; mapping(address => bool) public inserted; address[] public keys; // 实现可遍历式的这种映射 function Map(address _keys,uint num) external { balances[_keys] = num; if (!inserted[_keys]){ inserted[_keys] = true; keys.push(_keys); } } function getSize() external view returns (uint){ return keys.length; } // 获得数组第一位的余额 function frist() external view returns (uint){ return balances[keys[0]]; } // 获得数组最后一位的余额 function last() external view returns (uint){ return balances[keys[keys.length - 1]]; } // 获得数组任意一个索引的余额 function getInfo(uint _i) external view returns(uint){ return balances[keys[_i]]; } }
结构体
// SPDX-License-Identifier: MIT pragma solidity ^0.8.3; contract Structs{ // 定义了一个汽车的结构体 struct Car { // 型号,年份,拥有者是地址类型 string model ; uint year; address owner; } // 以 汽车为类型定义一个汽车的变量,结构体是大写字母开头,变量是小写字母开头,定义数组的话在类型后加个方括号就行[] Car public car; Car[] public cars; mapping(address => Car[]) public carsByOwner; function examples() external { // 标记它的位置为内存,合约这个函数运行完之后它就不存在了,只在内存中存在 // 向结构体中传入数据 Car memory toyata = Car("Toyata", 1998, msg.sender); // 这里必须按照顺序传入变量 Car memory lambo = Car({model:"Lambogini",year:1980,owner: msg.sender}); // 顺序没影响 Car memory tesla; tesla.model = "Tesla"; tesla.year = 1896; tesla.owner = msg.sender; cars.push(toyata); cars.push(lambo); cars.push(tesla); cars.push(Car("Ferrari",2000,msg.sender)); // 拿数据 // Car memory _car = cars[0]; // 这是内存中 // _car.model; // _car.year; // _car.owner; Car storage _car = cars[0]; // 如果是存储中,就可以对这个结构体的值进行修改和删除的操作了 // 存储代表着我们把状态变量读取出来,而且是指针类型的读取 _car.year = 1564; delete _car.owner; //删除后还是会恢复变成默认值,不会彻底删除 delete cars[1]; // 整个结构体的三个值都会变成默认值 } }
枚举
// SPDX-License-Identifier: MIT pragma solidity ^0.8.7; contract Enum { enum Status{ // 枚举类型的值是以索引的形式存在的 None, // 默认值,没有状态 Pending, // 处理中 Shipped, // 装载中 Completed, // 已装载的 Rejected, // 已完成的 canceled // 已拒绝的,已取消的 } Status public status; struct Order { address buyer; // 可以把枚举类型写进结构体内部 Status status; } // 可以反复式的嵌套使用 Order[] public order; // 写一个只读函数,返回状态的类型就可以 function getStatus() view external returns (Status){ return status; } // 设置枚举类型的状态变量 function set(Status _status) external { status = _status; } // 设置枚举类型指定为想要的状态 例:装载中 function ship() external { status = Status.Shipped; } // 设置状态恢复到默认值 function reset() external { // 可以使用删除的方法,让枚举类型恢复成默认值 --- 注意这里的默认值是设置Status的第一个字段 delete status; } }
部署合约
存储位置
storage 状态变量 memory 局部变量,只会在内存中生效,即使修改了值,也不会改变状态变量 calldata 和memory比较类似,但是只能用在输入的参数中 如果在智能合约的参数中使用calldata会节省gas 在函数的传参中,如果是数组,和字符串类型 必须要标记一下是memory内存中还是calldata这样的状态 其实字符串类型也是一个数组是 bytes类型的数组 内存中的数组必须是定长数组 动态数组只能用在状态变量 给数组赋值必须要带上索引,而不能使用push
简单存储
calldata
待办事项列表
事件
contract E { event Log(string message); function foo() public virtual { emit Log("E.foo"); } function bar() public virtual { emit Log("E.bar"); } }
继承
关键字 vritual 来定义一个函数是可以被重写的 关键字 override 证明他是覆盖掉父合约中的函数 vritual override 可以同时在一个函数使用 子函数定义继承于A contract A { function foo() public pure vritual rerutns(String memory){ return "A"; } } contract B is A { function foo() public pure vritual override rerutns(String memory){ return "B "; } } contract C is B { function foo() public pure override rerutns(String memory){ return " C"; } }
多线继承
要遵循一个原则是从基类到派生之间的关系 是一个顺序关系,继承最少的合约放在最前面,哪个合约更接近于基础 X / | Y | \ | z contract X { function foo() public pure vritual rerutns(String memory){ return "X"; } function X() public pure rerutns(String memory){ return "X"; } } contract Y is X { function foo() public pure vritual override rerutns(String memory){ return "Y"; } function Y() public pure rerutns(String memory){ return "Y"; } } contract Z is X,Y { // override(X,Y) 同时覆盖两个合约的相同的函数,这里面的X,Y顺序没有影响 function foo() public pure override(X,Y) rerutns(String memory){ return "Z"; } }
运行父级合约构造函数
继承的父级合约中有构造函数 // SPDX-License-Identifier: MIT pragma solidity ^0.8.3; // 2 ways to call parent constructors // Order of initialization contract S { string public name; constructor(string memory _name) { name = _name; } } contract T { string public text; constructor(string memory _text) { text = _text; } } contract U is S("string"),T("string") { // 这里的参数是继承的父级的参数 } contract V is S,T { // 这里的参数是由部署时候传进来的 constructor(string memory _name,string memory _text) S(_name) T(_text) { } } contract VV is S("string"),T { // 一个构造函数是由继承的时候传进去的,一个是由部署的时候传进去的 constructor(string memory _text) T(_text) {} } // 1.S // 2.T // 3.V0 contract V0 is S,T { // 这里的参数是由部署的时候传进来的 constructor(string memory _name,string memory _text) T(_text) S(_name) { } } // ======= 在这里运行的初始化顺序是会按照继承的顺序去初始化 ====== // 1.S // 2.T // 3.V1 contract V1 is S,T { // 这里的参数是由部署的时候传进来的 constructor(string memory _name,string memory _text) S(_name) T(_text) { } } // 1.T // 2.S // 3.V2 contract V2 is T,S { // 这里的参数是由部署的时候传进来的 constructor(string memory _name,string memory _text) S(_name) T(_text) { } } // 1.T // 2.S // 3.V3 contract V3 is T,S { // 这里的参数是由部署的时候传进来的 constructor(string memory _name,string memory _text) T(_text) S(_name) { } }
调用父级合约函数
// SPDX-License-Identifier: MIT pragma solidity ^0.8.3; /* 调用父级合约函数 - direct -super E / \ F G \ / H */ contract E { event Log(string message); function foo() public virtual { emit Log("E.foo"); } function bar() public virtual { emit Log("E.bar"); } } contract F is E { function foo() public virtual override{ emit Log("F.foo"); // 调用父级合约的函数 E.foo(); // 两次事件被触发 } function bar() public virtual override{ emit Log("F.bar"); // super的关键词,会自动的寻找父级合约,然后调用父级合约的函数 super.bar(); } } contract G is E { function foo() public virtual override{ emit Log("G.foo"); // 调用父级合约的函数 E.foo(); // 两次事件被触发 } function bar() public virtual override{ emit Log("G.bar"); // super的关键词,会自动的寻找父级合约,然后调用父级合约的函数 super.bar(); } } contract H is F,G { function foo() public override(F,G){ F.foo(); // 两个合约的事件都会被触发 } function bar() public override(F,G){ super.bar(); } }
可视范围
// SPDX-License-Identifier: MIT pragma solidity ^0.8.3; // visibility // private- 私有 -- 只在合约内部可见 // internal - 内部 -- 是在合约的内部和被继承的子合约中都可以看得见 // public - 公开 在内部和被继承的和外部都可见 // externali- 外部 仅在函数外部调用的可见,在内部不可见 可以用在状态变量和函数中 A合约: private pri() internal inter() C 合约在外部调用了A合约: public pub() pub()和ext() external ext() B is A: inter() pub() 小技巧 -- 浪费gas,没必要用 this.external -- 意思是先去外部然后在调用外部函数 就可以拿到仅外部调用的函数
不可变量
关键词 immutable 设置成常量 节省gas 但是必须在部署合约的时候给他赋值,才能像常量一样工作 // SPDX-License-Identifier: MIT pragma solidity ^0.8.3; contract Immutable { // 1、 address public owner = msg sender; // 2、 address public immutable owner; constructor() { owner = ms.sender; } uint public x; function foo() external { require(msg.sender == owner); x += 1; } }
接口合约
Interface.sol // SPDX-License-Identifier: MIT pragma solidity ^0.8.3; // 新的接口合约 以interface开始 接口的名称习惯以I为开头来定义 Counter这个接口合约的名字 interface ICounter{ function count() external view returns (uint); function inc() external; } contract CallInterface{ uint public count; function examples(address counter) external { ICounter(_counter).inc(); count = ICounter(_counter).count(); } } 编译这个合约 Counter.sol // SPDX-License-Identifier: MIT pragma solidity ^0.8.3; contract Counter { uint public count; function inc() external { count += 1; } function dec() external { count -=1; } }
库合约
避免重复代码的编写 创建一个数学的库合约 一般都是内部可视,或者想要外部调用的话设置成公开可视 library Math{ // 寻找最大值的函数 } // SPDX-License-Identifier: MIT pragma solidity ^0.8.3; library Math { function max(uint x, uint y) internal pure returns (uint) { return x >= y ? x : y; } } contract Test { function testMax(uint x, uint y) external pure returns (uint) { // 调用数学库中的函数 return Math.max(x,y); } // 寻找数组中对应值 library ArrayLib { function find(uint[] storage arr, uint x) internal view returns (uint) { for (uint i =0;i < arr.length; i++) { if (arr[i] == x) { return i; } } revert("not found"); } contract TestArray { // 相当于把库应用到了数组的类型中,这个类型中就拥有了这个库的所有函数功能 using ArrayLib for uint[]; // 推荐这种 uint[] public arr = [3,2,1]; function testFind() external view returns (uint i) [ // return ArrayLib.find(arr,2); return arr.find(2); } } 利用using for指令 指令using A for B;可用于附加库函数(从库 A)到任何类型(B)。添加完指令后,库A中的函数会自动添加为B类型变量的成员,可以直接调用。注意:在调用的时候,这个变量会被当作第一个参数传递给函数:
低级调用Call
函数签名
是用来标识函数的唯一标识符,它由函数的**名称**和**参数类型**组成。例如,函数 foo(uint256 a, string memory b) 的签名是 foo(uint256,string)。