我们很高兴地宣布 Solidity 编译器的发布v0.8.22。这个最新版本的编译器包含一系列语言和编译器改进,例如文件级事件定义、未检查循环增量的优化、支持导入 EVM 汇编 JSON 等。
重要说明
此版本弃用了对 Constantinople 之前的 EVM 版本的支持,这些版本越来越难以维护。这些旧版本在以太坊主网和测试网上早已过时,我们怀疑它们在其他网络上也不再相关。复杂的代码路径和变通方法减慢了针对新版本的功能的开发和测试,我们希望在编译器的未来版本中停止对它们的支持。如果您依赖于对这些 EVM 版本的支持,请联系我们。
值得注意的新功能
未检查循环增量
使用未检查算术来递增for 循环计数器在进行 gas 优化时很常见。让我们以以下带计数器 i 的循环为例
for (uint i = 0; i < array.length; ++i) { acc += array[i]; // i is not modified by the loop body }
在很多情况下(有关精确条件,请参见下文),比较将确保 i 永远不会达到其类型的最大值,因此通常可以安全地假设 i 不会溢出,因为循环将在溢出之前停止。在这种情况下,对计数器进行安全检查将是多余的并且浪费 gas。这鼓励用户使用详细的模式绕过检查,该模式将计数器增量包装在循环体内的未检查算术块中
for (uint i = 0; i < array.length;) { acc += array[i]; unchecked { i++; } // i gets incremented without overflow checks -- less gas used }
Solidity 0.8.22 引入了一种溢出检查优化,该优化自动生成 for 循环计数器的未检查算术增量。这种新的优化消除了在 for 循环体中使用糟糕的未检查增量模式的必要性,例如前面的示例中所示的那样。
相反,新的优化使用户能够返回到原始的、更易读的代码,而不会牺牲 gas 效率。
新的优化避免溢出检查的精确条件如下
- 循环条件是 i < ... 形式的比较,用于局部变量 i(从现在起称为“循环计数器”)。
- 此比较必须在与循环计数器相同的类型上执行,即右操作数的类型必须隐式可转换为循环计数器的类型,以便在比较之前不会隐式扩展循环计数器。
- 循环计数器必须是内置整数类型的局部变量。
- 循环表达式必须是循环计数器的前缀或后缀增量,即 i++ 或 ++i。
- 循环计数器不得在循环条件或循环体中修改。
为了阐明第二个条件,请考虑以下代码片段
for (uint8 i = 0; i < uint16(1000); ++i) { // loop body }
在这种情况下,i 在比较之前被转换为 uint16,并且条件实际上永远不会为假,因此无法删除增量的溢出检查。
另外,请注意 < 是唯一会触发优化的比较运算符。运算符 <= 及其他运算符被有意排除在外。此外,运算符必须是内置的 - 用户定义的 < 不符合条件。
优化简单明了,并且始终是有益的,因此即使使用通用 settings.optimizer.enabled 设置禁用优化器的其余部分时,也会启用它。可以通过在标准 JSON 输入中将 settings.optimizer.details.simpleCounterForLoopUncheckedIncrement 设置为 false 来显式关闭它。无法使用命令行界面禁用它。
调整 Yul 优化器以重新物化零字面量
此版本基于版本 0.8.20 中引入的 PUSH0 操作码的支持,通过扩展重新物化器 优化步骤始终重新物化零字面量,而不是将它们存储为变量引用,这反过来允许使用 PUSH0 而不是 DUP 以降低 gas 成本。为了确保有效地执行此操作,重新物化器 和 未使用修剪器 步骤已添加到 Yul 优化器的默认清理序列中。
添加对导入 EVM 汇编 JSON 的支持(实验性)
此新版本添加了对导入 EVM 汇编的支持(实验性),为外部工具在发出字节码之前执行超级优化提供了可能性。此功能的主要目的是为低级 EVM 汇编定义一种序列化格式,使编译器生成的汇编能够导出、修改和重新导入,从而恢复正常的编译过程。
重要提示:这是一个实验性功能,目前不适合在生产环境中使用。我们在此版本中提供此功能是为了让您尝试并提供反馈。
允许在文件级别定义事件
Solidity 0.8.22 允许您在文件级别定义事件。事件定义现在可以放置在合约范围之外。这为代码组织添加了另一种选择,无需在库中人工包装事件。
此外,此版本还修复了一个错误,该错误以前会导致在为发出在外部合约或接口中定义的事件的代码生成 NatSpec 时出现错误。在之前的版本 (0.8.21) 中,Solidity 编译器添加了对限定访问在当前合约未继承的合约和接口中定义的事件的支持,但该错误阻止了该功能的完全使用。
通过此错误修复和允许文件级事件定义,最新版本的 Solidity 使用户能够在没有任何错误的情况下编译以下示例
interface I { event ForeignEvent(); } contract C { event ForeignEvent(); } event E(); contract D { function f() public { // Emitting a foreign event would trigger an internal error on 0.8.21 emit I.ForeignEvent(); emit C.ForeignEvent(); // Emitting a file-level event. New feature. emit E(); } }
完整变更日志
语言特性
- 允许在文件级别定义事件。
编译器特性
- 代码生成器:当计数器变量不会溢出时,删除某些 for 循环的多余溢出检查。
- 命令行界面:添加 --no-import-callback 选项,该选项阻止编译器加载未在 CLI 或标准 JSON 输入中显式提供的源文件。
- 命令行界面:添加一个实验性的 --import-asm-json 选项,该选项可以导入 --asm-json 使用的格式的 EVM 汇编。
- 命令行界面:对于编译管道外部产生的错误消息,也使用正确的严重性和颜色。
- EVM:弃用对“homestead”、“tangerineWhistle”、“spuriousDragon”和“byzantium” EVM 版本的支持。
- 解析器:删除实验性错误恢复模式(--error-recovery / settings.parserErrorRecovery)。
- SMTChecker:支持用户定义的运算符。
- Yul 优化器:如果支持 PUSH0,则优先使用零字面量而不是将零值存储在变量中。
- Yul 优化器:在默认清理序列的末尾运行 重新物化器 和 未使用修剪器 步骤。
错误修复
- 代码生成器:修复通过 IR 代码生成器生成的输出依赖于导入回调发现的文件。在某些情况下,不同的 AST ID 分配会更改内部调度中函数的顺序,从而导致表面上不同但语义上等效的字节码。
- NatSpec:修复在请求发出在外部合约或接口中定义的事件的合约的用户文档或开发者文档时发生的内部错误。
- SMTChecker:修复导致循环在完成之后展开的编码错误。
- SMTChecker:修复在 while 或 for 循环在条件检查之前展开时,常量条件检查的不一致性。
- Yul 优化器:修复 CSE 期间替换决策受编译器生成的 Yul 变量名称的影响,从而在某些情况下导致不同的(但等效的)字节码。
AST 更改
- AST:修复 AST 中 Yul 节点的错误初始 ID。
如何安装/升级?
要升级到最新版本的 Solidity 编译器,请按照我们文档中提供的安装说明进行操作。
您可以在此处下载 Solidity 的新版本:v0.8.22。如果您想从源代码构建,请不要使用 GitHub 自动生成的源代码存档。而是使用 solidity_0.8.22.tar.gz 并查看我们关于如何从源代码构建的文档。
我们建议所有 Solidity 开发人员始终升级到最新版本的 Solidity,以利用改进、优化,最重要的是错误修复。