Solidity 0.8.5 发布

Solidity 0.8.5 新变化

> * 原文:[Solidity 0.8.5 发布公告](https://blog.soliditylang.org/2021/06/10/solidity-0.8.5-release-announcement/) > * 译文出自:[登链翻译计划](https://github.com/lbc-team/Pioneer) > * 译者:[翻译小组](https://learnblockchain.cn/people/412) > * 校对:[Tiny 熊](https://learnblockchain.cn/people/15) > * 本文永久链接:[learnblockchain.cn/article…](https://learnblockchain.cn/article/2624) Solidity团队于2021年6月10日发布0.8.5版本。 [Solidity v0.8.5](https://github.com/ethereum/solidity/releases/tag/v0.8.5)允许从`bytes`转换为`bytesNN`值,增加了`verbatim`内置函数以在Yul中注入任意字节码,并修复了几个较小的错误。 ## 值得注意的新功能 ### 字节转换 *完整的功能文档[可以在这里](https://docs.soliditylang.org/en/v0.8.5/types.html#explicit-conversions)找到。* 这个版本引入了将`bytes`和`bytes`切片转换为固定字节长度类型`bytes1`/.../`bytes32`的能力。虽然固定长度的字节类型之间的转换一直是可能的,而现在也可以将动态大小的字节类型转换成固定长度的字节类型。 如果一个字节数组长于目标固定字节类型,将进行末端截断: ```javascript function f(bytes calldata c) public view returns (bytes8) { // If c is longer than 8 bytes, truncation happens return bytes8(c); } ``` 调用`f("12345678")`将返回`12345678`,如同调用`f("1234567890")`。如果数组比目标固定类型短,它将在末尾填充零,所以调用`f("1234")`将返回``1234``。 使用 `bytes `转换功能的一个好例子是在代理中使用: ```javascript // 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."); } } ``` 在 0.8.5以前,不可能做到`bytes4 sig = bytes4(_payload[:4]);`,相反,你必须使用以下方法进行转换: ``` bytes4 sig = _payload[0] | (bytes4(_payload[1]) >> 8) | (bytes4(_payload[2]) >> 16) | (bytes4(_payload[3]) >> 24); ``` ### Yul 中的 `Verbatim ` *完整的功能文档可以在[这里](https://docs.soliditylang.org/en/v0.8.5/yul.html#verbatim)找到。* 这个版本为Yul引入了一组`verbatim`内置函数,允许你在二进制中注入任意字节码。目前只能通过纯Yul来实现,也就是说,不能通过内联汇编来实现。 主要有两个用途(下文将详细介绍): 1. 使用Yul不知道的操作码(因为它们只是被提议的,或者因为你的目标是一个与EVM不兼容的链)。 2. 产生未被优化器修改的特定字节码序列。 这些函数是`verbatim<n>i_<m>o("<data>", ...)`,其中: - `n`是一个介于0和99之间的小数,用于指定输入栈槽/变量的数量。 - `m`是一个介于0和99之间的十进制数,指定输出栈槽/变量的数量。 - `data`是一个字符串常量,包含字节的序列。 注意,在使用`verbatim`时有一些注意事项,关于它的细节可以在[文档](https://docs.soliditylang.org/en/v0.8.5/yul.html#verbatim)中找到。 #### 用于新的操作码 作为一个实际的例子,我们可以用它来方便地将一个新提出的EVM操作码注入二进制。以提议的`BASEFEE`(在`0x48`)操作码为例(见[EIP-3198](https://eips.ethereum.org/EIPS/eip-3198)和[EIP-1559](https://eips.ethereum.org/EIPS/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`分配一个堆栈槽,并将结果存储在那里。 #### 用于 Optimism 使用场景 第二个使用场景对于像Optimism这样的第2层解决方案来说是很有用的,以及其他类似的情况,比如字节码分析或调试。Optimism目前使用一个自定义的Solidity编译器,因为他们模拟了智能合约的执行,其中对状态的改变(存储、外部调用等)都不会直接执行,而是由对管理人合约的调用来代替,该合约存储了这些改变以备验证。这方面的问题是检查合约是否符合这些限制(即为每一个变化正确调用管理人合约),特别是由于这必须由链上欺诈检测机制来完成。他们所做的是,检查合约是否使用了任何一个改变状态的操作码,除了调用管理人合约的 `call `操作码之外。为了正确检测这个异常,导致这个`call`操作码的操作序列必须有一个特定的形式,通常,Solidity优化器会进行一些重新排列,并破坏这个形式。幸运的是,`verbatim`可以解决这个问题,这样Optimism就不需要再依赖自定义的Solidity编译器,可以使用所有后来的Solidity编译器版本而不需要修改。 Optimism编译器可以采用由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异常代码插入辅助函数,而不是无条件地内联。 - EVM:将默认的EVM版本设置为 `Berlin`。 - SMTChecker:函数定义可以用自定义的Natspec标签`custom:smtchecker abstract-function-nondet`来注解,以便在调用时用非确定性的值抽象化。 - 标准JSON/组合JSON:新的工件 `functionDebugData`,包含函数入口点的字节码偏移,未来可能会有更多信息。 - Yul优化器:评估`keccak256(a, c)`,当内存位置`a`的值在编译时是已知的,`c`是常数`<=32`。 ### AST的变化 - 增加成员`hexValue`,用于Yul字符串和十六进制字符 还修复一些 bug ,衷心感谢所有帮助实现该版本的贡献者。 可以在[这里](https://github.com/ethereum/solidity/releases/tag/v0.8.5)下载新版本的 Solidity 。 --- 本翻译由 [Cell Network](https://www.cellnetwork.io/?utm_souce=learnblockchain) 赞助支持。

  • 原文:Solidity 0.8.5 发布公告
  • 译文出自:登链翻译计划
  • 译者:翻译小组
  • 校对:Tiny 熊
  • 本文永久链接:learnblockchain.cn/article…

Solidity团队于2021年6月10日发布0.8.5版本。

Solidity v0.8.5允许从bytes转换为bytesNN值,增加了verbatim内置函数以在Yul中注入任意字节码,并修复了几个较小的错误。

值得注意的新功能

字节转换

完整的功能文档可以在这里找到。

这个版本引入了将bytesbytes切片转换为固定字节长度类型bytes1/.../bytes32的能力。虽然固定长度的字节类型之间的转换一直是可能的,而现在也可以将动态大小的字节类型转换成固定长度的字节类型。

如果一个字节数组长于目标固定字节类型,将进行末端截断:

function f(bytes calldata c) public view returns (bytes8) {
    // If c is longer than 8 bytes, truncation happens
    return bytes8(c); 
}

调用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.");
    }
}

在 0.8.5以前,不可能做到bytes4 sig = bytes4(_payload[:4]);,相反,你必须使用以下方法进行转换:

bytes4 sig =
    _payload[0] |
    (bytes4(_payload[1]) >> 8) |
    (bytes4(_payload[2]) >> 16) |
    (bytes4(_payload[3]) >> 24);

Yul 中的 Verbatim

完整的功能文档可以在这里找到。

这个版本为Yul引入了一组verbatim内置函数,允许你在二进制中注入任意字节码。目前只能通过纯Yul来实现,也就是说,不能通过内联汇编来实现。

主要有两个用途(下文将详细介绍):

  1. 使用Yul不知道的操作码(因为它们只是被提议的,或者因为你的目标是一个与EVM不兼容的链)。
  2. 产生未被优化器修改的特定字节码序列。

这些函数是verbatim&lt;n>i_&lt;m>o("&lt;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分配一个堆栈槽,并将结果存储在那里。

用于 Optimism 使用场景

第二个使用场景对于像Optimism这样的第2层解决方案来说是很有用的,以及其他类似的情况,比如字节码分析或调试。Optimism目前使用一个自定义的Solidity编译器,因为他们模拟了智能合约的执行,其中对状态的改变(存储、外部调用等)都不会直接执行,而是由对管理人合约的调用来代替,该合约存储了这些改变以备验证。这方面的问题是检查合约是否符合这些限制(即为每一个变化正确调用管理人合约),特别是由于这必须由链上欺诈检测机制来完成。他们所做的是,检查合约是否使用了任何一个改变状态的操作码,除了调用管理人合约的 call操作码之外。为了正确检测这个异常,导致这个call操作码的操作序列必须有一个特定的形式,通常,Solidity优化器会进行一些重新排列,并破坏这个形式。幸运的是,verbatim可以解决这个问题,这样Optimism就不需要再依赖自定义的Solidity编译器,可以使用所有后来的Solidity编译器版本而不需要修改。

Optimism编译器可以采用由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)
}

完整的更新日志

语言特性方面

  • 允许从 bytesbytes片转换到 bytes1/.../bytes32
  • Yul: 增加verbatim内置函数,以注入任意字节码。

编译器功能方面

  • 代码生成器:为panic异常代码插入辅助函数,而不是无条件地内联。
  • EVM:将默认的EVM版本设置为 Berlin
  • SMTChecker:函数定义可以用自定义的Natspec标签custom:smtchecker abstract-function-nondet来注解,以便在调用时用非确定性的值抽象化。
  • 标准JSON/组合JSON:新的工件 functionDebugData,包含函数入口点的字节码偏移,未来可能会有更多信息。
  • Yul优化器:评估keccak256(a, c),当内存位置a的值在编译时是已知的,c是常数&lt;=32

AST的变化

  • 增加成员hexValue,用于Yul字符串和十六进制字符

还修复一些 bug ,衷心感谢所有帮助实现该版本的贡献者。

可以在这里下载新版本的 Solidity 。

本翻译由 Cell Network 赞助支持。

区块链技术网。

  • 发表于 2021-06-17 11:55
  • 阅读 ( 776 )
  • 学分 ( 77 )
  • 分类:Solidity

评论