Solidity语法大致总结

目录

  • 一、数据类型
    • 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 false
    function 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; // 有符号整型 int256
  uint num2; // 无符号整型 uint256

  function 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 bit
    bytes1 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 < num2;
    }
    // 字节数组位运算
    function bitwise() public view returns(bytes1,bytes1,bytes1) {  // 多返回值
        return ((num1 & num2), (num1 | num2), (~num1));
    }

    // 先转为bytes动态数组,再通过string构造  0x7a7a -> zz
    function 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个byte
    string name3 = "张三";  // 中文在string中使用utf8的编码方式存储,占用3个byte

    function getLength() view public returns(uint) {
        // 不可以直接获取string的length
        return 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;
    }
    // 扩容数组后,右侧元素补0
    function 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;
    }
    // 扩容数组后,右侧元素补0
    function 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允许
    }
    // 强制转换为uint256
    function 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类型的变量操作结构体中的mapping
        storageStu = 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;  // 不写则默认private
    function 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函数,

Solidity语法大致总结_第1张图片

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

2.3、internal

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

2.4、external

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

三、函数修饰符

contract TestFuncDecorator {
    uint public num = 1;
    /// pure
    function testPure(uint _num) public pure {
        //uint num1 = num;  // pure不允许读状态变量
        //num = _num;  // pure不允许修改状态变量
    }
    /// view
    function testView(uint _num) public view {
        uint num1 = num;  // 允许读状态变量
        num = _num;  // 0.4语法上允许修改状态变量,但实际不会修改,所以num还是1
        // 0.5及之后不允许在view中这样修改,否则编译不通过
    }
    /// payable
    function () 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仍是true
    constructor() 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.blockhash
            block.basefee,  // 当前区块的基础费用
            block.chainid,  // 当前链 id
            block.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,  // 完整的 calldata
            msg.sender,  // 消息发送者(当前调用)
            msg.sig,  // calldata 的前 4 字节(也就是函数标识符)
            msg.value,  // 随消息发送的 wei 的数量
            gasleft(),  // 剩余的 gas,0.5移除了msg.gas
            tx.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 revert
        require(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又继承X
contract 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 <0.7.0;
//pragma solidity ^0.4.25;

library TestLibrary{
	function add (uint a,uint b) internal pure returns (uint){
		uint c = a + b;
		require(c > 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 false
    function 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; // 有符号整型 int256
    uint num2; // 无符号整型 uint256

    function 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 bit
    bytes1 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 < num2;
    }
    // 字节数组位运算
    function bitwise() public view returns(bytes1,bytes1,bytes1) {  // 多返回值
        return ((num1 & num2), (num1 | num2), (~num1));
    }

    // 先转为bytes动态数组,再通过string构造  0x7a7a -> zz
    function 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);
    }
}
// 固定长度的字节数组(静态)扩容和压缩
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.2.1 */
// 修改string类型的数据
contract TestString {
    string name = 'yorick';  // 字符串可以使用单引号或者双引号
    string name2 = "!@#$%^&";  // 特殊字符占1个byte
    string name3 = "张三";  // 中文在string中使用utf8的编码方式存储,占用3个byte  ->  切换到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的length
        return 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;
    }
    // 扩容数组后,右侧元素补0
    function 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;
    }
    // 扩容数组后,右侧元素补0
    function 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允许
    }
    // 强制转换为uint256
    function 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 */
// 结构体初始化的两种方法
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类型的变量操作结构体中的mapping
        storageStu = 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;  // 不写则默认private
    function 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;
    /// pure
    function testPure(uint _num) public pure {
        //uint num1 = num;  // pure不允许读状态变量
        //num = _num;  // pure不允许修改状态变量
    }
    /// view
    function testView(uint _num) public view {
        uint num1 = num;  // 允许读状态变量
        num = _num;  // 0.4语法上允许修改状态变量,但实际不会修改,所以num还是1
        // 0.5及之后不允许在view中这样修改,否则编译不通过
    }
    /// payable
    function () 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仍是true
    constructor() 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 < 0");
    }
    function testRevert(int x, int y) public pure {
        if (x != y) revert("x should equal to b");
    }
}
/** 十 */
contract TestOverload {  // 重载
    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;
    }
}
/** 十一 */
contract TestExtendA { // 父类TestExtendA要写在子类TestExtend之前
    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 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又继承X
contract 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 {
}
/** 十五 */
library TestLibrary{
	function add (uint a,uint b) internal pure returns (uint){
		uint c = a + b;
		require(c > 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 revert
        require(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不允许中文字符,必须改为unicode
        return (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.blockhash
            block.basefee,  // 当前区块的基础费用
            block.chainid,  // 当前链 id
            block.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,  // 完整的 calldata
            msg.sender,  // 消息发送者(当前调用)
            msg.sig,  // calldata 的前 4 字节(也就是函数标识符)
            msg.value,  // 随消息发送的 wei 的数量
            gasleft(),  // 剩余的 gas,0.5移除了msg.gas
            tx.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

你可能感兴趣的:(区块链,区块链,Solidity,Ethereum)