Solidity v0.6.0 突破性变化

本节重点介绍 Solidity 版本 0.6.0 中引入的主要突破性变化,以及这些变化背后的原因以及如何更新受影响的代码。有关完整列表,请查看 发行变更日志.

编译器可能不会警告的更改

本节列出了代码的行为可能会发生变化但编译器不会告诉你的更改。

  • 求幂的结果类型为底数的类型。它曾经是能够容纳底数类型和指数类型的最小的类型,就像对称运算一样。此外,有符号类型允许作为求幂的底数。

显式要求

本节列出了代码现在需要更加显式,但语义不会更改的更改。对于大多数主题,编译器将提供建议。

  • 函数现在只能在标记为 virtual 关键字或在接口中定义时被重写。接口外部没有实现的函数必须标记为 virtual。当重写函数或修饰符时,必须使用新的关键字 override。当重写多个并行基类中定义的函数或修饰符时,所有基类都必须像这样在关键字后面的括号中列出:override(Base1, Base2)

  • 数组的 length 的成员访问现在始终是只读的,即使对于存储数组也是如此。现在无法通过为其长度分配新值来调整存储数组的大小。请改为使用 push()push(value)pop(),或者分配一个完整的数组,当然这将覆盖现有内容。这样做的原因是防止大型存储数组的存储冲突。

  • 新的关键字 abstract 可用于将合约标记为抽象。如果合约没有实现其所有函数,则必须使用它。抽象合约不能使用 new 运算符创建,并且在编译期间无法为它们生成字节码。

  • 库必须实现其所有函数,而不仅仅是内部函数。

  • 在内联汇编中声明的变量名称不再以 _slot_offset 结尾。

  • 内联汇编中的变量声明不再可以隐藏内联汇编块外部的任何声明。如果名称包含句点,则其直到句点之前的部分不得与内联汇编块外部的任何声明冲突。

  • 在内联汇编中,不带参数的操作码现在用“内置函数”表示,而不是独立标识符。因此 gas 现在是 gas()

  • 现在不允许状态变量遮蔽。派生合约只能声明状态变量 x,前提是其任何基类中都没有相同名称的可见状态变量。

语义和语法更改

本节列出了您必须修改代码,并且之后代码执行其他操作的更改。

  • 现在不允许从外部函数类型转换为 address。相反,外部函数类型具有名为 address 的成员,类似于现有的 selector 成员。

  • 动态存储数组的函数 push(value) 不再返回新长度(它什么也不返回)。

  • 通常称为“回退函数”的无名函数被拆分为使用 fallback 关键字定义的新回退函数,以及使用 receive 关键字定义的接收以太坊函数。

    • 如果存在,接收以太坊函数将在调用数据为空时被调用(无论是否接收以太坊)。此函数隐式地为 payable

    • 当没有其他函数匹配时,将调用新的回退函数(如果接收以太坊函数不存在,则包括带有空调用数据的调用)。您可以将此函数设为 payable 或不是。如果它不是 payable,则没有匹配任何其他函数的交易将发送值并回滚。您只需要在遵循升级或代理模式时才实现新的回退函数。

新功能

本节列出了在 Solidity 0.6.0 之前不可能实现或更难实现的内容。

  • try/catch 语句 允许您对失败的外部调用做出反应。

  • structenum 类型可以在文件级别声明。

  • 数组切片可用于调用数据数组,例如 abi.decode(msg.data[4:], (uint, uint)) 是一种以低级方式解码函数调用有效负载的方法。

  • Natspec 在开发人员文档中支持多个返回值参数,强制执行与 @param 相同的命名检查。

  • Yul 和内联汇编具有一个名为 leave 的新语句,该语句退出当前函数。

  • 现在可以通过 payable(x)address 转换为 address payable,其中 x 必须为 address 类型。

接口更改

本节列出了与语言本身无关,但会影响编译器接口的更改。这些可能会改变您在命令行上使用编译器、使用其可编程接口或分析其生成的输出的方式。

新的错误报告器

引入了新的错误报告器,旨在生成更易于访问的命令行错误消息。它默认启用,但传递 --old-reporter 将回退到已弃用的旧错误报告器。

元数据哈希选项

编译器现在默认将元数据文件的 IPFS 哈希追加到字节码的末尾(有关详细信息,请参阅关于 合约元数据 的文档)。在 0.6.0 之前,编译器默认追加 Swarm 哈希,为了仍然支持此行为,引入了新的命令行选项 --metadata-hash。它允许您选择要生成和追加的哈希,方法是将 ipfsswarm 作为值传递给 --metadata-hash 命令行选项。传递值 none 将完全删除哈希。

这些更改也可以通过 标准 JSON 接口 使用,并影响编译器生成的元数据 JSON。

读取元数据的推荐方法是读取最后两个字节以确定 CBOR 编码的长度,并在该数据块上执行适当的解码,如 元数据部分 中所述。

Yul 优化器

与传统字节码优化器一起,Yul 优化器现在默认在您使用 --optimize 调用编译器时启用。可以通过使用 --no-optimize-yul 调用编译器来禁用它。这主要影响使用 ABI 编码器 v2 的代码。

C API 更改

现在使用 libsolc 的 C API 的客户端代码控制着编译器使用的内存。为了使此更改保持一致,solidity_free 已重命名为 solidity_reset,添加了函数 solidity_allocsolidity_free,而 solidity_compile 现在返回一个字符串,必须通过 solidity_free() 显式释放。

如何更新您的代码

本节详细介绍了如何为每个重大更改更新之前的代码。

  • address(f) 更改为 f.address,其中 f 为外部函数类型。

  • receive() external payable { ... }fallback() external [payable] { ... } 或两者替换 function () external [payable] { ... }。尽可能地只使用 receive 函数。

  • uint length = array.push(value) 更改为 array.push(value);。新长度可以通过 array.length 访问。

  • array.length++ 更改为 array.push() 以增加,并使用 pop() 来减少存储数组的长度。

  • 对于函数 @dev 文档中的每个命名返回值参数,定义一个 @return 条目,该条目包含参数名称作为第一个词。例如,如果您有一个函数 f() 定义为 function f() public returns (uint value) 并且有一个 @dev 对其进行注释,请像这样记录其返回值参数:@return value The return value.。只要通知按它们在元组返回类型中出现的顺序排列,您就可以混合命名和未命名返回值参数文档。

  • 为内联汇编中的变量声明选择唯一的标识符,这些标识符不会与内联汇编块外部的声明冲突。

  • 在您打算覆盖的每个非接口函数中添加 virtual。在接口外部没有实现的所有函数中添加 virtual。对于单继承,在每个覆盖函数中添加 override。对于多重继承,添加 override(A, B, ..),您在括号中列出定义覆盖函数的所有合约。当多个基类定义相同函数时,继承合约必须覆盖所有冲突函数。

  • 在内联汇编中,为所有不接受参数的操作码添加 ()。例如,将 pc 更改为 pc(),并将 gas 更改为 gas()