Solidity 0.8.26 发布
我们(Solidity Team)很高兴地宣布发布 Solidity 编译器 v0.8.26。这个编译器的最新版本为 require 中的自定义错误提供支持,改进了默认的 Yul 优化器序列,可以通过 IR 加快编译速度,修复了一些 bug 等等!
Solidity 中的自定义错误为用户提供了一种方便且高效的方式来解释操作失败的原因。Solidity 0.8.26 引入了一个备受期待的功能,使得可以在 require 函数中使用错误。
在 0.8.26 版本之前的 require 函数提供了两种重载:
在这个版本中,我们引入了一个新的重载以支持自定义错误:
让我们通过一个例子来理解带有自定义错误的 require 函数的用法:
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.26;
/// 转账的余额不足。需要 `required` 但只有 `available` 可用。
/// @param available 可用余额。
/// @param required 请求转账的金额。
error InsufficientBalance(uint256 available, uint256 required);
// 这将只能通过 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。
请注意,目前仅支持通过 IR 管道使用 require 的自定义错误,即通过 Yul 进行编译。对于旧管道,请使用 if (!condition) revert CustomError(); 模式。
在具有静态编码大小较小的自定义错误的情况下,例如,没有参数的错误,或者参数足够小以适应临时空间的开发人员通常会借助内联汇编来执行此类回滚,以节省部署 gas 成本。
从这个版本开始,在代码生成阶段执行检查,并应用了该优化,这意味着以下情况现在与内联汇编变体一样优化:
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.26;
error ForceFailure();
contract FailureForcer {
function fail() external pure {
revert ForceFailure();
}
}
这个版本的亮点之一是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 Non-Expression-Split Argument Evaluation Order Bug的影响,这对于最新版本不是问题,但会在早于v0.8.21的版本上引起问题。
在此版本中,我们还将我们的内部 JSON 库jsoncpp替换为nlohmann::json。
因此,JSON 输出的格式略有变化,它也变得更加严格,对 UTF-8 编码更加敏感。旧的jsoncpp允许一些无效的 UTF-8 序列,但也没有正确处理它们。
然而,我们不认为这会在实践中造成问题,因为绝大多数实现都假定使用 UTF-8。
要升级到最新版本的 Solidity 编译器,请按照我们文档中提供的安装说明。你可以在这里下载新版本的 Solidity:v0.8.26。如果你想从源代码构建,请不要使用 GitHub 自动生成的源代码存档。
本文由 AI 翻译,欢迎小伙伴们来校对。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!