我们很高兴地宣布 Solidity 编译器的最新版本,Solidity v0.8.21.
主要新功能
通过 IR 始终启用栈到内存移动器
该版本解决了基于 IR 的代码生成管道产生的未优化代码过于容易出现“栈过深”错误的问题。这旨在帮助调试器等工具,因为它们在处理优化代码时会失去很大一部分效力。
传统管道通常可以避免在未优化模式下耗尽可访问的栈槽,但这是以更高的复杂性为代价的,因为许多优化被硬编码为代码生成的一部分。新的管道从一开始就旨在将这两个问题分开。它产生直接但非常低效的输出,这些输出旨在通过 Yul 优化器以离散步骤进行改进。每一步执行的转换可以在孤立状态下进行验证和推理。这种方法的缺点是,未优化代码通常有很多未使用的局部变量,并且会遇到栈问题。
我们的解决方案是将未优化代码通过一些最小转换,这些转换有助于解决栈问题,但不会像完全优化那样显著改变它。具体来说,编译器现在将运行UnusedPruner 优化器步骤来删除未使用的变量。此外,栈到内存移动器机制现在始终启用,解决了剩余的栈问题,尽管这会带来额外的内存访问。
这两件事都可以通过现有的优化器设置实现,但不是默认行为的一部分。尽管在技术上涉及优化器模块,但我们认为它们是新管道的重要组成部分,也是未优化代码含义的新基线。
重要错误修复
- 代码生成器:始终为 <expression>.selector 中的表达式生成代码,在传统代码生成管道中。您可以在我们的博客文章中阅读更多相关内容 这里。
- Yul 优化器:修复 FullInliner 步骤 (i) 未在未处于表达式拆分形式的代码中保留传递给内联函数的参数的评估顺序(即,当使用自定义优化器序列时,该步骤之前没有 ExpressionSplitter (x))。您可以在我们的博客文章中阅读更多相关内容 这里。
语言特性
- 允许从其他合约进行限定访问事件。
- 放宽对不可变变量初始化的限制。读写现在可以在构造期间的任何时间点发生,函数和修饰符除外。不再强制进行显式初始化。
对外部事件进行限定访问
Solidity 0.8.21 允许访问在当前合约未继承的合约和接口中定义的事件。因此,以下示例现在将编译而不会出现错误
library L { event EL(); } interface I { event EI(); } contract C { event EC(); } contract D { event ED(); } contract E is D { event EE(); function foo() public { emit L.EL(); emit I.EI(); // Not allowed on 0.8.20 emit C.EC(); // Not allowed on 0.8.20 emit ED(); emit EE(); } }
这曾经是不允许的,因为 NatSpec JSON 格式的限制,它是合约元数据的组成部分。该格式通过其签名识别事件,签名仅包含非限定名称,并且只允许指定一块 NatSpec。由于在不同合约中定义了相同签名的事件定义在 ABI 中表示同一个事件,因此正确的解决方案是包含与所有可用定义相关的 NatSpec,但这种改变不是向后兼容的,必须等到重大版本发布后才能进行。
与此同时,这种限制对语言用户来说是一个很大的可用性问题,我们决定通过任意地忽略其中一个事件的 NatSpec 来规避它。在发生冲突的情况下,当前合约或其继承层次结构中的另一个合约中存在的定义的 NatSpec 优先。这将在引入 NatSpec v2 时得到纠正。
放宽对不可变变量初始化的限制
鉴于在过去几个月发现的几个错误,我们对不可变初始化的检查并不完善,因此允许在某些情况下,这些不可变变量仍然无法初始化(例如,try/catch 块的分支、for 循环等),我们决定大幅放宽对不可变初始化的限制。
虽然不可变变量仍然只能在构造期间进行显式初始化,但这种初始化现在是可选的,因为所有不可变变量都将被默认(零)初始化,无论如何。我们还决定取消 一次赋值 限制,这意味着您现在可以对不可变变量进行多次赋值,最后一次赋值将被保留。
负责执行这些限制的机制的一个重大缺点是,出于必要性,它阻止了一些初始化模式,这些模式是完全安全的,但难以验证。在一般情况下,在没有误报的情况下,精确确定变量是否在所有可能的构造代码路径上都已初始化是不可能的。为了解决这个问题,使用的机制只是不允许在函数、修饰符、条件语句、循环和 try/catch 块内部进行初始化。现在对后三种情况的限制已经取消,我们希望最终也取消在函数和修饰符内部进行初始化的限制。
以下示例展示了现在可以初始化不可变变量的一些方法。
contract C { uint immutable x; uint immutable y; uint immutable z; // Not initialized. Will be 0 constructor(bool condition, uint value) { x = value; if (condition) { x = 42; // Overwriting already assigned value z = 42; } else z = 24; // On 0.8.20 initialization inside an if is not allowed // even if there's no way for z to remain uninitialized } }
重要的是要注意,我们放宽这些限制是因为这些检查最终人为地将 部署 合约不可变性限制强加于合约创建,以实现一致性,而不是出于安全原因。并非所有由此实现的模式都一定推荐,但现在由用户决定是否使用它们。
影响 IR 管道产生的未优化字节码可重复性的错误
该版本修复了基于 IR 的代码生成器中的一个次要错误,该错误导致产生的字节码不同(但功能等效),具体取决于编译器二进制文件的构建方式。特别是,使用 Clang C++ 编译器构建的二进制文件会在某些情况下以与使用 GCC 构建的二进制文件不同的顺序排列 Yul 函数。触发该错误的案例是内存数组的索引。这会导致生成多个 Yul 辅助函数,它们的顺序取决于使用的 C++ 编译器。
该错误只影响 IR 管道生成的未优化代码,即需要在标准 JSON 中使用 settings.viaIR 或在 CLI 上使用 --via-ir 标志。使用 Yul 优化器会消除其影响,因为它会重新排列函数。
该错误会影响 solc-bin 中提供的官方二进制文件。Emscripten 和 macOS 二进制文件使用 Clang 构建,Linux 二进制文件使用 GCC 构建,Windows 二进制文件使用 MSVC 构建。建议对合约进行源代码验证的工具考虑部署的合约可能由这两个二进制文件中的任何一个产生。
完整变更日志
编译器功能
- 命令行界面:在汇编器模式下添加 --ast-compact-json 输出。
- 命令行界面:为 Solidity 输入添加 --ir-ast-json 和 --ir-optimized-ast-json 输出,提供 IR 和优化 IR 的紧凑 JSON 格式的 AST。
- 命令行界面:在编译器模式下尊重 --optimize-yul 和 --no-optimize-yul,并在汇编器模式下也接受它们。 --optimize --no-optimize-yul 组合现在允许启用 EVM 汇编优化器而无需启用 Yul 优化器。
- EWasm:删除 EWasm 后端。
- 解析器:引入 pragma experimental solidity,它将启用一种实验性语言模式,这种模式特别是在非重大版本之间没有稳定性保证,不适合生产使用。
- SMTChecker:添加 --model-checker-print-query CLI 选项和 settings.modelChecker.printQuery JSON 选项,以便以 SMTLIB2 格式输出 SMTChecker 查询。这需要仅使用 smtlib2 求解器。
- 标准 JSON 接口:为 Yul 输入添加 ast 文件级输出。
- 标准 JSON 接口:为 Solidity 输入添加 irAst 和 irOptimizedAst 合约级输出,提供 IR 和优化 IR 的紧凑 JSON 格式的 AST。
- Yul 优化器:删除实验性 ReasoningBasedSimplifier 优化步骤。
- Yul 优化器:栈到内存移动器现在默认情况下在通过 IR 代码生成和纯 Yul 编译时始终启用。
错误修复
- 代码生成器:禁止结果为类型、内置函数、模块或某些不可分配函数的复杂表达式。传统的代码生成管道实际上不会评估它们,而会丢弃它们可能具有的任何副作用。
- 代码生成器:修复未优化 Yul 输出中函数顺序不完全确定。在某些情况下,C++ 编译器的选择会导致不同的(但等效的)字节码(特别是来自原生二进制文件与 emscripten 二进制文件)。
- 命令行界面:修复使用 --stop-after parsing 并请求需要完整分析或编译的某些输出时出现的内部错误。
- 命令行界面:不再可能同时指定 --optimize-yul 和 --no-optimize-yul。
- SMTChecker:修复 BMC 引擎中 if 和 三元条件语句内部副作用的编码。
- SMTChecker:修复当验证目标只能通过来自另一个公共函数的受信任外部调用来违反时出现的假阴性。
- SMTChecker:修复当 CHC 引擎超时时,在 BMC 引擎中使用外部调用受信任模式生成无效的 SMT-LIB2 脚本。
- SMTChecker:修复由于将使用函数指针的外部函数调用错误地分类为公共 getter 而导致的内部错误。
- SMTChecker:修复由于使用外部标识符来编码对以内部函数作为参数的函数的成员访问而导致的内部错误。
- 标准 JSON 接口:修复当分析因某些类型的致命错误中断时返回的 AST 不完整。
- 类型检查器:禁止在复杂表达式中使用某些不可分配的函数类型。
- 类型检查器:引用不同声明的函数声明类型不再可以相互转换。
- Yul 优化器:确保为移至内存的变量分配内存槽不依赖于可能取决于编译期间是否包含其他文件的 AST ID。
- Yul 优化器:修复 FullInliner 步骤未忽略未处于表达式拆分形式的代码。
- Yul 优化器:修复优化后的 IR 在字节码生成之前再次不必要地通过 Yul 优化器。
AST 更改
- AST:在 SourceUnit 节点中添加了 experimentalSolidity 字段,该字段指示是否已通过 pragma experimental solidity 启用了实验性解析模式。
如何安装/升级
要升级到最新版本的 Solidity 编译器,只需按照我们文档中提供的 安装说明 即可。我们的团队已确保提供详细且直接的步骤,使升级过程尽可能无缝。如果您在升级过程中有任何疑问或遇到任何问题,请随时联系我们的 社区矩阵频道。
您可以在此处下载 Solidity 的新版本:v0.8.21。如果您想从源代码构建,请不要使用 GitHub 自动生成的源代码存档,而是请使用 solidity_0.8.21.tar.gz,如果您需要指导,请查看 我们关于如何从源代码构建的文档。
请注意,对于使用低于 0.8.0 版本的 Solidity 的用户,存在重大更改。我们强烈建议您查看我们文档中有关重大更改的详细列表,以确保升级过程顺利进行。此外,我们鼓励所有 Solidity 用户定期检查更新,以了解最新的改进和优化。我们建议所有 Solidity 开发人员升级到 0.8.21 版本,以利用这些改进和优化。