Solidity v0.8.0 突破性变更
本节重点介绍了 Solidity 版本 0.8.0 中引入的主要突破性变更。有关完整列表,请查看 发布变更日志.
语义的静默更改
本节列出了现有代码更改其行为,而编译器没有通知您的更改。
算术运算在出现下溢和上溢时会回退。您可以使用
unchecked { ... }来使用之前的包装行为。溢出检查非常普遍,因此我们将其设为默认值以提高代码的可读性,即使这会略微增加 gas 成本。
ABI 编码器 v2 默认激活。
您可以选择使用旧的行为,使用
pragma abicoder v1;。pragmapragma experimental ABIEncoderV2;仍然有效,但已弃用且没有效果。如果您想明确说明,请使用pragma abicoder v2;代替。请注意,ABI 编码器 v2 支持比 v1 更多的类型,并对输入执行更多完整性检查。ABI 编码器 v2 使一些函数调用更昂贵,它还会使合约调用回退,这些调用在使用 ABI 编码器 v1 时不会回退,当它们包含不符合参数类型的 data 时。
指数运算符是右结合的,即表达式
a**b**c被解析为a**(b**c)。在 0.8.0 之前,它被解析为(a**b)**c。这是解析指数运算符的常见方法。
失败的断言和其他内部检查(如除以零或算术溢出)不使用无效操作码,而是使用回退操作码。更具体地说,它们将使用等于对
Panic(uint256)的函数调用的错误数据,该数据包含特定于情况的错误代码。这将节省错误时的 gas,同时仍然允许静态分析工具将这些情况与对无效输入的回退区分开来,例如失败的
require。如果访问存储中的字节数组,其长度编码不正确,则会导致 panic。除非使用内联汇编来修改存储字节数组的原始表示,否则合约不会陷入这种情况。
如果在数组长度表达式中使用常量,以前的 Solidity 版本将在评估树的所有分支中使用任意精度。现在,如果使用常量变量作为中间表达式,则它们的价值将像在运行时表达式中使用时一样被正确舍入。
类型
byte已被移除。它只是bytes1的别名。
新限制
本节列出了可能导致现有合约无法再编译的更改。
与字面量的显式转换相关的限制新增。以下情况下的先前行为可能模棱两可
不允许从负字面量和大于
type(uint160).max的字面量到address的显式转换。字面量和整数类型
T之间的显式转换仅在字面量介于type(T).min和type(T).max之间时才允许。特别地,请将uint(-1)的用法替换为type(uint).max。字面量和枚举之间的显式转换仅在字面量可以表示枚举中的值时才允许。
字面量和
address类型之间的显式转换(例如address(literal))的类型为address而不是address payable。可以通过使用显式转换来获得可支付地址类型,即payable(literal)。
地址字面量 的类型为
address而不是address payable。可以通过使用显式转换将它们转换为address payable,例如payable(0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF)。显式类型转换存在新的限制。只有在符号、宽度或类型类别 (
int、address、bytesNN等) 最多发生一次变化时,才允许进行转换。要执行多次更改,请使用多次转换。让我们使用符号
T(S)来表示显式转换T(x),其中,T和S是类型,而x是任何任意类型为S的变量。此类不允许的转换的示例将是uint16(int8),因为它既更改宽度(8 位到 16 位)又更改符号(有符号整数到无符号整数)。为了进行转换,必须通过中间类型进行。在前面的示例中,这将是uint16(uint8(int8))或uint16(int16(int8))。请注意,这两种转换方式将产生不同的结果,例如,对于-1。以下是一些按此规则不允许的转换示例。address(uint)和uint(address):转换类型类别和宽度。将此分别替换为address(uint160(uint))和uint(uint160(address))。payable(uint160)、payable(bytes20)和payable(integer-literal):转换类型类别和状态可变性。将此分别替换为payable(address(uint160))、payable(address(bytes20))和payable(address(integer-literal))。请注意,payable(0)是有效的,是该规则的例外。int80(bytes10)和bytes10(int80):转换类型类别和符号。将此分别替换为int80(uint80(bytes10))和bytes10(uint80(int80)。Contract(uint):转换类型类别和宽度。将此替换为Contract(address(uint160(uint)))。
这些转换被禁止以避免歧义。例如,在表达式
uint16 x = uint16(int8(-1))中,x的值将取决于符号或宽度转换的优先顺序。函数调用选项只能给出一次,即
c.f{gas: 10000}{value: 1}()无效,必须更改为c.f{gas: 10000, value: 1}()。全局函数
log0、log1、log2、log3和log4已被移除。这些是基本函数,基本上没有使用。它们的 behavior 可以从内联汇编中访问。
enum定义不能包含超过 256 个成员。这将使安全地假设 ABI 中的底层类型始终为
uint8。使用名称
this、super和_的声明被禁止,公共函数和事件除外。例外是为了能够声明以 Solidity 以外的语言实现的合约的接口,这些语言允许使用此类函数名称。代码中不再支持
\b、\f和\v转义序列。它们仍然可以通过十六进制转义插入,例如\x08、\x0c和\x0b。全局变量
tx.origin和msg.sender的类型为address,而不是address payable。可以通过显式转换将它们转换为address payable,例如payable(tx.origin)或payable(msg.sender)。进行此更改是因为编译器无法确定这些地址是否可支付,因此现在需要显式转换才能使此要求可见。
显式转换为
address类型始终返回非可支付的address类型。特别是,以下显式转换的类型为address,而不是address payableaddress(u),其中u是类型为uint160的变量。可以通过使用两个显式转换将u转换为address payable类型,即payable(address(u))。address(b),其中b是类型为bytes20的变量。可以通过使用两个显式转换将b转换为address payable类型,即payable(address(b))。address(c),其中c是一个合约。之前,此转换的返回值类型取决于合约是否可以接收以太币(要么是具有接收函数,要么是具有可支付回退函数)。转换payable(c)的类型为address payable,并且仅当合约c可以接收以太币时才允许。通常,始终可以通过使用以下显式转换将c转换为address payable类型:payable(address(c))。请注意,address(this)与address(c)属于同一类别,并遵循相同的规则。
内联汇编中的
chainid内建函数现在被认为是view,而不是pure。一元否定运算符不再适用于无符号整数,仅适用于有符号整数。
接口变更
--combined-json的输出已更改:JSON 字段abi、devdoc、userdoc和storage-layout现在是子对象。在 0.8.0 之前,它们被序列化为字符串。“传统 AST” 已被删除(命令行界面上的
--ast-json和标准 JSON 的legacyAST)。使用“紧凑 AST”(--ast-compact--json或AST)作为替代。旧的错误报告器(
--old-reporter)已被删除。
如何更新您的代码
如果您依赖于包装算术运算,请将每个运算用
unchecked { ... }括起来。可选:如果您使用 SafeMath 或类似的库,请将
x.add(y)更改为x + y,将x.mul(y)更改为x * y等等。如果您想保留旧的 ABI 编码器,请添加
pragma abicoder v1;。可选地删除
pragma experimental ABIEncoderV2或pragma abicoder v2,因为它已变得多余。将
byte更改为bytes1。根据需要添加中间显式类型转换。
将
c.f{gas: 10000}{value: 1}()合并为c.f{gas: 10000, value: 1}()。将
msg.sender.transfer(x)更改为payable(msg.sender).transfer(x)或使用address payable类型存储的变量。将
x**y**z更改为(x**y)**z。使用内联汇编作为
log0、…、log4的替代品。通过从类型的最大值减去无符号整数并加上 1 来对无符号整数取反(例如
type(uint256).max - x + 1,同时确保x不为零)