******************************** Solidity v0.8.0 重大变更 ******************************** 本节重点介绍了 Solidity 版本 0.8.0 中引入的主要重大变更。 完整列表请查看 `变更日志 `_。 语义的静默变化 =============================== 本节列出了现有代码在没有编译器通知的情况下改变其行为的变化。 * 算术运算在下溢和上溢时会回退。你可以使用 ``unchecked { ... }`` 来使用之前的环绕行为。 溢出检查非常常见,因此我们将其设为默认,以提高代码的可读性,即使这会略微增加 gas 成本。 * ABI 编码器 v2 默认启用。 你可以选择使用旧行为,使用 ``pragma abicoder v1;``。 pragma ``pragma experimental ABIEncoderV2;`` 仍然有效,但已被弃用且没有效果。 如果你想要明确,请使用 ``pragma abicoder v2;``。 请注意,ABI 编码器 v2 支持比 v1 更多的类型,并对输入执行更多的有效性检查。 ABI 编码器 v2 使某些函数调用的成本更高,并且它还可能使合约调用 在包含不符合参数类型的数据时回退,而在使用 ABI 编码器 v1 时不会回退。 * 指数运算是右结合的,即表达式 ``a**b**c`` 被解析为 ``a**(b**c)``。 在 0.8.0 之前,它被解析为 ``(a**b)**c``。 这是解析指数运算符的常见方式。 * 失败的断言和其他内部检查,如除以零或算术溢出,不使用无效操作码,而是使用回退操作码。 更具体地说,它们将使用等于对 ``Panic(uint256)`` 的函数调用的错误数据,错误代码特定于情况。 这将节省错误时的 gas ,同时仍然允许静态分析工具区分 这些情况与无效输入的回退,例如失败的 ``require``。 * 如果访问存储中长度编码不正确的字节数组,将导致恐慌。 合约无法进入这种情况,除非使用内联汇编修改存储字节数组的原始表示。 * 如果在数组长度表达式中使用常量,之前版本的 Solidity 会在评估树的所有分支中使用任意精度。 现在,如果常量变量用作中间表达式,它们的值将以与在运行时表达式中使用时相同的方式进行适当舍入。 * 类型 ``byte`` 已被移除。它是 ``bytes1`` 的别名。 新限制 ================ 本节列出了可能导致现有合约无法再编译的变化。 * 与文字的显式转换相关的新限制。以下情况下的先前行为可能存在歧义: 1. 不允许将负文字和大于 ``type(uint160).max`` 的文字显式转换为 ``address``。 2. 仅当文字位于 ``type(T).min`` 和 ``type(T).max`` 之间时,才允许在文字和整数类型 ``T`` 之间进行显式转换。特别是,将 ``uint(-1)`` 替换为 ``type(uint).max``。 3. 仅当文字可以表示枚举中的值时,才允许在文字和枚举之间进行显式转换。 4. 在文字和 ``address`` 类型之间的显式转换(例如 ``address(literal)``)的类型为 ``address`` 而不是 ``address payable``。 可以通过使用显式转换来获得可支付地址类型,即 ``payable(literal)``。 * :ref:`地址字面量 ` 的类型为 ``address`` 而不是 ``address payable``。它们可以通过使用显式转换转换为 ``address payable``,例如 ``payable(0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF)``。 * 显式类型转换的新限制。仅当在符号、宽度或类型类别(``int``、``address``、``bytesNN`` 等)中最多有一次变化时,才允许转换。 要进行多次变化,请使用多次转换。 让我们使用符号 ``T(S)`` 来表示显式转换 ``T(x)``,其中 ``T`` 和 ``S`` 是类型,``x`` 是类型 ``S`` 的任意变量。 一个不允许的转换示例是 ``uint16(int8)``,因为它同时改变了宽度(8 位到 16 位)和符号(有符号整数到无符号整数)。 为了进行转换,必须经过一个中间类型。在前面的示例中,这将是 ``uint16(uint8(int8))`` 或 ``uint16(int16(int8))``。 请注意,这两种转换方式将产生不同的结果,例如,对于 ``-1``。 以下是一些被此规则禁止的转换示例。 - ``address(uint)`` 和 ``uint(address)``:同时转换类型类别和宽度。将其替换为 ``address(uint160(uint))`` 和 ``uint(uint160(address))``。 - ``payable(uint160)``, ``payable(bytes20)`` 和 ``payable(integer-literal)``:同时转换类型类别和状态可变性。 将其替换为 ``payable(address(uint160))``, ``payable(address(bytes20))`` 和 ``payable(address(integer-literal))``。 请注意,``payable(0)`` 是有效的,并且是此规则的例外。 - ``int80(bytes10)`` 和 ``bytes10(int80)``:同时转换类型类别和符号。将其替换为 ``int80(uint80(bytes10))`` 和 ``bytes10(uint80(int80))``。 - ``Contract(uint)``:同时转换类型类别和宽度。将其替换为 ``Contract(address(uint160(uint)))``。 这些转换被禁止以避免歧义。例如,在表达式 ``uint16 x = uint16(int8(-1))`` 中,``x`` 的值将取决于先应用符号转换还是宽度转换。 * 函数调用选项只能给出一次,即 ``c.f{gas: 10000}{value: 1}()`` 是无效的,必须更改为 ``c.f{gas: 10000, value: 1}()``。 * 全局函数 ``log0``、``log1``、``log2``、``log3`` 和 ``log4`` 已被移除。 这些是大多数未使用的低级函数。它们的行为可以通过内联汇编访问。 * ``enum`` 定义不能包含超过 256 个成员。 这将使得可以安全地假设 ABI 中的底层类型始终为 ``uint8``。 * 名为 ``this``、``super`` 和 ``_`` 的声明被禁止,公共函数和事件除外。这个例外是为了使得能够声明在其他语言中实现的合约接口,这些语言允许这样的函数名称。 * 移除对代码中 ``\b``、``\f`` 和 ``\v`` 转义序列的支持。 它们仍然可以通过十六进制转义插入,例如 ``\x08``、``\x0c`` 和 ``\x0b``。 * 全局变量 ``tx.origin`` 和 ``msg.sender`` 的类型为 ``address`` 而不是 ``address payable``。 可以通过使用显式转换将它们转换为 ``address payable``,即 ``payable(tx.origin)`` 或 ``payable(msg.sender)``。 这个变化是因为编译器无法确定这些地址是否可支付,因此现在需要显式转换以使这一要求可见。 * 显式转换为 ``address`` 类型始终返回不可支付的 ``address`` 类型。特别是,以下显式转换的类型为 ``address`` 而不是 ``address payable``: - ``address(u)`` 其中 ``u`` 是类型为 ``uint160`` 的变量。可以通过使用两个显式转换将 ``u`` 转换为类型 ``address payable``,即 ``payable(address(u))``。 - ``address(b)`` 其中 ``b`` 是类型为 ``bytes20`` 的变量。可以通过使用两个显式转换将 ``b`` 转换为类型 ``address payable``,即 ``payable(address(b))``。 - ``address(c)`` 其中 ``c`` 是一个合约。之前,这种转换的返回类型取决于合约是否可以接收以太(要么有接收函数,要么有可支付的回退函数)。 转换 ``payable(c)`` 的类型为 ``address payable``,仅在合约 ``c`` 可以接收以太时允许。一般来说,可以通过以下显式转换将 ``c`` 转换为类型 ``address payable``:``payable(address(c))``。 请注意,``address(this)`` 属于与 ``address(c)`` 相同的类别,并且适用相同的规则。 * 内联汇编中的 ``chainid`` 内置函数现在被视为 ``view`` 而不是 ``pure``。 * 不再可以对无符号整数使用一元否定,只能对有符号整数使用。 接口更改 ================= * ``--combined-json`` 的输出已更改:JSON 字段 ``abi``、``devdoc``、``userdoc`` 和 ``storage-layout`` 现在是子对象。在 0.8.0 之前,它们被序列化为字符串。 * "遗留 AST" 已被移除(命令行接口上的 ``--ast-json`` 和标准 JSON 中的 ``legacyAST``)。使用 "紧凑 AST"(``--ast-compact-json`` 或 ``AST``)作为替代。 * 旧的错误报告器(``--old-reporter``)已被移除。 如何更新你的代码 ======================= - 如果你依赖于包装算术,请将每个操作包围在 ``unchecked { ... }`` 中。 - 可选:如果你使用 SafeMath 或类似库,将 ``x.add(y)`` 更改为 ``x + y``,将 ``x.mul(y)`` 更改为 ``x * y`` 等等。 - 如果你希望保留旧的 ABI 编码器,请添加 ``pragma abicoder v1;``。 - 可选地移除 ``pragma experimental ABIEncoderV2`` 或 ``pragma abicoder v2``,因为它是多余的。 - 将 ``byte`` 更改为 ``bytes1``。 - 如有需要,添加中间显式类型转换。 - 将 ``c.f{gas: 10000}{value: 1}()`` 合并为 ``c.f{gas: 10000, value: 1}()``。 - 将 ``msg.sender.transfer(x)`` 更改为 ``payable(msg.sender).transfer(x)`` 或使用类型为 ``address payable`` 的存储变量。 - 将 ``x**y**z`` 更改为 ``(x**y)**z``。 - 使用内联汇编替代 ``log0``、...、``log4``。 - 通过从该类型的最大值中减去无符号整数并加 1 来否定无符号整数(例如 ``type(uint256).max - x + 1``,同时确保 ``x`` 不为零)。