{ 跳至内容 }

Solidity 0.8.13 版本发布公告

发布者:Solidity 团队,2022 年 3 月 16 日

版本发布

Solidity v0.8.13 修复了一个与 abi.encodeCall 相关的严重错误,扩展了 using for 指令,并为语言服务器实现了“转到定义”功能。此外,通过新的 Yul IR 管道进行编译现已视为生产就绪。

重要错误

当在 Solidity 0.8.11 中引入 abi.encodeCall 时,十六进制字面量(0x1234)和字符串字面量("abcd")处理不当。请在

安全警报中了解更多信息。.

值得注意的新功能

Yul IR 管道生产就绪

几年来,我们一直在将 Yul 作为 Solidity 的中间语言进行开发。Yul 本身已有一段时间被认为是生产就绪的——至少从 ABI 编码器 V2 被标记为“稳定”以来,因为它大量使用了 Yul。另一部分,即从 Solidity 到 Yul 的代码生成,现在也可以被认为是稳定且生产就绪的。

它现在已经具备了完整的功能几个月了。从一开始,我们就始终通过两个编译器管道运行所有测试。在过去几周中,我们在测试基础设施中添加了更多复杂的真实世界合约,并修复了组件中的一些小错误。新的代码生成器已经稳定运行了很长时间。之前没有将其标记为“非实验性”的主要原因是它在堆栈空间有限的情况下仍然存在问题,我们现在已经能够解决这些问题。

您可以使用 --via-ir 命令行开关或将 settings.viaIR 设置为 true 在标准 JSON 输入中启用新管道(即在您放置 optimizer 键的同一级别)。

不建议在没有优化器的情况下使用 Yul 编译器管道,因为未优化的输出主要是 Solidity 高级代码的直接转换,并且与旧的编译器管道相比,它有许多额外的检查。这使其更具模块化且更容易审核,而新的基于 Yul 的优化器功能强大,足以从优化版本中删除冗余。如果没有优化器,您也更有可能遇到“堆栈过深”问题,如下所述,优化器通常可以避免这些问题。

新管道的性能并不总是优于旧管道,但它可以在函数之间进行更高级别的优化,因此请尝试一下并给我们反馈!

最后,在某些极端情况下,通过新编译器管道编译的代码的行为与通过旧编译器管道编译的代码不同。最突出的是依赖于其他合约中构造函数结果的状态变量的初始化顺序。其他一些与修改器的奇怪用法、语句内部评估顺序的依赖性或脏数据有关。您可以在我们的 文档 中找到所有更改的列表。

内存安全汇编/堆栈过深

新编译器管道的一个重要功能是 Yul 优化器可以将堆栈变量移动到内存,从而避免在很多情况下出现“堆栈过深”问题。为了安全地做到这一点,编译器需要知道它打算使用的内存槽没有被内联汇编使用。

如果内联汇编仅使用先前由 Solidity 高级代码分配的内存或从 0x40 处的空闲内存指针读取内存,则它被认为是内存安全的。在代码的开头,编译器将空闲内存指针设置为移动到内存的堆栈变量保留区域之后的位置。

编译器仅将不包含任何访问内存的操作码且不访问与内存引用类型相关的 Solidity 变量的非常简单的块视为内存安全。对于所有其他块,以安全的方式自动确定这一点很困难甚至不可能,因此我们为开发人员添加了一种语法机制来断言它。

assembly ("memory-safe") { ... }

将不安全的块标记为内存安全可能导致由于堆栈变量和内存之间的冲突而导致的未定义行为。编译器不会检查标记的块是否实际上是内存安全的。有关内联汇编块何时为内存安全的更多信息,请参阅 文档

默认情况下,访问内存的内联汇编块不被视为内存安全,即使只有一个这样的块也会禁用整个合约的堆栈到内存机制。否则假设将很危险,因为存在覆盖移动到内存的堆栈变量的风险。

例如,如果您正在编写一个旨在跨编译器版本使用的代码库,您也可以使用具有相同效果的特殊注释语法。

/// @solidity memory-safe-assembly
assembly { ... }

不过,我们建议不要使用此语法。请始终使用最新的编译器版本。

using for 在文件级别使用全局绑定

此版本大大扩展了 using for 指令。它现在可以在文件级别使用,可以使用自由函数,并且可以选择使其效果全局化。我们相信,结合最近发布的用户定义值类型,这使得设计漂亮的自包含类型成为可能。

在未来的版本中,我们计划还为用户定义类型添加运算符,以便像固定点类型库这样的东西非常易于使用,并且感觉像编译器内置类型。

您现在可以使用类似 using {f, g, L.h} for Type; 的语句,其中 fg 是自由函数(在文件级别定义),而 L.h 是库函数。

您也可以在末尾添加 global 一词,以使其效果在任何地方都可用。由于您的想法是定义一个类型并实现该类型的函数,因此 global 只能用于用户定义类型,并且只能在定义该类型的同一文件中使用。我们建议将 using 指令始终放在类型定义之后(前向引用在 Solidity 中有效)。

例如,让我们考虑一个只能一次增加或减少一个的类型。由于此限制,我们可以安全地删除溢出检查,因为达到溢出所需的 gas 比宇宙中可用的 gas 要多。

文件 "restrictedNumber.sol"

type RestrictedNumber is int256;
using {plusOne, minusOne} for RestrictedNumber global;

function plusOne(RestrictedNumber x) pure returns (RestrictedNumber)
{
    unchecked {
        return RestrictedNumber.wrap(RestrictedNumber.unwrap(x) + 1);
    }
}

function minusOne(RestrictedNumber x) pure returns (RestrictedNumber)
{
    unchecked {
        return RestrictedNumber.wrap(RestrictedNumber.unwrap(x) - 1);
    }
}

/// This is a creation function that ensures that
/// values are small enough. The idea is that the function
/// RestrictedNumber.wrap should only be used in the file
/// that defines the type, so that we have control over
/// the invariants.
function createRestrictedNumber(int256 value) pure returns (RestrictedNumber)
{
    // Ensure that the number is "small".
    // Larger constants like 2**200 would also work.
    require(value <= 100 && -value <= 100);
    return RestrictedNumber.wrap(value);
}

文件 "owned.sol"

import {RestrictedNumber} from "./restrictedNumber.sol";

contract Owned {
    RestrictedNumber public ownerCount;
    mapping(address => bool) public isOwner;

    constructor() {
        _addOwner(msg.sender);
    }

    function addOwner(address owner) external {
        require(isOwner[msg.sender]);
        _addOwner(owner);
    }

    function removeOwner(address owner) external {
        require(isOwner[msg.sender]);
        require(isOwner[owner]);
        // Because of `global`, we do not have to add
        // `using for` in the contract to use the
        // ``minusOne`` function.
        ownerCount = ownerCount.minusOne();
        isOwner[owner] = false;
    }

    function _addOwner(address owner) internal {
        require(!isOwner[owner]);
        ownerCount = ownerCount.plusOne();
        isOwner[owner] = true;
    }
}

LSP 的“转到定义”

Solidity 语言服务器得到了进一步改进,并新增了一项功能:底层符号的定义查找。

这适用于每个符号,例如局部变量、返回值、类型名称,以及导入语句。

我们认为官方的 Solidity 语言服务器仍处于预览阶段,也就是说,虽然它已经可以运行,但未来还会有更多功能。

请试用它,并在我们的 GitHub 问题跟踪器中留下一些 反馈

完整变更日志

重要错误修复

  • 代码生成器:正确编码在 abi.encodeCall 中用于替换固定字节参数的字面量。

语言功能

  • 常规:允许将内联汇编注释为内存安全,以允许依赖于尊重 Solidity 内存模型的优化和堆栈限制规避。
  • 常规:using M for Type; 允许在文件级别使用,并且 M 现在也可以是大括号括起来的自由函数或库函数列表。
  • 常规:using ... for T global; 允许在文件级别使用,其中已定义用户定义类型 T,从而导致语句的效果在 T 可用的任何地方都可用。

编译器功能

  • 命令行界面:允许使用 --via-ir 代替 --experimental-via-ir
  • 通过 Yul IR 进行编译不再被标记为实验性。
  • JSON-AST:为错误和事件添加了选择器字段。
  • LSP:实现了转到定义。
  • 窥孔优化器:优化条件跳转前的比较和跨单个无条件跳转的条件跳转。
  • Yul EVM 代码转换:避免在终止控制流时不必要的 pop
  • Yul 优化器:删除永远不会从中读取的 sstoremstore 操作。

错误修复

  • 常规:修复了具有不寻常大写规则的语言环境的内部错误。现在完全忽略环境中设置的语言环境。
  • 类型检查器:修复了导入重载函数时类型检查器错误不正确的问题。
  • Yul IR 代码生成:使用正确的设置优化嵌入式创建代码。这修复了独立编译的合约的构造函数代码与 type(C).creationCode 中的字节码之间潜在的不匹配,以及用于 new C(...) 的字节码。

非常感谢所有帮助使此次发布成为可能的贡献者!

在此处下载 Solidity 的新版本 此处

上一篇

下一篇

参与进来

GitHub

推特

Mastodon

矩阵

了解更多

博客文档使用案例贡献关于论坛

2024Solidity 团队

安全策略

行为准则