Solidity v0.8.5 允许从bytes 到 bytesNN 值的转换,添加了 verbatim 内置函数,用于在 Yul 中注入任意字节码,并修复了几个较小的错误。
值得注意的新功能
字节转换
查找完整的特性文档这里.
此版本引入了将 bytes 和 bytes 切片转换为固定字节类型 bytes1 / ... / bytes32 的功能。虽然固定长度字节类型之间的转换一直都是可能的,但现在也可以将动态大小的字节类型转换为固定长度的字节类型。
如果字节数组比目标固定字节类型更长,它将在末尾被截断。
function f(bytes memory c) public pure returns (bytes8) { // If c is longer than 8 bytes, truncation happens return bytes8(c); }
在 Solidity 代码中调用 f("12345678") 将返回 "12345678",与调用它作为 f("1234567890") 一样。如果数组比目标固定类型短,它将在末尾用零填充,因此调用 f("1234") 将返回 "1234"。
使用 bytes 转换功能的一个很好的例子是它在代理中的应用。
// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.5; contract Proxy { /// @dev Address of the client contract managed by this proxy address client; constructor(address _client) { client = _client; } /// Forwards all calls to the client but performs additional checks for calls to "setOwner(address)". function forward(bytes calldata _payload) external { require(_payload.length >= 4); bytes4 sig = bytes4(_payload[:4]); if (sig == bytes4(keccak256("setOwner(address)"))) { address owner = abi.decode(_payload[4:], (address)); require(owner != address(0), "Address of owner cannot be zero."); } (bool status,) = client.delegatecall(_payload); require(status, "Forwarded call failed."); } }
之前,无法执行 bytes4 sig = bytes4(_payload[:4]);,而是必须使用以下方法。
bytes4 sig = _payload[0] | (bytes4(_payload[1]) >> 8) | (bytes4(_payload[2]) >> 16) | (bytes4(_payload[3]) >> 24);
Verbatim 在 Yul 中
查找完整的特性文档 这里。
此版本引入了一组用于 Yul 的 verbatim 内置函数,允许您将任意字节码注入二进制文件中。这目前仅通过纯 Yul 可用,即它无法通过内联汇编访问。
这本质上有两个用例(下面将详细介绍这些用例)。
- 使用 Yul 不认识的操作码(因为它们只是被提议或因为您要针对与 EVM 不兼容的链)。
- 生成优化器不会修改的特定字节码序列。
这些函数是 verbatim<n>i_<m>o("<data>", ...),其中
- n 是 0 到 99 之间的十进制数,它指定输入堆栈槽位的数量 / 变量数量,
- m 是 0 到 99 之间的十进制数,它指定输出堆栈槽位的数量 / 变量数量,
- data 是一个字符串字面量,包含字节序列。
请注意,使用 verbatim 时有一些注意事项。有关详细信息,请参阅 文档。
使用新的操作码
作为一个实际的例子,可以方便地将新提出的 EVM 操作码注入二进制文件。以提议的 BASEFEE(位于 0x48)操作码为例(参见 EIP-3198 和 EIP-1559),因为 Solidity 编译器目前不支持此操作码,因此可以使用 verbatim 在 Yul 中实现它。
{ function basefee() -> out { out := verbatim_0i_1o(hex"48") } sstore(0, basefee()) }
以下是一个具有 verbatim 输入参数的示例。
let x := calldataload(0) // The hex"600202" corresponds to EVM instructions: // PUSH 02 MUL // That is, it multiplies x by 2. let double := verbatim_1i_1o(hex"600202", x)
上面的代码将生成一个 dup1 操作码,以检索 x(虽然优化器可能会直接使用 calldataload 操作码的结果),紧随其后的是 600202。假设代码会消耗 x 的(复制)值,并在堆栈顶端产生结果。然后编译器生成代码,为 double 分配堆栈槽位,并将结果存储在那里。
乐观主义的用例
第二个用例对于乐观主义等第二层解决方案很有用,但也想到了一些其他情况,比如字节码分析或调试。乐观主义目前使用定制的 Solidity 编译器,因为它们模拟智能合约的执行,其中对状态(存储、外部调用等)的每一次更改不会直接执行,而是被替换为对管理器合约的调用,该合约存储更改以进行验证。这样做的问题是检查合约是否符合这些限制(即对于每一次更改都适当地调用管理器合约),尤其是因为这必须由链上欺诈检测机制来完成。他们的做法是检查合约是否使用了任何状态更改操作码,除了调用管理器合约的 call 操作码之外。为了正确检测此例外,导致此 call 操作码的操作码序列必须具有特定形式,并且通常,Solidity 优化器会进行一些重新排列,破坏这种形式。幸运的是,verbatim 可以解决这个问题,这样乐观主义就不再需要依赖定制的 Solidity 编译器,并且可以在不进行修改的情况下使用所有后期的 Solidity 编译器版本。
乐观主义编译器可以获取 Solidity 编译器生成的 Yul 代码,附加以下 Yul 辅助函数,并将所有状态更改内置函数调用在语法上替换为其 ovm_ 对应项。例如,所有 sstore(x, y) 调用都替换为 ovm_sstore(x, y) 调用。完成此替换后,甚至可以再次运行 Yul 优化器。(此代码仅说明 sstore。)
/// Generic call to the manager contract. function ovm_callManager(arguments, arguments_size, output_area, output_area_size) { verbatim_4i_0o( hex"336000905af158600e01573d6000803e3d6000fd5b3d6001141558600a015760016000f35b", arguments, arguments_size, output_area, output_area_size ) } // Call a manager function with two arguments function ovm_kall_2i(signature, x, y) { // Store touched memory in locals and restore it at the end. let tmp_a := mload(0x00) let tmp_b := mload(0x20) let tmp_c := mload(0x40) mstore(0, signature) mstore(4, x) mstore(0x24, y) ovm_callManager(0, 0x44, 0, 0) mstore(0x00, tmp_a) mstore(0x20, tmp_b) mstore(0x40, tmp_c) } // Replace all calls to ``sstore(x, y)`` by ``ovm_sstore(x, y)`` function ovm_sstore(x, y) { // The hex code is the selector of // the sstore function on the manager contract. ovm_kall_2i(hex"22bd64c0", x, y) }
完整变更日志
语言功能
- 允许从 bytes 和 bytes 切片转换为 bytes1/.../bytes32。
- Yul:添加 verbatim 内置函数,用于注入任意字节码。
编译器功能
- 代码生成器:为 panic 代码插入辅助函数,而不是无条件内联。如果插入了大量 panic(检查),这可以降低成本,但如果使用了很少的 panic,则可能会增加成本。
- EVM:将默认 EVM 版本设置为“柏林”。
- SMTChecker:函数定义可以使用自定义 Natspec 标签 custom:smtchecker abstract-function-nondet 来抽象,以便在调用时被非确定性值抽象。
- 标准 JSON / 组合 JSON:新的工件“functionDebugData”,它包含函数入口点的字节码偏移量,以及将来可能包含更多信息。
- Yul 优化器:评估 keccak256(a, c),当内存位置 a 处的 value 在编译时已知且 c 是一个常量 <= 32 时。
错误修复
- AST:如果 Yul 字符串和十六进制字面量不是有效的 UTF-8 字符串,则不要输出其 value。
- 代码生成器:修复函数数组分配给存储变量时出现的内部错误,以及函数类型可以隐式转换但并不完全相同的情况。
- 代码生成器:修复在虚拟解析顺序中,super 必须跳过未实现的函数时出现的内部错误。
- 控制流图:假设未实现的修饰符使用占位符。
- 控制流图:考虑始终还原的函数的内部调用,以报告未使用的或未分配的变量。
- 函数调用图:修复与循环常量引用相关的内部错误。
- 名称解析器:如果阴影名称不可直接访问,则不要发出阴影警告。
- Natspec:允许公共状态变量文档中使用多个 @return 标签。
- SMTChecker:修复从 bytes 到 fixed bytes 的转换时出现的内部错误。
- SMTChecker:修复从构造函数进行外部调用时出现的内部错误。
- SMTChecker:修复使用字符串字面量初始化固定字节成员的结构构造函数时出现的内部错误。
- 源位置:正确设置作用域块的源位置。
- 标准 JSON:在 settings.optimizer.details 下正确允许 inliner 设置。
- 类型检查器:修复与抽象合约构造函数参数中存在映射类型相关的内部编译器错误。
- 类型检查器:修复在尝试对前拜占庭 EVM 使用无效的外部函数类型时出现的内部编译器错误。
- 类型检查器:修复在继承期间用具有不同参数的函数覆盖接收以太币函数时出现的内部编译器错误。
- 类型检查器:将事件或错误参数中的(嵌套)映射类型错误转换为致命类型错误。
AST 更改
- 为 Yul 字符串和十六进制字面量添加成员 hexValue。
衷心感谢所有为这个版本做出贡献的人!
从这里下载 Solidity 的新版本 这里。