{ 跳至内容 }

Solidity 0.8.0 版本发布公告

发布于 2020年12月16日 由 Solidity 团队

版本发布

Solidity 0.8.0 是 Solidity 编译器和语言的一个重大版本发布。

此版本的一些新特性已在0.8.x 预览版本发布文章中进行了详细说明。请将预览版本的二进制文件视为已失效,请勿再使用。

值得注意的新特性和更改

像往常一样,此重大版本不包含许多新特性,而是包含一些需要在语法或语义上进行向后不兼容调整的更改。有关详细说明,请参阅文档

将影响大多数用户的更改是,算术运算现在默认情况下会进行检查,这意味着溢出和下溢将导致回退。此功能可以通过使用unchecked 块在本地禁用。

第二个非常明显的更改是 ABI 编码器 v2 默认情况下处于激活状态。您可以使用 pragma abicoder v1 激活旧的编码器,或使用 pragma abicoder v2 显式选择 v2 - 这与 pragma experimental ABIEncoderV2 的效果相同。ABI 编码器 v2 比 v1 更复杂,但它还会对输入执行额外的检查,并支持比 v1 更广泛的类型集。

此外,诸如除以零、断言失败等内部错误不再使用无效操作码,而是使用 revert 并附带特殊的错误消息,以避免在这些情况下浪费 gas。

另一个重要的更改是,我们限制了显式转换的可能性以避免歧义。之前所有可能的转换仍然可能,但您可能需要执行两次转换才能达到目标 - 尽管这不会影响生成的代码。

这些只是 0.8.0 中一些重要的重大更改,请参阅下面的更改日志以获取完整列表!

检查算术

Solidity 0.8.x 的“检查算术”特性包含三个子特性

  • 在断言失败和类似条件下回退,而不是使用无效操作码。
  • 检查算术,即在溢出、下溢等情况下回退。
  • unchecked 块。

在断言失败和类似条件下回退,而不是使用无效操作码

以前,诸如除以零、断言失败、数组越界访问等内部错误会导致执行无效操作码。其目的是将这些更关键的错误与非关键错误区分开来,非关键错误超出了智能合约程序员的范围,例如无效输入、余额不足以进行代币转账等。这些非关键错误使用 revert 操作码,并可选地添加错误描述。

无效操作码的问题在于,与 revert 操作码相反,它会消耗所有可用的 gas。这使得它非常昂贵,您应该不惜一切代价避免它。

我们希望将算术溢出视为关键错误,但又不想让它消耗所有 gas。作为折衷方案,我们选择使用 revert 操作码,但提供不同的错误数据,以便静态(和动态)分析工具可以轻松地将它们区分开来。

更具体地说

  • 非关键错误将使用空错误数据或与对具有 Error(string) 签名函数的函数调用的 ABI 编码相对应的错误数据进行回退。
  • 关键错误将使用与对具有 Panic(uint256) 签名函数的函数调用的 ABI 编码相对应的错误数据进行回退。

因此,我们也使用术语“panic”来表示此类关键错误。

为了区分这两种情况,您可以在发生错误时查看返回数据的头四个字节。如果它是 0x4e487b71,则表示 panic,如果它是 0x08c379a0 或消息为空,则表示“常规”错误。

Panic 使用特定的错误代码来区分触发 panic 的某些情况。这是当前的错误代码列表

  1. 0x01:如果您使用计算结果为 false 的参数调用 assert
  2. 0x11:如果算术运算导致在 unchecked { ... } 块之外发生下溢或溢出。
  3. 0x12:如果您除以零或对零取模(例如 5 / 023 % 0)。
  4. 0x21:如果您将过大或负的值转换为枚举类型。
  5. 0x22:如果您访问编码不正确的存储字节数组。
  6. 0x31:如果您在空数组上调用 .pop()
  7. 0x32:如果您在超出范围或负索引处访问数组、bytesN 或数组切片(即 x[i],其中 i >= x.lengthi < 0)。
  8. 0x41:如果您分配了太多内存或创建了过大的数组。
  9. 0x51:如果您调用内部函数类型的零初始化变量。

此代码列表将来可能会扩展。

请注意,您目前无法使用 try ... catch Panic(uint _code) { ... } 来捕获 panic,但这计划在不久的将来实现。

检查算术,即在溢出、下溢等情况下回退。

默认情况下,所有算术运算都将执行溢出和下溢检查(Solidity 已经具有除以零检查)。如果发生下溢或溢出,将抛出 Panic(0x11) 错误,并且调用将回退。

例如,以下代码将触发此类错误

contract C {
    function f() public pure {
        uint x = 0;
        x--;
    }
}

这些检查基于变量的实际类型,因此即使结果适合 256 位的 EVM 字,如果您的类型是 uint8 且结果大于 255,它也会触发 Panic

我们没有对从较大类型到较小类型的显式类型转换引入检查,因为我们认为此类检查可能会出乎意料。我们很乐意收到您对此事的意见!

以下操作(包括它们的复合赋值版本,例如 +=)现在具有之前没有的检查

  • 加法 (+)、减法 (-)、乘法 (*)
  • 增量和减量 (++ / --)
  • 一元否定 (-)
  • 幂运算 (**)
  • 除法(见下文)(/)

有一些您可能意想不到的极端情况。例如,一元否定可以在有符号类型上触发 panic。问题在于,在二进制补码表示中,对于每个位宽,负数的数量比正数的数量多一个。这意味着以下代码将触发 panic

function f() pure {
    int8 x = -128;
    -x;
}

原因是 128 不适合 8 位有符号整数。

出于同样的原因,有符号除法可能导致 panic

function f() pure {
    int8 x = -128;
    x/(-1);
}

每个想要为幂运算编写 SafeMath 函数的人可能都注意到这样做相当昂贵,因为 EVM 没有提供溢出信号。本质上,您必须在不使用 exp 操作码的情况下实现自己的 exp 例程以应对一般情况。

我们希望我们找到了一个相当有效的实现,并且也感谢您对此的反馈!

对于许多特殊情况,我们实际上使用 exp 操作码而不是我们自己的实现来实现它。更具体地说,使用字面量数字作为底数的幂运算将直接使用 exp 操作码。对于值较小的变量作为底数,也存在专门的代码路径。对于最多 306 的底数,如果指数小于硬编码的安全上限,它将直接使用 exp 操作码。如果底数或指数过大,它可能会回退到基于循环的实现。

unchecked

由于检查算术使用更多 gas,如上一节所述,并且因为有时您确实希望包装行为,例如在实现加密例程时,我们提供了一种禁用检查算术并重新启用以前的“包装”或“模”算术的方法

表单 unchecked { ... } 的块内的所有操作都使用包装算术。您可以将此特殊块用作另一个块内的常规语句

contract C {
    function f() public pure returns (uint) {
        uint x = 0;
        unchecked { x--; }
        return x;
    }
}

unchecked 块可以包含任意数量或任何类型的语句,并且会创建自己的变量作用域。如果 unchecked 块中包含函数调用,则函数不会继承该设置 - 如果您也希望它使用包装算术,则必须在函数内部使用另一个 unchecked 块。

我们决定对 unchecked 块进行一些限制

  1. 在修饰符中,您不能在 unchecked 块内使用 _;,因为这可能会导致对属性是否被继承产生混淆。

  2. 您只能在块内使用它,而不能用它替换块。例如,以下代码片段均无效

    function f() unchecked { uint x = 7; }
    
    function f() pure {
        uint x;
        for (uint i = 0; i < 100; i++) unchecked { x += i; }
    }
    

    您必须将 unchecked 块包装在另一个块中才能使其工作。

显式转换

只有当符号、宽度、可支付性或类型类别(intaddressbytesNN 等)最多发生一次更改时,显式转换才可能。

例如,将 int8 转换为 uint16 只有在您通过 uint8int16 进行转换时才可能。由于中间转换的选择会对结果产生影响,因此我们希望将其明确化

  • uint16(int16(int8(-1))) 的结果为 0xffff,而
  • uint16(uint8(int8(-1)))0xff

在可支付地址、字面量、枚举和合约方面还有更多此类更改。有关更改的完整列表,请参阅文档

一般说明

由于我们通常不向后移植错误修复,因此建议将所有代码升级为与 Solidity v0.8.0 兼容。

您可以在此处找到有关如何更新代码的指南。

请注意,下面列出的更改是**0.7.6 和 0.8.0 之间的更改**。对于在 0.7.x 系列期间引入的更改,请参阅此博客上的各个更改日志或版本发布公告。

完整更改日志

重大更改

  • 代码生成器:默认情况下会检查所有算术运算。可以使用 unchecked { ... } 禁用这些检查。
  • 代码生成器:如果访问存储中的字节数组且其长度编码不正确,则导致恐慌。
  • 代码生成器:在断言失败时,使用错误签名 Panic(uint256) 和错误代码的 revert,而不是无效操作码。
  • 命令行界面:JSON 字段 abidevdocuserdocstorage-layout 现在是子对象而不是字符串。
  • 命令行界面:删除 --old-reporter 选项。
  • 命令行界面:删除旧版 --ast-json 选项。现在仅支持 --ast-compact-json 选项。
  • 通用:默认启用 ABI 编码器 v2。
  • 通用:删除全局函数 log0log1log2log3log4
  • 解析器:指数运算符是右结合的。 a**b**c 被解析为 a**(b**c)
  • 扫描器:删除对 \b\f\v 转义序列的支持。
  • 标准 JSON:删除 legacyAST 选项。
  • 类型检查器:函数调用选项只能给出一次。
  • 类型系统:不允许使用名称为 thissuper_ 的声明,公共函数和事件除外。
  • 类型系统:不允许在 receive() 函数中使用 msg.data
  • 类型系统:不允许 type(super)
  • 类型系统:不允许枚举具有超过 256 个成员。
  • 类型系统:不允许从负字面量和大于 type(uint160).max 的字面量到 address 类型的显式转换。
  • 类型系统:不允许 byte 类型。它曾是 bytes1 的别名。
  • 类型系统:显式转换为 address 类型始终返回非可支付的 address 类型。特别是,address(u)address(b)address(c)address(this) 的类型为 address 而不是 address payable(这里 ubc 是类型分别为 uint160bytes20 和合约类型的任意变量)。
  • 类型系统:如果两种类型之间的显式转换同时更改了符号、宽度或种类中的多个,则不允许。
  • 类型系统:仅当值适合枚举时,才允许从字面量到枚举的显式转换。
  • 类型系统:从字面量到整数类型的显式转换与隐式转换一样严格。
  • 类型系统:引入 address(...).code 以检索代码作为 bytes memory。可以通过 address(...).code.length 获取大小,但目前它始终包括复制代码。
  • 类型系统:引入 block.chainid 用于检索当前链 ID。
  • 类型系统:支持 address(...).codehash 用于检索帐户的代码哈希。
  • 类型系统:全局变量 tx.originmsg.sender 的类型为 address 而不是 address payable
  • 类型系统:一元否定运算符只能用于带符号整数,不能用于无符号整数。
  • 视图纯检查器:将 chainid 标记为视图。
  • Yul:不允许使用保留标识符,例如 EVM 指令,即使它们在给定的方言/EVM 版本中不可用。
  • Yul:"带对象的 EVM" 方言中的 assignimmutable 内置函数将要修改的代码的基本偏移量作为附加参数。

语言特性

  • 现在可以使用成员表示法调用超级构造函数,例如 M.C(123)

错误修复

  • 类型检查器:在数组长度表达式中使用常量时,执行正确的截断整数算术运算。

AST 更改

  • 新的 AST 节点 IdentifierPath 在许多地方替换了 UserDefinedTypeName
  • 新的 AST 节点 UncheckedBlock 用于 unchecked { ... }

非常感谢所有帮助使此版本发布成为可能的贡献者!

从此处下载新版本的 Solidity 这里

上一篇

下一篇

参与进来

GitHub

推特

Mastodon

矩阵

了解更多

博客文档用例贡献关于论坛

2024Solidity 团队

安全策略

行为准则