Solidity v0.6.0重大更新

本节重点介绍Solidity 版本0.6.0 中引入的重大突破性变化,以及更改背后的原因以及如何更新受影响的代码。

有关完整列表,请检查 更新日志.

更改编译器不用关注的修改

本节列出了代码行为可能发生的更改, 而编译器不会告诉您。

  • 求幂的结果类型是基数的类型。 曾经是最小的可以容纳基数和指数的类型, 此外,幂的基数类型允许使用带符号的类型。

需要明确指定的更改

本节列出了现在需要更明确地显示代码的更改, 但是语义不会改变。 对于大多数情况,编译器将提供建议。

  • 现在,仅当函数被标记为 virtual 或在接口中定义 时,才可以覆盖。

  在接口外部没有实现的函数必须标记为 virtual 。   覆盖函数或修饰符时,必须使用新关键字 override 。 覆盖多个基类定义的函数或修饰符时, 需要像这样 override(Base1, Base2) 列出.

  • 访问数组的 length 现在是只读的, 即便是storage 数组. 不能通过修改length来调整数组的大小, 必须通过函数 push(), push(value)pop() 或者分配一个完整的数组(不过这会覆盖现有内容)。 这个修改背后的原因是为了防止存储冲突.
  • 关键字 abstract 用来标记一个抽象合约. 如果合约有没有实现的函数, 需要使用。抽象合约不能通过 new 操作符创建,并且不能在编译期生成字节码(bytecode)。
  • 库需要实现所有的函数,不仅仅是内部函数.
  • 内联汇编中声明的变量名称可以不再以 _slot_offset 结尾。
  • 内联汇编中的变量声明可能不会在内联汇编块之外屏蔽声明。

  如果名称包含一个点,则该点之前的前缀可能与内联汇编块外部的任何声明都没有冲突。

  • 现在不允许状态变量屏蔽。 派生合约只能声明基类合约中都没有同名的可见状态变量 x (即如果基类合约中有共有的 状态变量 x, 则派生合约不能在声明 x )。

语义和句法变化

本节列出了必须修改代码的更改.

  • 现在禁止从外部函数类型到 address 的转换。 现在外部函数类型具有一个称为 address 的成员属性,类似于现有的 selector 成员。
  • 对于动态的存储数组, 函数 push(value) 不再返回长度(现在什么都不返回).
  • 未命名的函数通常称为 fallback function ,已拆分为一个使用 fallback 关键字定义的回退函数 和 使用关键字 receive 定义的接受以太函数.
    • 如果有 receive 函数, 向合约转账的时候会调用,不管是否是调用数据(即便没有以太接收), receive 函数隐含带有 payable .
    • 新的回退函数会被调用,当没有其他的函数被匹配时, (如果不存在 receive 函数,则没有调用数据的转账也会调用回退函数). 回退函数可以标记为 payable (也可以不标记). 如果没有 payable 那么附加以太的调用会revert.

    如果是需要实现升级或代理模式,则仅需要实现新的回退函数。

新特性

本节列出了在Solidity 0.6.0 之前不可能实现的事情,或者在Solidity 0.6.0之前很难实现的事情。

  • 加入了 try/catch 语句 使您可以对失败的外部调用做出反应。
  • structenum 类型可以在文件级别声明.
  • 数组切片可以用于 calldata 数组, 例如: abi.decode(msg.data[4:], (uint, uint)) 是一个对函数调用payload进行解码底层方法。
  • Natspec在开发人员文档中支持多个返回参数,并强制执行与 @param 的同名检查。
  • Yul和Inline Assembly有一个名为 leave 的新语句,该语句退出当前函数.
  • 现在可以通过 payable(x)address 转换为 address payable , 当然 x 需要是 地址类型.

接口变化

本节列出了与语言本身无关的更改,但这些更改会影响编译器的接口。 如果命令行上使用编译器的, 或编程方式使用, 或分析编译器输出时, 都会产生改变.

新错误报告器

引入了一个新的错误报告器,能够在命令行输出可读性更强的错误信息。默认是开启的,但可以通过添加 --old-reporter 参数使用旧版本的错误报告器。

Metadata 哈希选项

编译器现在默认在字节码的末尾自带 IPFS 元数据文件的哈希(更多的细节请参见 contract metadata)。在0.6.0版本之前,编译器默认自带`Swarm <https://ethersphere.github.io/swarm-home/>`_ ,为了继续支持这个特性,引入了新的命令行选项 --metadata-hash 。在命令行选项中,可以选择将 ipfsswarm 传入 --metadata-hash 来进行选择。 如果传入 none 则会不附带任何哈希值。

这些变化可以参见 Standard JSON Interface 并且影响编译器生成的元数据JSON。

推荐阅读元数据的方法是去读最后的两字节,以决定CBOR编码,并且对数据区块选择合适的解码。参见: metadata section

Yul 优化器

与传统的字节码优化器一起, Yul 优化器现在默认开启,当你对编译器使用 --optimize 参数的时候,并且可以通过 --no-optimize-yul 参数关闭。这一选项主要影响的是 ABIEncoderV2。

C API 的变化

使用 libsolc 的C API的客户端代码现在由编译器使用的内存控制。 solidity_free 被重命名为 solidity_reset``来保持一致,添加了 ``solidity_alloc 函数和 solidity_freesolidity_compile 的返回值字符串现在必须通过 solidity_free() 来明确。

怎样修改代码

本节提供有关如何为每个重大变更更新先前代码的详细说明。

  • 如果 f 是外部函数类型, address(f) 修改为 f.address
  • 接收以太的函数, function () external [payable] { ... } 修改为 receive() external payable { ... }, fallback() external [payable] { ... } 或者两个度加上. 只要有可能,最好是仅仅使用 receive  函数.
  • 修改 uint length = array.push(value)array.push(value);. 通过 array.length  获取长度.
  • 修改 array.length++array.push() 去增加storage数组长度,  用 pop() 去减少storage数组长度.
  • 对于函数的 @dev 开发这文档中的每个命名返回参数,请定义一个 @return 条目,其中将返回参数名称作为第一个单词。 例如。 已定义函数 f() 是这样  function f() public returns (uint value) , 记录返回值的文档注释是这样: @return value 这是返回值. 您可以混合使用命名和未命名的返回参数,只要按它们在返回元组类型中出现的顺序排列即可。
  • 为内联汇编中的变量声明选择与内联汇编块外的声明不冲突的唯一标识符。
  • 为每个非接口函数添加 virtual. 对于单继承,请在每个重写函数中添加 override 。 对于多重继承,添加 override(A, B, ..), 在在括号中列出所有覆盖函数的的合约。

 当多个基类合约有相同定义的函数时,继承合约必须覆盖所有冲突的函数。