******************************** 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 之前无法实现或更难实现的功能。 * :ref:`try/catch 语句 ` 允许你对失败的外部调用做出反应。 * ``struct`` 和 ``enum`` 类型可以在文件级别声明。 * 数组切片可以用于 calldata 数组,例如 ``abi.decode(msg.data[4:], (uint, uint))`` 是解码函数调用有效负载的一种低级方式。 * Natspec 支持开发文档中的多个返回参数,强制执行与 ``@param`` 相同的命名检查。 * Yul 和内联汇编有一个新语句 ``leave``,用于退出当前函数。 * 现在可以通过 ``payable(x)`` 将 ``address`` 转换为 ``address payable``,其中 ``x`` 必须是 ``address`` 类型。 接口变更 ================= 本节列出了与语言本身无关的变更,但对编译器的接口有影响。这些可能会改变你在命令行上使用编译器的方式、如何使用其可编程接口或如何分析其生成的输出。 新错误报告器 ~~~~~~~~~~~~~~~~~~ 引入了新的错误报告器,旨在在命令行上生成更易于访问的错误消息。默认启用,但传递 ``--old-reporter`` 将回退到已弃用的旧错误报告器。 元数据哈希选项 ~~~~~~~~~~~~~~~~~~~~~ 编译器现在默认将元数据文件的 `IPFS `_ 哈希附加到字节码的末尾(有关详细信息,请参见 :doc:`合约元数据 ` 文档)。 在 0.6.0 之前,编译器默认附加 `Swarm `_ 哈希,为了仍然支持此行为,引入了新的命令行选项 ``--metadata-hash``。 它允许你选择要生成和附加的哈希,通过将 ``ipfs`` 或 ``swarm`` 作为值传递给 ``--metadata-hash`` 命令行选项。传递值 ``none`` 将完全移除哈希。 这些更改也可以通过 :ref:`标准 JSON 接口 ` 使用,并影响编译器生成的元数据 JSON。 读取元数据的推荐方法是读取最后两个字节以确定 CBOR 编码的长度,并对该数据块进行适当解码,如 :ref:`元数据部分 ` 中所述。 Yul 优化器 ~~~~~~~~~~~~~ 与传统字节码优化器一起,:doc:`Yul ` 优化器现在在使用 ``--optimize`` 调用编译器时默认启用。可以通过使用 ``--no-optimize-yul`` 调用编译器来禁用它。这主要影响使用 ABI 编码器 v2 的代码。 C API 变更 ~~~~~~~~~~~~~ 使用 ``libsolc`` 的 C API 的客户端代码现在控制编译器使用的内存。为了使此更改一致,``solidity_free`` 被重命名为 ``solidity_reset``,添加了 ``solidity_alloc`` 和 ``solidity_free`` 函数,并且 ``solidity_compile`` 现在返回一个必须通过 ``solidity_free()`` 显式释放的字符串。 如何修改代码 ======================= 本节提供了有关如何变更先前代码以应对每个重大更改的详细说明。 * 将 ``address(f)`` 更改为 ``f.address``,其中 ``f`` 为外部函数类型。 * 将 ``function () external [payable] { ... }`` 替换为 ``receive() external payable { ... }``、``fallback() 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()``。