我们很高兴地宣布 Solidity 编译器的发布v0.8.26。这个最新版本的编译器带来了对require 中自定义错误的支持,改进了默认的 Yul 优化器序列,通过 IR 加速编译,还包含一些错误修复等等!
重要功能
require 中支持自定义错误
Solidity 中的自定义错误为用户提供了一种便捷且节省 gas 的方式来解释操作失败的原因。 Solidity 0.8.26 引入了一项备受期待的功能,该功能允许在 require 函数中使用错误。
0.8.26 版本之前的 require 函数提供了两个重载
- require(bool) 会在没有数据的情况下回滚(甚至没有错误选择器)。
- require(bool, string) 会用 Error(string) 回滚。
在这个版本中,我们引入了新的重载来支持自定义错误
- require(bool, error) 会使用作为第二个参数提供的自定义用户提供的错误回滚。
让我们看一个例子来理解 require 函数与自定义错误一起使用的用法
// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.26; /// Insufficient balance for transfer. Needed `required` but only /// `available` available. /// @param available balance available. /// @param required requested amount to transfer. error InsufficientBalance(uint256 available, uint256 required); // This will only compile via IR contract TestToken { mapping(address => uint) balance; function transferWithRequireError(address to, uint256 amount) public { require( balance[msg.sender] >= amount, InsufficientBalance(balance[msg.sender], amount) ); balance[msg.sender] -= amount; balance[to] += amount; } // ... }
请注意,就像以前可用的 require 的重载一样,参数会无条件地进行评估,因此请特别注意确保它们不是具有意外副作用的表达式。例如,在 require(condition, CustomError(f())) 和 require(condition, f()) 中,对函数 f() 的调用将始终执行,无论提供的条件是 true 还是 false。
请注意,目前,将自定义错误与 require 一起使用仅受 IR 管道支持,即通过 Yul 进行编译。对于传统管道,请使用 if (!condition) revert CustomError(); 模式代替。
使用具有较小静态编码大小的错误进行回滚的优化
在使用具有较小静态编码大小的自定义错误的情况下,例如,没有参数的错误,或者参数足够小以至于可以放入 scratch 空间,开发人员通常选择使用内联汇编来执行此类回滚,以节省部署 gas 成本。
从这个版本开始,将在代码生成阶段进行检查,如果可能,将应用上述优化,这意味着以下情况现在与内联汇编变体一样优化
// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.26; error ForceFailure(); contract FailureForcer { function fail() external pure { revert ForceFailure(); } }
新的、更快的 Yul 优化器序列
这个版本的亮点之一是改进了 Yul 优化器使用的默认序列Yul 优化器.
序列告诉优化器模块要运行哪些 步骤 以及运行的顺序。它可以由用户提供,但推荐的默认值是在编译器中硬编码的,因为创建好的序列并非易事。序列的选择主要影响 IR 管道生成的代码,但也对传统管道有轻微的影响,因为编译器生成的内联汇编和实用程序代码都以这种方式进行优化。
作为改进新管道性能的持续努力的一部分,我们分析了当前的默认序列,以确定哪些部分对最终结果的贡献最大。
旧序列的一个主要功能是它的主循环——可以重复的长中间段,它给了优化器一个机会来改进结果,如果上一次传递创建了新的优化机会。然而,我们的分析表明,第一次传递的结果几乎总是非常接近最终结果,并且随后重复主循环只贡献了一点点。虽然简单地删除循环会得到的结果仍然明显比旧序列差,但经过一些实验,我们设法创建了一个 新的序列,它可以在单次传递中提供相当的优化质量。
例如,这是我们分析的一些示例合约中每个优化步骤后的字节码大小
新序列更早停止:
类似地,对于运行时 gas。当前序列:
新序列:
下表显示了新序列对我们用于基准测试的几个实际项目中的编译时间和字节码大小的影响
项目 | 编译时间1 | 字节码大小 | 运行时 gas |
---|---|---|---|
pool-together | -63% | -1.29% | |
uniswap | -53% | +1.67% | |
zeppelin | -47% | -0.48% | -0.01% |
elementfi | -42% | -1.87% | |
euler | -34% | +1.00% | |
yield_liquidator | -27% | +0.84% | +0.14% |
ens | -22% | -1.20% | -0.01% |
brink | -20% | +0.61% | |
perpetual-pools | -16% | -0.23% | +0.02% |
gp2 | -12% | +0.50% |
虽然我们没有上面列出的所有项目的运行时 gas 结果,因为执行它们的测试套件存在问题,但在我们拥有的项目中,差异非常小。
根据我们的基准测试,我们预计大多数项目的 IR 编译时间最多会减少 65%。虽然对字节码大小的影响并不总是积极的,但差异通常足够小,值得改进编译时间。我们预计优化器即将进行的改进将带来比这更大的影响。
如果您在项目中观察到优化质量明显下降,我们建议您暂时 切换回旧序列 并 打开一个问题,以便我们进行调查。 Solidity v0.8.25 中的默认序列包含以下步骤
dhfoDgvulfnTUtnIf [ xa[r]EscLM cCTUtTOntnfDIul Lcul Vcul [j] Tpeul xa[rul] xa[r]cL gvif CTUca[r]LSsTFOtfDnca[r]Iulc ] jmul[jul] VcTOcul jmul : fDnTOcmu
警告:我们尽一切努力确保编译器无论使用哪个序列都能正常工作,并采用模糊测试来查找任何异常,但就其本质而言,默认序列获得了更多覆盖范围,自定义序列的问题更容易遗漏。因此,虽然新序列也可以与旧的编译器一起使用,但我们建议在这样做时要格外小心。特别是,新序列容易受到 FullInliner 非表达式拆分参数评估顺序错误 的影响,这对于最近的版本来说不是问题,但会对 v0.8.21 之前的版本造成问题。
替换内部 JSON 库
在这个版本中,我们还用 nlohmann::json 替换了我们内部的 JSON 库 jsoncpp。
因此,JSON 输出的格式略有变化,它对 UTF-8 编码也更加严格。旧的 jsoncpp 允许一些无效的 UTF-8 序列,但也没有正确处理它们。
但是,我们预计这在实际操作中不会造成问题,因为绝大多数实现都假设使用 UTF-8。
完整变更日志
语言功能
- 引入新的重载 require(bool, Error),允许 require 函数使用自定义错误。此功能仅在 via-ir 管道中可用。
编译器功能
- SMTChecker:为 CHC 引擎创建余额检查验证目标。
- Yul IR 代码生成:使用具有较小静态编码大小的错误进行回滚时,成本更低的代码。
- Yul 优化器:新的、更快的默认优化器步骤序列。
错误修复
- 命令行界面:修复优化器被禁用且 --yul-optimizations 序列使用空字符串或空白字符串时出现的 ICE 错误。
- 优化器:修复优化器执行步骤序列的每个重复部分至少两次,即使代码大小在第一次迭代后已经稳定。
- SMTChecker:修复比较相同数组或字符串字面量的哈希值时出现的误报。
- SMTChecker:修复由于对索引和映射域的排序兼容性要求过高而导致的映射访问内部错误。
- SMTChecker:修复在条件运算符中使用空元组时的内部错误。
- SMTChecker:修复在使用数组元素作为参数时使用按位运算符时的内部错误。
- 标准 JSON 接口:修复优化器被禁用且 optimizerSteps 序列使用空字符串或空白字符串时出现的 ICE 错误。
- 静态分析器:仅当它在字面量之间时,才会针对零除和模除零引发编译时错误。
- Yul 优化器:修复 SSATransform 生成的赋值顺序依赖于 AST ID,有时会导致在将不相关文件添加到编译管道时产生不同的(但等效的)字节码。
构建系统
- 用 nlohmann::json 替换内部 JSON 库 jsoncpp。
如何安装/升级?
要升级到最新版本的 Solidity 编译器,请按照我们文档中提供的 安装说明 操作。您可以在此处下载 Solidity 的新版本:v0.8.26。如果您想从源代码构建,请不要使用 GitHub 自动生成的源代码归档文件。
脚注
-
请注意,表中的数字是指总编译时间,其中包括分析、代码生成、优化,尤其是 Yul->EVM 转换,而前面显示的图表只包括执行序列所花费的时间。 ↩