Solidity v0.5.0 突破性变化

本节重点介绍Solidity版本0.5.0中引入的主要突破性更改,以及更改背后的原因以及如何更新受影响的代码。完整列表检查 the release changelog .

注解

使用solidity v0.5.0编译的契约仍然可以与契约、甚至使用旧版本编译的库交互,而无需重新编译或重新部署它们。将接口更改为包含数据位置、可见性和可变性说明符就足够了。查看 Interoperability With Older Contracts 下面部分。

仅语义更改

本节列出了仅在语义上进行的更改,从而潜在地隐藏现有代码中的新行为和不同行为。

  • 有符号右移现在使用适当的算术移位,即舍入到负无穷大,而不是舍入到零。有符号移位和无符号移位将在君士坦丁堡有专用的操作码,目前可以用solidity来模拟。

  • 这个 continue 陈述 do...while 循环现在跳转到条件,这是此类情况下的常见行为。它曾经跳到循环体上。因此,如果条件为假,则循环终止。

  • 功能 .call().delegatecall().staticcall() 当给一个单人间的时候不要再垫 bytes 参数。

  • 现在使用操作码调用pure和view函数 STATICCALL 而不是 CALL 如果EVM版本为拜占庭或更高版本。这不允许对EVM级别进行状态更改。

  • ABI编码器现在可以正确地从CallData填充字节数组和字符串。 (msg.data 和外部函数参数)用于外部函数调用和 abi.encode .对于未添加的编码,请使用 abi.encodePacked .

  • ABI解码器在函数的开头和 abi.decode() 如果传递的calldata太短或超出界限。请注意,脏的高阶位仍然被简单地忽略。

  • 转发所有可用的气体,外部函数调用从橘子哨声开始。

语义和句法变化

本节重点介绍影响语法和语义的更改。

  • 功能 .call().delegatecall()staticcall()keccak256()sha256()ripemd160() 现在只接受一个 bytes 参数。此外,这一论点没有得到补充。为了更明确和清楚地说明参数是如何连接的,这一点做了更改。更改间隔 .call() (和家人)给 .call("").call(signature, a, b, c) 使用 .call(abi.encodeWithSignature(signature, a, b, c)) (最后一个只适用于值类型)。更改间隔 keccak256(a, b, c)keccak256(abi.encodePacked(a, b, c)) .尽管这不是一个突破性的改变,但有人建议开发人员改变 x.call(bytes4(keccak256("f(uint256)")), a, b)x.call(abi.encodeWithSignature("f(uint256)", a, b)) .

  • 功能 .call().delegatecall().staticcall() 现在回来 (bool, bytes memory) 提供对返回数据的访问。更改 bool success = otherContract.call("f")(bool success, bytes memory data) = otherContract.call("f") .

  • Solidity现在为函数局部变量实现了C99样式的作用域规则,也就是说,变量只能在声明之后使用,并且只能在相同或嵌套的作用域中使用。在初始化块中声明的变量 for 循环在循环内的任何点都有效。

明确性要求

本节列出了代码现在需要更明确的地方的更改。对于大多数主题,编译器将提供建议。

  • 显式函数可见性现在是必需的。添加 public 每个函数和构造函数,以及 external 对于尚未指定其可见性的每个回退或接口函数。

  • 结构、数组或映射类型的所有变量的显式数据位置现在是必需的。这也适用于函数参数和返回变量。例如,更改 uint[] x = m_xuint[] storage x = m_xfunction f(uint[][] x)function f(uint[][] memory x) 在哪里? memory 是数据位置,可能被替换为 storagecalldata 因此。注意 external 函数需要数据位置为的参数 calldata .

  • 合同类型不包括 address 成员,以便分隔命名空间。因此,在使用 address 成员。示例:如果 c 是合同,变更 c.transfer(...)address(c).transfer(...)c.balanceaddress(c).balance .

  • 现在不允许在不相关的合同类型之间进行显式转换。只能将合同类型转换为其基类型或祖先类型之一。如果您确定某个协定与要转换为的协定类型兼容,尽管它不从中继承,但可以通过将转换为 address 第一。示例:如果 AB 是合同类型, B 不从继承 Ab 是一种类型的合同 B ,您仍然可以转换 b 到类型 A 使用 A(address(b)) .请注意,您仍然需要注意匹配应付回退函数,如下所述。

  • 这个 address 类型被拆分为 addressaddress payable ,只有在哪里 address payable 提供 transfer 功能。一个 address payable 可直接转换为 address 但另一种方式是不允许的。转换 addressaddress payable 可以通过转换 uint160 .如果 c 是一份合同, address(c) 结果在 address payable 只有 c 具有应付回退功能。如果你使用 withdraw pattern ,您很可能不需要更改代码,因为 transfer 仅用于 msg.sender 而不是存储的地址和 msg.sender 是一个 address payable .

  • 之间的转换 bytesXuintY 由于以下原因,现在不允许使用不同大小的 bytesX 右侧的填充和 uintY 可能导致意外转换结果的左侧填充。现在,在转换之前,必须在文字内调整大小。例如,您可以将 bytes4 (4个字节)设置为 uint64 (8字节)首先将 bytes4 变量设置为 bytes8 然后再到 uint64 。进行转换时,您会得到相反的填充 uint32 。在v0.5.0之前的版本中, bytesXuintY 会经历 uint8X 。例如 uint8(bytes3(0x291807)) 将转换为 uint8(uint24(bytes3(0x291807))) (结果是 0x07 )。

  • 使用 msg.value 在非应付函数(或通过修饰符引入)中,不允许将其作为安全功能。将函数转换为 payable 或者为程序逻辑创建一个新的内部函数 msg.value .

  • 为了清楚起见,命令行界面现在需要 - 如果标准输入用作源。

不推荐使用的元素

本节列出了不支持先前功能或语法的更改。请注意,许多这些更改已经在实验模式下启用。 v0.5.0 .

命令行和JSON接口

  • 命令行选项 --formal (用于生成用于进一步正式验证的WHY3输出)已弃用,现在已删除。新的正式验证模块smtchecker通过 pragma experimental SMTChecker; .

  • 命令行选项 --julia 重命名为 --yul 由于中间语言的更名 JuliaYul .

  • 这个 --clone-bin--combined-json clone-bin 已删除命令行选项。

  • 不允许使用空前缀重新映射。

  • JSON AST字段 constantpayable 已删除。信息现在出现在 stateMutability 字段。

  • JSON AST字段 isConstructorFunctionDefinition 节点被一个名为 kind 有价值的 "constructor""fallback""function" .

  • 在未链接的二进制十六进制文件中,库地址占位符现在是完全限定库名的keccak256哈希的前36个十六进制字符,由 $...$ .以前,只使用完全限定的库名称。这减少了碰撞的机会,尤其是在使用长路径时。二进制文件现在还包含从这些占位符到完全限定名的映射列表。

施工人员

  • 现在必须使用 constructor 关键字。

  • 现在不允许调用不带括号的基构造函数。

  • 现在不允许在同一继承层次结构中多次指定基本构造函数参数。

  • 现在不允许使用参数调用带有错误参数计数的构造函数。如果只想指定继承关系而不提供参数,则完全不要提供括号。

功能

  • 功能 callcode 现在不允许 delegatecall )。仍然可以通过内联程序集使用它。

  • suicide 现在不允许 selfdestruct

  • sha3 现在不允许 keccak256

  • throw 现在不允许 revertrequireassert

转换

  • 从十进制文本到 bytesXX 现在不允许使用类型。

  • 从十六进制文本到 bytesXX 现在不允许使用不同大小的类型。

文字和后缀

  • 单位名称 years 现在由于错综复杂和对闰年的困惑而被禁止。

  • 不允许尾随数字的点。

  • 将十六进制数与单位名称(例如 0x1e wei )现在不允许。

  • 前缀 0X 只允许十六进制数 0x 是可能的。

变量

  • 为了清晰起见,现在不允许声明空结构。

  • 这个 var 现在不允许使用关键字来支持明确性。

  • 现在不允许在具有不同数量组件的元组之间进行赋值。

  • 不允许使用非编译时常量的值。

  • 现在不允许使用值数量不匹配的多变量声明。

  • 现在不允许使用未初始化的存储变量。

  • 现在不允许使用空元组组件。

  • 在变量和结构中检测循环依赖项的递归限制为256。

  • 现在不允许使用长度为零的固定大小数组。

句法

  • 使用 constant 现在不允许使用as函数状态可变修饰符。

  • 布尔表达式不能使用算术运算。

  • 一元的 + 现在不允许使用运算符。

  • 文字不能再用于 abi.encodePacked 没有预先转换为显式类型。

  • 对于具有一个或多个返回值的函数,现在不允许使用空的返回语句。

  • 现在完全不允许使用“松散汇编”语法,也就是说,不能再使用跳转标签、跳转和非功能指令。使用新的 whileswitchif 而是构造。

  • 没有实现的函数不能再使用修饰符。

  • 现在不允许使用具有命名返回值的函数类型。

  • 现在不允许在if/while/中为非块体声明单语句变量。

  • 新关键字: calldataconstructor .

  • 新的保留关键字: aliasapplyautocopyofdefineimmutableimplementsmacromutableoverridepartialpromisereferencesealedsizeofsupportstypedefunchecked .

与旧合同的互操作性

通过为它们定义接口,仍然可以与为v0.5.0之前的solidity版本编写的合同(或其他方式)进行接口。假设您已经部署了以下0.5.0之前的合同:

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.4.25;
// This will report a warning until version 0.4.25 of the compiler
// This will not compile after 0.5.0
contract OldContract {
    function someOldFunction(uint8 a) {
        //...
    }
    function anotherOldFunction() constant returns (bool) {
        //...
    }
    // ...
}

这将不再使用solidity v0.5.0进行编译。但是,您可以为它定义一个兼容的接口:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.9.0;
interface OldContract {
    function someOldFunction(uint8 a) external;
    function anotherOldFunction() external returns (bool);
}

注意我们没有声明 anotherOldFunction 成为 view 尽管它被宣布 constant 在原合同中。这是因为从固体度v0.5.0开始 staticcall 用于呼叫 view 功能。在v0.5.0之前, constant 未强制使用关键字,因此调用声明的函数 constant 具有 staticcall 可能仍然会恢复,因为 constant 函数仍可能尝试修改存储。因此,在为旧合同定义接口时,应该只使用 view 代替 constant 如果您完全确定该函数将与 staticcall .

考虑到上面定义的接口,您现在可以轻松地使用已经部署的0.5.0之前的合同:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.9.0;

interface OldContract {
    function someOldFunction(uint8 a) external;
    function anotherOldFunction() external returns (bool);
}

contract NewContract {
    function doSomething(OldContract a) public returns (bool) {
        a.someOldFunction(0x42);
        return a.anotherOldFunction();
    }
}

同样,可以使用pre-0.5.0库,方法是在不实现的情况下定义库的功能,并在链接期间提供pre-0.5.0库的地址(请参见 使用命令行编译器 关于如何使用命令行编译器进行链接):

// This will not compile after 0.6.0
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.5.0;

library OldLibrary {
    function someFunction(uint8 a) public returns(bool);
}

contract NewContract {
    function f(uint8 a) public returns (bool) {
        return OldLibrary.someFunction(a);
    }
}

例子

以下示例显示了solidity v0.5.0的合同及其更新版本,其中包括本节列出的一些更改。

旧版本:

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.4.25;
// This will not compile after 0.5.0

contract OtherContract {
    uint x;
    function f(uint y) external {
        x = y;
    }
    function() payable external {}
}

contract Old {
    OtherContract other;
    uint myNumber;

    // Function mutability not provided, not an error.
    function someInteger() internal returns (uint) { return 2; }

    // Function visibility not provided, not an error.
    // Function mutability not provided, not an error.
    function f(uint x) returns (bytes) {
        // Var is fine in this version.
        var z = someInteger();
        x += z;
        // Throw is fine in this version.
        if (x > 100)
            throw;
        bytes memory b = new bytes(x);
        y = -3 >> 1;
        // y == -1 (wrong, should be -2)
        do {
            x += 1;
            if (x > 10) continue;
            // 'Continue' causes an infinite loop.
        } while (x < 11);
        // Call returns only a Bool.
        bool success = address(other).call("f");
        if (!success)
            revert();
        else {
            // Local variables could be declared after their use.
            int y;
        }
        return b;
    }

    // No need for an explicit data location for 'arr'
    function g(uint[] arr, bytes8 x, OtherContract otherContract) public {
        otherContract.transfer(1 ether);

        // Since uint32 (4 bytes) is smaller than bytes8 (8 bytes),
        // the first 4 bytes of x will be lost. This might lead to
        // unexpected behavior since bytesX are right padded.
        uint32 y = uint32(x);
        myNumber += y + msg.value;
    }
}

新版本:

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.5.0;
// This will not compile after 0.6.0

contract OtherContract {
    uint x;
    function f(uint y) external {
        x = y;
    }
    function() payable external {}
}

contract New {
    OtherContract other;
    uint myNumber;

    // Function mutability must be specified.
    function someInteger() internal pure returns (uint) { return 2; }

    // Function visibility must be specified.
    // Function mutability must be specified.
    function f(uint x) public returns (bytes memory) {
        // The type must now be explicitly given.
        uint z = someInteger();
        x += z;
        // Throw is now disallowed.
        require(x <= 100);
        int y = -3 >> 1;
        require(y == -2);
        do {
            x += 1;
            if (x > 10) continue;
            // 'Continue' jumps to the condition below.
        } while (x < 11);

        // Call returns (bool, bytes).
        // Data location must be specified.
        (bool success, bytes memory data) = address(other).call("f");
        if (!success)
            revert();
        return data;
    }

    using address_make_payable for address;
    // Data location for 'arr' must be specified
    function g(uint[] memory /* arr */, bytes8 x, OtherContract otherContract, address unknownContract) public payable {
        // 'otherContract.transfer' is not provided.
        // Since the code of 'OtherContract' is known and has the fallback
        // function, address(otherContract) has type 'address payable'.
        address(otherContract).transfer(1 ether);

        // 'unknownContract.transfer' is not provided.
        // 'address(unknownContract).transfer' is not provided
        // since 'address(unknownContract)' is not 'address payable'.
        // If the function takes an 'address' which you want to send
        // funds to, you can convert it to 'address payable' via 'uint160'.
        // Note: This is not recommended and the explicit type
        // 'address payable' should be used whenever possible.
        // To increase clarity, we suggest the use of a library for
        // the conversion (provided after the contract in this example).
        address payable addr = unknownContract.make_payable();
        require(addr.send(1 ether));

        // Since uint32 (4 bytes) is smaller than bytes8 (8 bytes),
        // the conversion is not allowed.
        // We need to convert to a common size first:
        bytes4 x4 = bytes4(x); // Padding happens on the right
        uint32 y = uint32(x4); // Conversion is consistent
        // 'msg.value' cannot be used in a 'non-payable' function.
        // We need to make the function payable
        myNumber += y + msg.value;
    }
}

// We can define a library for explicitly converting ``address``
// to ``address payable`` as a workaround.
library address_make_payable {
    function make_payable(address x) internal pure returns (address payable) {
        return address(uint160(x));
    }
}