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));
}
}