目录

  • 一、数据类型
    • 1.1、值类型
      • 1.1.1、布尔
      • 1.1.2、整数
      • 1.1.3、定长浮点型
      • 1.1.4、地址类型
      • 1.1.5、合约类型
      • 1.1.6、枚举类型
      • 1.1.7、定长字节数组
      • 1.1.8、函数类型
    • 1.2、引用类型
      • 1.2.1、字符串
      • 1.2.2、变长字节数组
      • 1.2.3、数组
      • 1.2.4、结构体
    • 1.3、映射
  • 二、作用域(访问修饰符)
    • 2.1、private
    • 2.2、public
    • 2.3、internal
    • 2.4、external
  • 三、函数修饰符
    • 3.1、pure
    • 3.2、view
    • 3.3、payable
  • 四、构造函数
  • 五、修饰器modifier
  • 六、数据位置
    • 6.1、memory
    • 6.2、storage
    • 6.3、calldata
    • 6.4、stack
  • 七、事件event
  • 八、单位和全局变量
  • 九、异常处理
    • 9.1、assert
    • 9.2、require
    • 9.3、revert
    • 9.4、try/catch
  • 十、重载
  • 十一、继承
  • 十二、抽象合约
  • 十三、重写
  • 十四、接口
  • 十五、库
  • 0x1、示例代码
  • 0x2、各版本主要变化

一、数据类型

1.1、值类型

1.1.1、布尔

pragma solidity ^0.4.25;contract TestBool {bool flag;int num1 = 100;int num2 = 200;// default falsefunction getFlag() public view returns(bool) {return flag;// false}// 非function getFlag2() public view returns(bool) {return !flag;// true}// 与function getFlagAnd() public view returns(bool) {return (num1 != num2) && !flag;// true}// 或function getFlagOr() public view returns(bool) {return (num1 == num2) || !flag;// true}}

1.1.2、整数

加减乘除、取余、幂运算,

pragma solidity ^0.4.25;// 整型特性与运算contract TestInteger {int num1; // 有符号整型 int256uint num2; // 无符号整型 uint256function add(uint _a, uint _b) public pure returns(uint) {return _a + _b;}function sub(uint _a, uint _b) public pure returns(uint) {return _a - _b;}function mul(uint _a, uint _b) public pure returns(uint) {return _a * _b;}function div(uint _a, uint _b) public pure returns(uint) {return _a / _b;// 在solidity中,除法是做整除,没有小数点}function rem(uint _a, uint _b) public pure returns(uint) {return _a % _b;}function square(uint _a, uint _b) public pure returns(uint) {return _a ** _b;// 幂运算在0.8.0之后,变为右优先,即a ** b ** c => a ** (b ** c)}function max() public view returns(uint) {return uint(-1);// return type(uint).max;// 0.8不再允许uint(-1)}}

位运算,

pragma solidity ^0.4.25;// 位运算contract TestBitwise {uint8 num1 = 3;uint8 num2 = 4;function bitAdd() public view returns(uint) {return num1 & num2;}function bitOr() public view returns(uint) {return num1 | num2;}function unBit() public view returns(uint) {return ~num1;}function bitXor() public view returns(uint) {return num1 ^ num2;}function bitRight() public view returns(uint) {return num1 >> 1;}function bitLeft() public view returns(uint) {return num1 << 1;}}

1.1.3、定长浮点型

目前只支持定义,不支持赋值使用,

fixed num; // 有符号ufixed num; // 无符号

1.1.4、地址类型

address addr = msg.sender;address addr = 0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF;

1.1.5、合约类型

在合约TestType中使用TestBitwise合约,

TestBitwise t = TestBitwise(addr);

1.1.6、枚举类型

enum ActionChoices { Up, Down, Left, Right }

1.1.7、定长字节数组

pragma solidity ^0.4.25;// 固定长度的字节数组(静态),以及转换为string类型contract TestBytesFixed {// public 自动生成同名的get方法bytes1 public num1 = 0x7a;// 1 byte = 8 bitbytes1 public num2 = 0x68;bytes2 public num3 = 0x128b;// 2 byte = 16 bit// 获取字节数组长度function getLength() public view returns(uint) {return num3.length; // 2}// 字节数组比较function compare() public view returns(bool) {return num1  zzfunction toString(bytes2 _val) public pure returns(string) {bytes memory buf = new bytes(_val.length);for (uint i = 0; i < _val.length; i++) {buf[i] = _val[i];}return string(buf);}}

固定长度字节数组的扩充和压缩,

pragma solidity ^0.4.25;// 固定长度的字节数组(静态)扩容和压缩contract TestBytesExpander {// public 自动生成同名的get方法bytes6 name = 0x796f7269636b;function changeTo1() public view returns(bytes1) {return bytes1(name); // 0x79}function changeTo2() public view returns(bytes2) {return bytes2(name); // 0x796f}function changeTo16() public view returns(bytes16) {return bytes16(name); // 0x796f7269636b00000000000000000000}}

1.1.8、函数类型

function () {internal|external|public|private} [pure|constant|view|payable] [returns ()]

1.2、引用类型

1.2.1、字符串

pragma solidity ^0.4.25;// 修改string类型的数据contract TestString {string name = 'yorick';// 字符串可以使用单引号或者双引号string name2 = "!@#$%^&";// 特殊字符占1个bytestring name3 = "张三";// 中文在string中使用utf8的编码方式存储,占用3个bytefunction getLength() view public returns(uint) {// 不可以直接获取string的lengthreturn bytes(name).length; // 6}function getLength2() view public returns(uint) {return bytes(name2).length; // 7}function getLength3() view public returns(uint) {return bytes(name3).length; // 6}function getElmName() view public returns(bytes1) {// 不可以直接通过数组下标name[0]获取return bytes(name)[0]; // 0x79}function changeElmName() public {bytes(name)[0] = "h";}function getName() view public returns(bytes) {return bytes(name);}}

1.2.2、变长字节数组

pragma solidity ^0.4.25;// 动态的字节数组,以及转换为string类型contract TestBytesDynamic {bytes public dynamicBytes;function setDynamicBytes(string memory val) public {dynamicBytes = bytes(val);}function getVal() public view returns(string){return string(dynamicBytes);}}

1.2.3、数组

定长数组,

pragma solidity ^0.4.25;// 定长数组contract TestArrFixed {uint[5] arr = [1,2,3,4,5];// 修改数组元素内容function modifyElements() public {arr[0] = 12;arr[1] = 14;}// 查看数组function watchArr() public view returns(uint[5]) {return arr;}// 数组元素求和计算function sumArr() public view returns(uint) {uint sum = 0;for (uint i = 0; i < arr.length; i++) {sum += arr[i];}return sum;}// 数组长度function getLength() public view returns(uint) {return arr.length;}// delete重置数组某下标的元素值,不会真正删除该元素function deleteElm(uint idx) public {delete arr[idx];}// delete重置整个数组function deleteArr() public {delete arr;}/**定长数组不允许改变长度和push// 压缩数组后,右侧多余元素被丢弃function changeLengthTo1() public {arr.length = 1;}// 扩容数组后,右侧元素补0function changeLengthTo10() public {arr.length = 10;}// 追加新元素function pushElm(uint _elm) public {arr.push(_elm);}*/}

变长数组,

pragma solidity ^0.4.25;// 变长数组contract TestArrDynamic {uint[] arr = [1,2,3,4,5];// 查看数组function watchArr() public view returns(uint[]) {return arr;}// 数组长度function getLength() public view returns(uint) {return arr.length;}// 压缩数组后,右侧多余元素被丢弃function changeLengthTo1() public {arr.length = 1;}// 扩容数组后,右侧元素补0function changeLengthTo10() public {arr.length = 10;}// 追加新元素function pushElm(uint _elm) public {arr.push(_elm);}// delete重置数组某下标的元素值,不会真正删除该元素function deleteElm(uint idx) public {delete arr[idx];}// delete重置整个数组function deleteArr() public {delete arr;}}

二维数组,

pragma solidity ^0.4.25;/**二维数组solidity的二维数组与其他语言不同,[2] [3]表示3行2列,而其他语言为2行3列;二维动态数组与一维数组类似,可以改变其数组长度;*/contract TestArr2Dimensional {uint[2][3] arr = [[1,2],[3,4],[5,6]];function getRowSize() public view returns(uint) {return arr.length; // 3}function getColSize() public view returns(uint) {return arr[0].length; // 2}function watchArr() public view returns(uint[2][3]) {return arr;// 1,2,3,4,5,6}function sumArr() public view returns(uint) {uint sum = 0;for (uint i = 0; i < getRowSize(); i++) {for (uint j = 0; j < getColSize(); j++) {sum += arr[i][j];}}return sum;}function modifyArr() public {arr[0][0] = 99;}}

数组字面值,

pragma solidity ^0.4.25;// 数组字面值contract TestArrLiteral {// 最小存储匹配,未超过255,所以使用uint8存储function getLiteral8() pure public returns(uint8[3]) {return [1,2,3];}// 超过255,所以使用uint16存储function getLiteral16() pure public returns(uint16[3]) {return [256,2,3];// [255,2,3] 不被uint16允许}// 强制转换为uint256function getLiteral256() pure public returns(uint[3]) {return [uint(1),2,3];// 给任意元素强转即可,否则不被允许}// 计算外界传入的内容function addLiterals(uint[3] arr) pure external returns(uint) {uint sum = 0;for (uint i = 0; i < arr.length; i++) {sum += arr[i];}return sum;}}

1.2.4、结构体

pragma solidity ^0.4.25;// 结构体初始化的两种方法contract TestStruct {struct Student {uint id;string name;mapping(uint=>string) map;}// 默认为storage类型,只能通过storage类型操作结构体中的mapping类型数据Student storageStu;// mapping类型可以不用在定义的时候赋值,而其他类型不赋值则会报错function init() public pure returns(uint, string) {Student memory stu = Student(100, "Jay");return (stu.id, stu.name);}function init2() public pure returns(uint, string) {Student memory stu = Student({name: "Jay", id: 100});return (stu.id, stu.name);}function init3() public returns(uint, string, string) {Student memory stu = Student({name: "Jay", id: 100});// 直接操作结构体中的mapping不被允许: Student memory out of storage// stu.map[1] = "artist";// 通过storage类型的变量操作结构体中的mappingstorageStu = stu;storageStu.map[1] = "artist";return (storageStu.id, storageStu.name, storageStu.map[1]);}// 结构体作为入参时,为memory类型,并且不能使用public或external修饰函数:Internal or recursive type is not allowed for public or external functions.// 赋值时也要指定为memory,否则报错function testIn(Student stu) internal returns(uint) {return stu.id;}// 结构体作为出参,同样只能private或internal声明内部使用function testOut(Student stu) private returns(Student) {return stu;}}

1.3、映射

contract TestMapping {mapping(address => uint) private scores;// 的单层映射mapping(address => mapping(bytes32 => uint8)) private _scores;// <学生,>的两层映射function getScore() public view returns(address, uint) {address addr = msg.sender;return (addr, scores[addr]);}function setScore() public {scores[msg.sender] = 100;}}

二、作用域(访问修饰符)

contract TestAccessCtrl {constructor () public {}uint public num1 = 1;// 自动为public生成同名的get函数,但在编码时不可直接调用num1()uint private num2 = 2;uint num3 = 3;// 不写则默认privatefunction funcPublic() public returns(string) {return "public func";}function funcPrivate() private returns(string) {return "private func";}function funcInternal() internal returns(string) {return "internal func";}function funcExternal() external returns(string) {return "external func";}function test1(uint choice) public returns(string) {if (choice == 1) return funcPublic();if (choice == 2) return funcPrivate();if (choice == 3) return funcInternal();//if (choice == 4) return funcExternal();// external不允许直接在内部用if (choice == 4) return this.funcExternal();// 间接通过this才可以调用external}}contract TestAccessCtrlSon is TestAccessCtrl {function test2(uint choice) public returns(string) {if (choice == 1) return funcPublic();// public允许派生合约使用//if (choice == 2) return funcPrivate();// private不允许派生合约使用if (choice == 3) return funcInternal();// internal允许派生合约使用//if (choice == 4) return funcExternal();// external也不允许派生合约直接使用}}contract TestAccessCtrl2 {function test2(uint choice) public returns(string) {TestAccessCtrl obj = new TestAccessCtrl();return obj.funcExternal();// external只允许在外部合约中这样间接调用}}

2.1、private

仅在当前合约使用,且不可被继承,私有状态变量只能从当前合约内部访问,派生合约内不能访问。

2.2、public

同时支持内部和外部调用。修饰状态变量时,自动生成同名get函数,

但在编码时不可直接调用num1()

2.3、internal

只支持内部调用,也包括其派生合约内访问。

2.4、external

外部才可调用,内部想要调用可以用this

三、函数修饰符

contract TestFuncDecorator {uint public num = 1;/// purefunction testPure(uint _num) public pure {//uint num1 = num;// pure不允许读状态变量//num = _num;// pure不允许修改状态变量}/// viewfunction testView(uint _num) public view {uint num1 = num;// 允许读状态变量num = _num;// 0.4语法上允许修改状态变量,但实际不会修改,所以num还是1// 0.5及之后不允许在view中这样修改,否则编译不通过}/// payablefunction () public payable {}function getBalance() public view returns(uint) {// balance获取合约地址下的以太币余额return address(this).balance;}// 充值函数payable,只有添加这个关键字,才能在执行这个函数时,给这个合约充以太币,否则该函数自动拒绝所有发送给它的以太币function testPayable() payable public {// transfer转账address(this).transfer(msg.value);}}

3.1、pure

表明该函数是纯函数,连状态变量都不用读,函数的运行仅仅依赖于参数。不消耗gas。承诺不读取或修改状态,否则编译出错。

3.2、view

设置了view修饰符,就是一次调用,不需要执行共识、进入EVM,而是直接查询本地节点数据,因此性能会得到很大提升。不消耗gas。不会发起交易,所以不能实际改变状态变量。

3.3、payable

允许函数被调用的时候,让合约接收以太币。如果未指定,该函数将自动拒绝所有发送给它的以太币。

四、构造函数

唯一,不可重载。也可以接收入参。

pragma solidity ^0.4.25;contract Test1 {address private _owner;constructor() public {_owner = msg.sender;}/**constructor(int num) public {重载构造->编译错误_owner = msg.sender;}*/}contract Test2 {uint public num;constructor(uint x) public {// 带参构造,在deploy时传入num = x;}}

五、修饰器modifier

方法修饰器modifier,类似AOP处理。

pragma solidity ^0.4.25;contract TestModifier {address private _owner;bool public endFlag;// 执行完test后的endFlag仍是trueconstructor() public {_owner = msg.sender;}modifier onlyOwner {// 权限拦截器,非合约部署账号执行test()则被拦截require(_owner == msg.sender, "Auth: only owner is authorized.");_;// 类似被代理的test()方法调用endFlag = true;}function test() public onlyOwner {endFlag = false;}}

六、数据位置

6.1、memory

其生命周期只存在于函数调用期间,局部变量默认存储在内存,不能用于外部调用。内存位置是临时数据,比存储位置便宜。它只能在函数中访问。

通常,内存数据用于保存临时变量,以便在函数执行期间进行计算。一旦函数执行完毕,它的内容就会被丢弃。你可以把它想象成每个单独函数的内存(RAM)。

6.2、storage

状态变量保存的位置,只要合约存在就一直保存在区块链中。该存储位置存储永久数据,这意味着该数据可以被合约中的所有函数访问。可以把它视为计算机的硬盘数据,所有数据都永久存储。

保存在存储区(Storage)中的变量,以智能合约的状态存储,并且在函数调用之间保持持久性。与其他数据位置相比,存储区数据位置的成本较高。

6.3、calldata

calldata是不可修改的只读的非持久性数据位置,所有传递给函数的值,都存储在这里。此外,calldata是外部函数的参数(而不是返回参数)的默认位置。

0.4的external入参声明calldata则报错,0.5及之后的external入参必须声明calldata否则报错。

从 Solidity 0.6.9 版本开始,之前仅用于外部函数的calldata位置,现在可以在内部函数使用了。

6.4、stack

堆栈是由EVM (Ethereum虚拟机)维护的非持久性数据。EVM使用堆栈数据位置在执行期间加载变量。堆栈位置最多有1024个级别的限制。

【总结】

花费gas:storage > memory(calldata) > stack

状态变量总是存储在存储区storage中。(隐式地标记状态变量的位置)

函数参数(值类型)包括返回参数(值类型)都存储在内存memory中

值类型的局部变量:栈(stack)

值类型的局部变量存储在内存中。但是,对于引用类型,需要显式地指定数据位置

不能显式声明具有值类型的局部变量为memory还是storage

七、事件event

定义使用event,类似一个函数声明,调用试图emit

contract Test {event testEvent(uint a, uint b, uint c, uint result);function calc(uint a, uint b, uint c) public returns(uint) {uint result = a ** b ** c;emit testEvent(a, b, c, result);return result;}}

事件会输出在logs中,

[{"from": "0x19a0870a66B305BE9917c0F14811C970De18E6fC","topic": "0x271cb5fa8dca917938dbd3f2522ef54cf70092ead9e1871225a2b3b407f9a81a","event": "testEvent","args": {"0": "2","1": "1","2": "3","3": "8","a": "2","b": "1","c": "3","result": "8"}}]

通过给event形参添加indexed,便于事件条件的筛选,indexed不能超过三个,否则编译出错,

event testEvent(uint indexed a, uint b, uint c, uint result);

八、单位和全局变量

时间单位不加默认s,以太币默认wei

function testUnit() pure public {require(1 == 1 seconds);require(1 minutes == 60 seconds);require(1 hours == 60 minutes);require(1 days == 24 hours);require(1 weeks == 7 days);require(1 years == 365 days);// years 从 0.5.0 版本开始不再支持require(1 ether == 1000 finney);require(1 finney == 1000 szabo);// 从0.7.0开始 finney 和 szabo 被移除了require(1 szabo == 1e12 wei);//require(1 gwei == 1e9);// 0.7.0开始加入gwei}

全局变量,

pragma solidity ^0.8.0;contract TestGlobalVariable {function test1() public view returns(/**bytes32,*/ uint, uint, address, uint, uint, uint, uint) {return (// blockhash(block.number - 1),// 指定区块的区块哈希,仅可用于最新的 256 个区块且不包括当前区块,否则返回0。0.5移除了block.blockhashblock.basefee,// 当前区块的基础费用block.chainid,// 当前链 idblock.coinbase,// 挖出当前区块的矿工地址block.difficulty,// 当前区块难度block.gaslimit,// 当前区块 gas 限额block.number,// 当前区块号block.timestamp// 自 unix epoch 起始当前区块以秒计的时间戳,0.7.0移除了now);}function test2() public payable returns(bytes memory, address, bytes4, uint, uint256, uint, address) {return (msg.data,// 完整的 calldatamsg.sender,// 消息发送者(当前调用)msg.sig,// calldata 的前 4 字节(也就是函数标识符)msg.value,// 随消息发送的 wei 的数量gasleft(),// 剩余的 gas,0.5移除了msg.gastx.gasprice,// 交易的 gas 价格tx.origin// 交易发起者(完全的调用链));}}

九、异常处理

9.1、assert

assert(1 == 1 seconds);

9.2、require

require(1 == 1 seconds);require(1 == 1 seconds, "err");

9.3、revert

if (x != y) revert("x should equal to b");

9.4、try/catch

Solidity0.6版本之后加入。try/catch仅适用于外部调用,另外,try大括号内的代码是不能被catch捕获的。

pragma solidity ^0.6.10;contract TestTryCatch {function execute (uint256 amount) external returns(bool){try this.onlyEvent(amount){return true;} catch {return false;}}function onlyEvent (uint256 a) public {//code that can revertrequire(a % 2 == 0, "Ups! Reverting");}}

【assert和require的选择】

在EVM里,处理assert和require两种异常的方式是不一样的,虽然他们都会回退状态,不同点表现在:

  1. gas消耗不同。assert类型的异常会消耗掉所有剩余的gas,而require不会消耗掉剩余的gas(剩余的gas会返还给调用者)
  2. 操作符不同。当assert发生异常时,Solidity会执行一个无效操作(无效指令0xfe)。当发生require类型的异常时,Solidity会执行一个回退操作(REVERT指令0xfd)
  • 优先使用require()
    • 用于检查用户输入。
    • 用于检查合约调用返回值,如require(external.send(amount))。
    • 用于检查状态,如msg.send == owner。
    • 通常用于函数开头。
    • 不知道使用哪一个的时候,就使用require。
  • 优先使用assert()
    • 用于检查溢出错误,如z = x + y; assert(z >= x);
    • 用于检查不应该发生的异常情况。
    • 用于在状态改变之后,检查合约状态。
    • 尽量少使用assert。
    • 通常用于函数中间或者尾部。

十、重载

// 重载function addNums(uint x, uint y) public pure returns(uint) {return x + y;}function addNums(uint x, uint y, uint z) public pure returns(uint) {return x + y + z;}

十一、继承

子合约is父合约的格式,

pragma solidity ^0.4.25;contract TestExtendA { // 父类要写在子类之前uint public a;constructor() public {a = 1;}}contract TestExtend is TestExtendA {uint public b;constructor() public {b = 2;}}

直接在继承列表中指定基类的构造参数,

contract A { // 父类要写在子类之前uint public x;constructor(uint _a) public {// 带参构造x = _a;}}contract B is A(1) {// 指定父类构造参数uint public y;constructor() public {y = 2;}}

通过派生合约(子类)的构造函数中使用修饰符方式调用基类合约,

contract A { // 父类要写在子类之前uint public x;constructor(uint _a) public {// 带参构造x = _a;}}// 方式一:contract B1 is A {uint public b;constructor() A(1) public {// 子类构造使用父类带参修饰符A(1)b = 2;}}// 方式二:contract B2 is A {uint public b;constructor(uint _b) A(_b / 2) public {// 子类带参构造使用父类带参修饰符A(_b / 2)b = _b;}}

连续继承、多重继承,

/// 连续继承,Z继承Y,Y又继承Xcontract X {uint public x;constructor() public{x = 1;}}contract Y is X {uint public y;constructor() public{y = 1;}}// 如果是多个基类合约之间也有继承关系,那么is后面的合约书写顺序就很重要。顺序应该是,基类合约在前面,派生合约在后面,否则无法编译。// 实际上Z只需要继承Y就行contract Z is X,Y {// 所以必须是X,Y而不是Y,X}/// 多重继承,子类可以拥有多个基类的属性contract Father {uint public x = 180;}contract Mother {uint public y = 170;}contract Son is Father, Mother {}

十二、抽象合约

0.6开始支持。

如果一个合约有构造函数,且是内部(internal)函数,或者合约包含没有实现的函数,这个合约将被标记为抽象合约,使用关键字abstract,抽象合约无法成功部署,他们通常用作基类合约。

抽象合约可以声明一个virtual纯虚函数,纯虚函数没有具体实现代码的函数。其函数声明用;结尾,而不是用{}结尾。

如果合约继承自抽象合约,并且没有通过重写(override)来实现所有未实现的函数,那么他本身就是抽象合约的,隐含了一个抽象合约的设计思路,即要求任何继承都必须实现其方法。

//pragma solidity ^0.4.25;// 0.4/0.5不兼容pragma solidity ^0.6.10;abstract contract TestAbstractContract {uint public a;constructor(uint _a) internal {a = _a;}function get () virtual public;}

十三、重写

0.8以下不支持。

合约中的虚函数(函数使用了virtual修饰的函数)可以在子合约重写该函数,以更改他们在父合约中的行为。重写的函数需要使用关键字override修饰。

pragma solidity ^0.8.0;contract TestOverride {function get() virtual public{}}contract Middle is TestOverride {}contract Inherited is Middle{function get() public override{}}

对于多重继承,如果有多个父合约有相同定义的函数,override关键字后必须指定所有的父合约名称,

contract Base1 {function get () virtual public{}}contract Base2 {function get () virtual public{}}contract Middle2 is Base1, Base2{// 指定所有父合约名称function get() public override( Base1, Base2){}}

注意:如果函数没有标记为virtual(除接口外,因为接口里面所有的函数会自动标记为virtual),那么派生合约是不能重写来更改函数行为的。另外,private的函数是不可标记为virtual的。

十四、接口

0.8以下不支持。

接口和抽象合约类似,与之不同的是,接口不实现任何函数,同时还有以下限制:

  1. 无法继承其他合约或者接口

  2. 无法定义构造函数

  3. 无法定义变量

  4. 无法定义结构体

  5. 无法定义枚举

pragma solidity ^0.8.0;interface TestInterface {function transfer (address recipient, uint amount) external;}contract TestInterfaceSon {function transfer(address recipient, uint amount) public {}}

就像继承其他合约一样,合约可以继承接口,接口中的函数会隐式地标记为virtual,意味着他们会被重写。

十五、库

开发合约的时候,总是会有一些函数经常被多个合约调用,这个时候可以把这些函数封装为一个库,库的关键字用library来定义。

如果合约引用的库函数都是内部(internal)函数,那么编译器在编译合约时,会把库函数的代码嵌入到合约里,就像合约自己实现了这些函数,这时并不会单独部署。

pragma solidity >=0.4.0  a, "SafeMath: addition overflow");return c;}}

库的调用,

contract Test {function add (uint x, uint y) public pure returns(uint){return TestLibrary.add(x, y);// 调用库}}

除了使用上面的TestLibrary.add(x, y)这种方式来调用库函数,还有一个是使用using LibA for B这种附着库的方式。

它表示把所有LibA的库函数关联到数据类型B,这样就可以在B类型直接调用库函数。

contract Test {using TestLibrary for uint;//using TestLibrary for *;function add2 (uint x,uint y) public pure returns (uint){return x.add(y);// uint的数据x就可以直接调用add(y)}}

0x1、示例代码

在https://remix.ethereum.org/下,基于不同版本语法的差异,分别用三个合约文件基本覆盖到了以上语法,

  • Test04.sol => ^0.4.25
  • Test06.sol => ^0.6.10
  • Test08.sol => ^0.8.0
  1. Test04.sol,
pragma solidity ^0.4.25;//pragma solidity ^0.8.0;/** 1.1.1 */contract TestBool {bool flag;int num1 = 100;int num2 = 200;// default falsefunction getFlag() public view returns(bool) {return flag;// false}// 非function getFlag2() public view returns(bool) {return !flag;// true}// 与function getFlagAnd() public view returns(bool) {return (num1 != num2) && !flag;// true}// 或function getFlagOr() public view returns(bool) {return (num1 == num2) || !flag;// true}}/** 1.1.2 */// 整型特性与运算contract TestInteger {int num1; // 有符号整型 int256uint num2; // 无符号整型 uint256function add(uint _a, uint _b) public pure returns(uint) {return _a + _b;}function sub(uint _a, uint _b) public pure returns(uint) {return _a - _b;}function mul(uint _a, uint _b) public pure returns(uint) {return _a * _b;}function div(uint _a, uint _b) public pure returns(uint) {return _a / _b;// 在solidity中,除法是做整除,没有小数点}function rem(uint _a, uint _b) public pure returns(uint) {return _a % _b;}function square(uint _a, uint _b) public pure returns(uint) {return _a ** _b;// 幂运算在0.8.0之后,变为右优先,即a ** b ** c => a ** (b ** c)}function max() public view returns(uint) {return uint(-1);// return type(uint).max;// 0.8不再允许uint(-1)}}// 位运算contract TestBitwise {uint8 num1 = 3;uint8 num2 = 4;function bitAdd() public view returns(uint) {return num1 & num2;}function bitOr() public view returns(uint) {return num1 | num2;}function unBit() public view returns(uint) {return ~num1;}function bitXor() public view returns(uint) {return num1 ^ num2;}function bitRight() public view returns(uint) {return num1 >> 1;}function bitLeft() public view returns(uint) {return num1 << 1;}}/** 1.1.3 - 1.1.6 */contract TestType {fixed num;ufixed num2;fixed8x8 decimal;// fixedMxN, M表示位宽,必须位8的整数倍,N表示十进制小数部分的位数address addr = msg.sender;address addr2 = 0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF;TestBitwise t = TestBitwise(addr);enum ActionChoices { Up, Down, Left, Right }}/** 1.1.7 */// 固定长度的字节数组(静态),以及转换为string类型contract TestBytesFixed {// public 自动生成同名的get方法bytes1 public num1 = 0x7a;// 1 byte = 8 bitbytes1 public num2 = 0x68;bytes2 public num3 = 0x128b;// 2 byte = 16 bit// 获取字节数组长度function getLength() public view returns(uint) {return num3.length; // 2}// 字节数组比较function compare() public view returns(bool) {return num1  zzfunction toString(bytes2 _val) public pure returns(string) {bytes memory buf = new bytes(_val.length);for (uint i = 0; i 切换到0.8则中文字符报错// string memory str = unicode"Hello ";// 0.7.0支持Unicode字符串// string memory str2 = unicode"\u20ac";// 0.7.0支持Unicode字符串function getLength() view public returns(uint) {// 不可以直接获取string的lengthreturn bytes(name).length; // 6}function getLength2() view public returns(uint) {return bytes(name2).length; // 7}function getLength3() view public returns(uint) {return bytes(name3).length; // 6}function getElmName() view public returns(bytes1) {// 不可以直接通过数组下标name[0]获取return bytes(name)[0]; // 0x79}function changeElmName() public {bytes(name)[0] = "h";}function getName() view public returns(bytes) {return bytes(name);}}/** 1.2.2 */// 动态的字节数组,以及转换为string类型contract TestBytesDynamic {bytes public dynamicBytes;function setDynamicBytes(string memory val) public {dynamicBytes = bytes(val);}function getVal() public view returns(string){return string(dynamicBytes);}}/** 1.2.3 */// 定长数组contract TestArrFixed {uint[5] arr = [1,2,3,4,5];// 修改数组元素内容function modifyElements() public {arr[0] = 12;arr[1] = 14;}// 查看数组function watchArr() public view returns(uint[5]) {return arr;}// 数组元素求和计算function sumArr() public view returns(uint) {uint sum = 0;for (uint i = 0; i < arr.length; i++) {sum += arr[i];}return sum;}// 数组长度function getLength() public view returns(uint) {return arr.length;}// delete重置数组某下标的元素值,不会真正删除该元素function deleteElm(uint idx) public {delete arr[idx];}// delete重置整个数组function deleteArr() public {delete arr;}/**定长数组不允许改变长度和push// 压缩数组后,右侧多余元素被丢弃function changeLengthTo1() public {arr.length = 1;}// 扩容数组后,右侧元素补0function changeLengthTo10() public {arr.length = 10;}// 追加新元素function pushElm(uint _elm) public {arr.push(_elm);}*/}// 变长数组contract TestArrDynamic {uint[] arr = [1,2,3,4,5];// 查看数组function watchArr() public view returns(uint[] memory) {return arr;}// 数组长度function getLength() public view returns(uint) {return arr.length;}// 压缩数组后,右侧多余元素被丢弃function changeLengthTo1() public {arr.length = 1;}// 扩容数组后,右侧元素补0function changeLengthTo10() public {arr.length = 10;}// 追加新元素function pushElm(uint _elm) public {arr.push(_elm);}/**// 弹出元素function popElm(uint _elm) public {arr.pop();// 0.4不支持pop}*/// delete重置数组某下标的元素值,不会真正删除该元素function deleteElm(uint idx) public {delete arr[idx];}// delete重置整个数组function deleteArr() public {delete arr;}}/**二维数组solidity的二维数组与其他语言不同,[2] [3]表示3行2列,而其他语言为2行3列;二维动态数组与一维数组类似,可以改变其数组长度;*/contract TestArr2Dimensional {uint[2][3] arr = [[1,2],[3,4],[5,6]];function getRowSize() public view returns(uint) {return arr.length; // 3}function getColSize() public view returns(uint) {return arr[0].length; // 2}function watchArr() public view returns(uint[2][3]) {return arr;// 1,2,3,4,5,6}function sumArr() public view returns(uint) {uint sum = 0;for (uint i = 0; i < getRowSize(); i++) {for (uint j = 0; j < getColSize(); j++) {sum += arr[i][j];}}return sum;}function modifyArr() public {arr[0][0] = 99;}}// 数组字面值contract TestArrLiteral {// 最小存储匹配,未超过255,所以使用uint8存储function getLiteral8() pure public returns(uint8[3]) {return [1,2,3];}// 超过255,所以使用uint16存储function getLiteral16() pure public returns(uint16[3]) {return [256,2,3];// [255,2,3] 不被uint16允许}// 强制转换为uint256function getLiteral256() pure public returns(uint[3]) {return [uint(1),2,3];// 给任意元素强转即可,否则不被允许}// 计算外界传入的内容function addLiterals(uint[3] arr) pure external returns(uint) {uint sum = 0;for (uint i = 0; i string) map;}// 默认为storage类型,只能通过storage类型操作结构体中的mapping类型数据Student storageStu;// mapping类型可以不用在定义的时候赋值,而其他类型不赋值则会报错function init() public pure returns(uint, string) {Student memory stu = Student(100, "Jay");return (stu.id, stu.name);}function init2() public pure returns(uint, string) {Student memory stu = Student({name: "Jay", id: 100});return (stu.id, stu.name);}function init3() public returns(uint, string, string) {Student memory stu = Student({name: "Jay", id: 100});// 直接操作结构体中的mapping不被允许: Student memory out of storage// stu.map[1] = "artist";// 通过storage类型的变量操作结构体中的mappingstorageStu = stu;storageStu.map[1] = "artist";return (storageStu.id, storageStu.name, storageStu.map[1]);}// 结构体作为入参时,为memory类型,并且不能使用public或external修饰函数:Internal or recursive type is not allowed for public or external functions.// 赋值时也要指定为memory,否则报错function testIn(Student stu) internal returns(uint) {return stu.id;}// 结构体作为出参,同样只能private或internal声明内部使用function testOut(Student stu) private returns(Student) {return stu;}}/** 1.3 */contract TestMapping {mapping(address => uint) private scores;// 的单层映射mapping(address => mapping(bytes32 => uint8)) private _scores;// <学生,>的两层映射function getScore() public view returns(address, uint) {address addr = msg.sender;return (addr, scores[addr]);}function setScore() public {scores[msg.sender] = 100;}}/** 二 */contract TestAccessCtrl {constructor () public {}uint public num1 = 1;// 自动为public生成同名的get函数,但在编码时不可直接调用num1()uint private num2 = 2;uint num3 = 3;// 不写则默认privatefunction funcPublic() public returns(string) {return "public func";}function funcPrivate() private returns(string) {return "private func";}function funcInternal() internal returns(string) {return "internal func";}function funcExternal() external returns(string) {return "external func";}function test1(uint choice) public returns(string) {if (choice == 1) return funcPublic();if (choice == 2) return funcPrivate();if (choice == 3) return funcInternal();//if (choice == 4) return funcExternal();// external不允许直接在内部用if (choice == 4) return this.funcExternal();// 间接通过this才可以调用external}}contract TestAccessCtrlSon is TestAccessCtrl {function test2(uint choice) public returns(string) {if (choice == 1) return funcPublic();// public允许派生合约使用//if (choice == 2) return funcPrivate();// private不允许派生合约使用if (choice == 3) return funcInternal();// internal允许派生合约使用//if (choice == 4) return funcExternal();// external也不允许派生合约直接使用}}contract TestAccessCtrl2 {function test2(uint choice) public returns(string) {TestAccessCtrl obj = new TestAccessCtrl();if (choice == 4) {return obj.funcExternal();// external只允许在外部合约中这样间接调用} else return "0x0";}}/** 三 */contract TestFuncDecorator {uint public num = 1;/// purefunction testPure(uint _num) public pure {//uint num1 = num;// pure不允许读状态变量//num = _num;// pure不允许修改状态变量}/// viewfunction testView(uint _num) public view {uint num1 = num;// 允许读状态变量num = _num;// 0.4语法上允许修改状态变量,但实际不会修改,所以num还是1// 0.5及之后不允许在view中这样修改,否则编译不通过}/// payablefunction () public payable {}function getBalance() public view returns(uint) {// balance获取合约地址下的以太币余额return address(this).balance;}// 充值函数payable,只有添加这个关键字,才能在执行这个函数时,给这个合约充以太币,否则该函数自动拒绝所有发送给它的以太币function testPayable() payable public {// transfer转账address(this).transfer(msg.value);}}/** 四 */contract TestConstruct1 {address private _owner;constructor() public {_owner = msg.sender;}/**constructor(int num) public {// 重载构造->编译错误_owner = msg.sender;}*//**function TestFuncDecorator(uint x) {}// 0.5之前还可以用同名函数定义*/}contract TestConstruct2 {uint public num;constructor(uint x) public {// 带参构造,在deploy时传入num = x;}}/** 五 */contract TestModifier {address private _owner;bool public endFlag;// 执行完test后的endFlag仍是trueconstructor() public {_owner = msg.sender;}modifier onlyOwner {// 权限拦截器,非合约部署账号执行test()则被拦截require(_owner == msg.sender, "Auth: only owner is authorized.");_;// 类似被代理的test()方法调用endFlag = true;}function test() public onlyOwner {endFlag = false;}}/** 七 */contract TestEvent {event testEvent(uint indexed a, uint indexed b, uint indexed c, uint result); // indexed不能超过三个function calc(uint a, uint b, uint c) public returns(uint) {uint result = a ** b ** c;emit testEvent(a, b, c, result);// 事件会输出在logs中return result;}}/** 八 */contract TestUnit {function testUnit() pure public {require(1 == 1 seconds);require(1 minutes == 60 seconds);require(1 hours == 60 minutes);require(1 days == 24 hours);require(1 weeks == 7 days);require(1 years == 365 days); // years 从 0.5.0 版本开始不再支持require(1 ether == 1000 finney);require(1 finney == 1000 szabo);require(1 szabo == 1e12 wei);//require(1 gwei == 1e9);// 0.7.0开始加入gwei}}/** 九 */contract TestException {function testAssert(int x) public pure {assert(x >= 0);}function testRequire(int x) public pure {require(x >= 0);//require(x >= 0, "x  a, "Math: addition overflow");return c;}}// 库的调用contract TestLibraryCall {function add(uint x, uint y) public pure returns(uint){return TestLibrary.add(x, y);// 调用库}}// 除了使用上面的TestLibrary.add(x, y)这种方式来调用库函数,还有一个是使用using LibA for B这种附着库的方式。// 它表示把所有LibA的库函数关联到数据类型B,这样就可以在B类型直接调用库函数。contract TestLibraryUsing {using TestLibrary for uint;//using TestLibrary for *;function add2(uint x,uint y) public pure returns (uint){return x.add(y);// uint的数据x就可以直接调用add(y)}}
  1. Test06.sol,
pragma solidity ^0.6.10;/** 1.2.3 */contract TestArrDynamic {uint[] arr = [1,2,3,4,5];// 弹出元素function popElm() public {arr.pop();}function watchArr() public view returns(uint[] memory) {return arr;}/**0.6开始不再可以通过修改length改变数组长度,需要通过push(),push(value),pop的方式,或者赋值一个完整的数组*//**function changeLengthTo1() public {arr.length = 1;}*/}/** 九 */// Solidity0.6版本之后加入。try/catch仅适用于外部调用,另外,try大括号内的代码是不能被catch捕获的。contract TestTryCatch {function execute (uint256 amount) external returns(bool) {try this.run(amount) {// 这里的函数异常会被捕获return true;// 这里的异常不再会被捕获} catch {return false;}}function run(uint256 a) public {//code that can revertrequire(a % 2 == 0, "Ups! Reverting");}}/** 十二 */// 0.6后支持。抽象合约不能使用new创建abstract contract TestAbstractContract {uint public a;constructor(uint _a) internal {a = _a;}function get () virtual public;}
  1. Test08.sol,
pragma solidity ^0.8.0;/** 1.2.1 */contract TestString {function test() public view returns(string memory, string memory, string memory) {string memory str = unicode"Hello ";// Hello string memory str2 = unicode"\u20ac";// €string memory str3 = hex"414243444546474849";// ABCDEFGHI// string memory name3 = "张三";// 0.8不允许中文字符,必须改为unicodereturn (str, str2, str3);}}/** 八 */contract TestGlobalVariable {function test1() public view returns(/**bytes32,*/ uint, uint, address, uint, uint, uint, uint) {return (// blockhash(block.number - 1),// 指定区块的区块哈希,仅可用于最新的 256 个区块且不包括当前区块,否则返回0。0.5移除了block.blockhashblock.basefee,// 当前区块的基础费用block.chainid,// 当前链 idblock.coinbase,// 挖出当前区块的矿工地址block.difficulty,// 当前区块难度block.gaslimit,// 当前区块 gas 限额block.number,// 当前区块号block.timestamp// 自 unix epoch 起始当前区块以秒计的时间戳,0.7.0移除了now);}function test2() public payable returns(bytes memory, address, bytes4, uint, uint256, uint, address) {return (msg.data,// 完整的 calldatamsg.sender,// 消息发送者(当前调用)msg.sig,// calldata 的前 4 字节(也就是函数标识符)msg.value,// 随消息发送的 wei 的数量gasleft(),// 剩余的 gas,0.5移除了msg.gastx.gasprice,// 交易的 gas 价格tx.origin// 交易发起者(完全的调用链));}}/** 十三 */// 合约中的虚函数(函数使用了virtual修饰的函数)可以在子合约重写该函数,以更改他们在父合约中的行为。重写的函数需要使用关键字override修饰。// 0.8以下不支持。contract TestOverride {function get() virtual public{}}contract Middle is TestOverride {}contract Inherited is Middle {function get() public override {}}// 对于多重继承,如果有多个父合约有相同定义的函数,override关键字后必须指定所有的父合约名称contract Base1 {function get() virtual public {}}contract Base2 {function get() virtual public {}}contract Middle2 is Base1, Base2 {// 指定所有父合约名称function get() public override (Base1, Base2){}}/** 十四 */// 0.8以下不支持。interface TestInterface {function transfer(address recipient, uint amount) external;}contract TestInterfaceSon {function transfer(address recipient, uint amount) public {}}

0x2、各版本主要变化

0.5.0

  • sha3改用keccak256, keccak256只允许接收一个参数,使用abi.encodePacked等组合params
  • 构造函数由同名空参方法变成constructor

0.6.0

  • 仅标记virtual的接口才可以被覆盖,覆盖时需要使用新关键字override,如果多个基类同方法名时,需要像这样列出 override(Base1, Base2)
  • 不能通过修改length来修改数组长度,需要通过push(),push(value),pop的方式,或者赋值一个完整的数组
  • 使用abstract标识抽象合约,抽象合约不能使用new创建
  • 回调函数由function()拆分为fallback()和receive()
  • 新增try/catch,可对调用失败做一定处理
  • 数组切片,例如: abi.decode(msg.data[4:], (uint, uint)) 是一个对函数调用payload进行解码底层方法
  • payable(x) 把 address 转换为 address payable

0.7.0

  • call方式调用方法由x.f.gas(1000).value(1 ether)(arg1,arg2)改成 x.f{gas:1000,value:1 ether}(arg1,arg2)
  • now 不推荐使用,改用block.timestamp
  • gwei增加为关键字
  • 字符串支持ASCII字符,Unicode字符串
  • 构造函数不在需要 public修饰符,如需防止创建,可定义成abstract
  • 不允许在同一继承层次结构中具有同名同参数类型的多个事件
  • using A for B,只在当前合约有效, 以前是会继承的,现在需要使用的地方,都得声明一次

0.8.0

  • 弃用safeMath,默认加了溢出检查,如需不要检查使用 unchecked { ... } , 可以节省丢丢手续费
  • 默认支持ABIEncoderV2,不再需要声明
  • 求幂是右结合的,即表达式a**b**c被解析为a**(b**c)。在 0.8.0 之前,它被解析为(a**b)**c
  • assert 不在消耗完 完整的gas,功能和require基本一致,但是try/catch错误里面体现不一样,还有一定作用…
  • 不再允许使用uint(-1),改用type(uint).max