1.6.10 Solidity V0.6.0版本与之前版本的显著区别

注:本章由张华([email protected])提供,谭粤飞修订整理

1.6.10.1 编译器可能不会警告的操作

​乘方(求幂)运算所得结果的类型为底数类型。在以前的版本中,运算结果的类型和对称运算一样,使用能同时支持底数和乘方结果的最小数值类型。现在,有符号数也可以作为底数。

1.6.10.2 显式需求

​本节主要列出了那些在代码中需要显式标记或者申明但语法又没有改变的内容。在大多数情况下,编译器会给出提示信息。
- 函数现在只有被标记为virtual或者在接口中定义才能被重写。未实现的外部接口函数必须标记为virtual,在重写函数和修辞符时,需要使用override关键字。当重写多个基类的函数或者修辞符时,所有基类必须依次放在括号中,例如:override(Base1, Base2)
- 数组的成员length现在为只读属性,对storage数组都是如此。这意味着不能再通过将length重新赋值来改变数组的大小。作为替代,你只能使用push()、push(value)、pop(),或者重新赋值为一个新数组。当然重新赋值会改写现有数组的内容。此次改动的原因是为了防止大型storage数组在使用过程中造成存储冲突。
- 引入新的关键字abstract用来标记那些含有未实现函数的合约。
- 库必须实现它所有的函数,并不仅仅是内部函数。
- 内联汇编中定义的变量名不再以 _slot 或 _offset 结尾。
- 一段内联汇编代码中定义的变量不再能隐藏(shadow)该汇编代码外定义的同名变量。也即是说,当该汇编代码内的变量名与代码外的变量同名时,编译无法通过。如果变量名中包括圆点,则该圆点前的所有前缀不会和外部的任何变量冲突。
- 状态变量隐藏(定义同名状态变量)的用法被取消。现在派生合约中不能定义任何已在基类合约中定义的可见的同名变量。

1.6.10.3 语义或语法改变

- 不支持从外部函数类型到地址类型的转换。外部函数现在有一个address成员,它与已有selector成员类似。
- 对动态storage数组来说,push(value)不再返回数组新的长度,实际上它不返回任何值。
- 未命名,通常作为回调函数(fallback function)使用的函数现在被分为两个函数:一个使用fallback关键字定义的回调函数和一个使用receive关键字定义的接收ether的函数。
  • 当call data为空时,如果定义了接收ether的函数,则无论是否接收到ether,该函数都会被调用,并且该函数是隐式的payable。
  • 如果没有匹配的调用函数,则本版本引入的fallback回调函数会被调用(call data为空,并且不存在receive关键字定义的函数也属于这种情况)。你可以给它定义为payable或不定义。当没有定义为payable时,又没有匹配的函数可供调用,则本次交易回滚。你只在升级或者运行代理模式时才需要实现这个回调函数。

1.6.10.4 新特性

- try/catch 可以和失败的外部调用交互。
- 结构和枚举类型可以在文件作用域被定义。
- 数组切片(array slice)可以用于calldata数组。
例如:abi.decode(msg.data[4:], (uint, uint)) 就是解码函数调用数据的一个底层调用方法。
- Natspec注释支持在开发者文档里使用多个返回参数,和@param遵循相同的强制命名检查规则。
- Yul和内联汇编现在有一个新的语句leave,用来退出当前的函数。
- 从普通地址(address)到可支付地址(address payable)的转换现在可以使用payable(x),x必须为普通地址(address)。

1.6.10.5 接口变化

​本节列出的变化并不是语言本身的变化,而是和编译器接口相关的变化。这些改变可能影响你在命令行上使用编译器、程序接口或者对输出信息的分析等。
- 新的报错方式。新的报错方式让用户使用命令行时得到的错误信息更加易读,现在是默认支持的选项。你也可以使用 --old-reporter 来使用原来的方式。
- 元数据哈希选项。编译器现在默认会在字节码的最后附加元数据文件的IPFS哈希值。在0.6.0之前,附加的是Swarm哈希值,为了继续支持该功能,可以使用命令行选项 --metadata-hash 。它让用户选择附加哪种哈希值。你也可以使用 none 选项来去掉这些哈希值。
- Yul优化。和字节码的优化一样,当用户使用 --optimize 编译选项时Yul优化就默认支持。你也可以使用 --no-optimize-yul 选项来关掉它。它主要影响使用 ABIEncoderV2的代码。
- C API的变化。代码中如果使用了libsolc,则可以用它的C API接口操作编译器使用的内存。为了保证这个变化与老版本一致,solidity_free 重命名为 solidity_reset ,并增加了函数solidity_alloc 和 solidity_free。solidity_compile 现在返回一个字符串,它必须通过调用solidity_free()显式释放。

1.6.10.6 怎样升级你的代码

下面列出了一些升级老版本合约代码的方法:
- 如果f是一个外部函数类型,使用f.address来代替address(f)。
- 使用receive() external payable { … }或者fallback() external [payable] { … }来代替function () external [payable] { … } ,或者两者同时使用。记住,尽可能仅使用receive函数。
- 将uint length = array.push(value)改成array.push(value),数组的新长度可以通过length成员获取。
- 使用array.push()代替array.length++来增加、使用pop()来减少数组的长度。
- 对每个函数的@dev 文档中定义的返回参数定义一个单独的@return 项,该@return项中的第一个单词为该返回参数的名称。比如你有函数f(),其定义为“function f() public returns (uint value)”,该函数有一个@dev文档,该文档定义返回参数“@return value The return value.”。这时你可以把已命名和未命名的参数都罗列在一起,只要你罗列这些参数的顺序和它们返回的顺序相同即可。
- 当一段代码A包含了一段内联汇编代码B时,如果要在内联汇编代码B中定义变量,则为该变量取个唯一的名字,以使它不和代码A中的其它变量重名。
- 在有可能重写的每个非接口函数的定义前增加virtual关键字。对所有未实现的外部接口函数增加virtual关键字。对单继承(single inheritance),在每个要重写的函数前增加override关键字;对多继承(multiple inheritance),必须在括号中依次列出定义该函数的所有基类合约。如果多个基类合约定义了相同的函数,则继承合约必须重写这些函数。