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_x到- uint[] storage x = m_x和- function f(uint[][] x)到- function f(uint[][] memory x)在哪里?- memory是数据位置,可能被替换为- storage或- calldata因此。注意- external函数需要数据位置为的参数- calldata.
- 合同类型不包括 - address成员,以便分隔命名空间。因此,在使用- address成员。示例:如果- c是合同,变更- c.transfer(...)到- address(c).transfer(...)和- c.balance到- address(c).balance.
- 现在不允许在不相关的合同类型之间进行显式转换。只能将合同类型转换为其基类型或祖先类型之一。如果您确定某个协定与要转换为的协定类型兼容,尽管它不从中继承,但可以通过将转换为 - address第一。示例:如果- A和- B是合同类型,- B不从继承- A和- b是一种类型的合同- B,您仍然可以转换- b到类型- A使用- A(address(b)).请注意,您仍然需要注意匹配应付回退函数,如下所述。
- 这个 - address类型被拆分为- address和- address payable,只有在哪里- address payable提供- transfer功能。一个- address payable可直接转换为- address但另一种方式是不允许的。转换- address到- address payable可以通过转换- uint160.如果- c是一份合同,- address(c)结果在- address payable只有- c具有应付回退功能。如果你使用 withdraw pattern ,您很可能不需要更改代码,因为- transfer仅用于- msg.sender而不是存储的地址和- msg.sender是一个- address payable.
- 之间的转换 - bytesX和- uintY由于以下原因,现在不允许使用不同大小的- bytesX右侧的填充和- uintY可能导致意外转换结果的左侧填充。现在,在转换之前,必须在文字内调整大小。例如,您可以将- bytes4(4个字节)设置为- uint64(8字节)首先将- bytes4变量设置为- bytes8然后再到- uint64。进行转换时,您会得到相反的填充- uint32。在v0.5.0之前的版本中,- bytesX和- uintY会经历- 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由于中间语言的更名- Julia到- Yul.
- 这个 - --clone-bin和- --combined-json clone-bin已删除命令行选项。
- 不允许使用空前缀重新映射。 
- JSON AST字段 - constant和- payable已删除。信息现在出现在- stateMutability字段。
- JSON AST字段 - isConstructor的- FunctionDefinition节点被一个名为- kind有价值的- "constructor",- "fallback"或- "function".
- 在未链接的二进制十六进制文件中,库地址占位符现在是完全限定库名的keccak256哈希的前36个十六进制字符,由 - $...$.以前,只使用完全限定的库名称。这减少了碰撞的机会,尤其是在使用长路径时。二进制文件现在还包含从这些占位符到完全限定名的映射列表。
施工人员
- 现在必须使用 - constructor关键字。
- 现在不允许调用不带括号的基构造函数。 
- 现在不允许在同一继承层次结构中多次指定基本构造函数参数。 
- 现在不允许使用参数调用带有错误参数计数的构造函数。如果只想指定继承关系而不提供参数,则完全不要提供括号。 
功能
- 功能 - callcode现在不允许- delegatecall)。仍然可以通过内联程序集使用它。
- suicide现在不允许- selfdestruct)
- sha3现在不允许- keccak256)
- throw现在不允许- revert,- require和- assert)
转换
- 从十进制文本到 - bytesXX现在不允许使用类型。
- 从十六进制文本到 - bytesXX现在不允许使用不同大小的类型。
文字和后缀
- 单位名称 - years现在由于错综复杂和对闰年的困惑而被禁止。
- 不允许尾随数字的点。 
- 将十六进制数与单位名称(例如 - 0x1e wei)现在不允许。
- 前缀 - 0X只允许十六进制数- 0x是可能的。
变量
- 为了清晰起见,现在不允许声明空结构。 
- 这个 - var现在不允许使用关键字来支持明确性。
- 现在不允许在具有不同数量组件的元组之间进行赋值。 
- 不允许使用非编译时常量的值。 
- 现在不允许使用值数量不匹配的多变量声明。 
- 现在不允许使用未初始化的存储变量。 
- 现在不允许使用空元组组件。 
- 在变量和结构中检测循环依赖项的递归限制为256。 
- 现在不允许使用长度为零的固定大小数组。 
句法
- 使用 - constant现在不允许使用as函数状态可变修饰符。
- 布尔表达式不能使用算术运算。 
- 一元的 - +现在不允许使用运算符。
- 文字不能再用于 - abi.encodePacked没有预先转换为显式类型。
- 对于具有一个或多个返回值的函数,现在不允许使用空的返回语句。 
- 现在完全不允许使用“松散汇编”语法,也就是说,不能再使用跳转标签、跳转和非功能指令。使用新的 - while,- switch和- if而是构造。
- 没有实现的函数不能再使用修饰符。 
- 现在不允许使用具有命名返回值的函数类型。 
- 现在不允许在if/while/中为非块体声明单语句变量。 
- 新关键字: - calldata和- constructor.
- 新的保留关键字: - alias,- apply,- auto,- copyof,- define,- immutable,- implements,- macro,- mutable,- override,- partial,- promise,- reference,- sealed,- sizeof,- supports,- typedef和- unchecked.
与旧合同的互操作性
通过为它们定义接口,仍然可以与为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));
    }
}