solidityv0.5.0的重大改变

原创
2230 天前
13693

作者: 隔壁老王(Old Wang NExt Door)

前言

随着solidity 0.5.0 nightly build版本的稳步推进,正式版也将在不久的将来与开发者见面.作为一个大版本更新,新版引入了很多特性,也废弃了很多关键字,比如

  • .call()不仅可以获知远程调用执行成功与否,还将获得远程调用执行的返回值
  • ABI解码做了新的处理规范,有效防御了"短地址攻击"
  • address地址类型细分成 address和 address payable
  • uintY和 bytesX不能直接转换
  • 回退函数必须显式声明为 external可见性
  • 构造函数必须用 constructor关键字定义
  • 用于抛出异常的 throw关键字弃用, 函数状态可变性修饰符必须用 view,不能混用 constant和 view
  • ...

下面我们将对这些改变一一予以介绍,最后给出一个示例代码,对比展示新旧版solidity代码写法的区别,供大家参考.

显式声明

函数可见性

  • 函数可见性必须显式声明. 之前, 函数如果不显式声明,将默认 public可见性.
  • public: constructor构造函数必须声明为 public可见性,否则编译报错.
  • external: 回退函数(fallback function), 接口(interface)的函数必须声明为 external可见性,否则编译报错.

存储位置

  • 结构体(struct),数组(array),映射(mapping)类型的变量必须显式声明存储位置( storage, memeory, calldata),包括函数参数和返回值变量都必须显式声明.
  • external 的函数参数需显式声明为 calldata.

合约与地址

  • contract合约类型不再包括 address类型的成员函数,必须显式转换成 address地址类型才能使用 send(), transfer(), balance等与之相关的成员函数/变量成员.
  • address地址类型细分为 address和 address payable,只有 address payable可以使用 transfer(), send()函数.
  • address payable类型可以直接转换为 address类型, 反之不能.
  • 但是 address x可以通过 address(uint160(x)),强制转换成 address payable类型.
  • 如果 contract A不具有 payable的回退函数, 那么 address(A)是 address类型.
  • 如果 contract A具有 payable的回退函数, 那么 address(A)是 address payable类型.
  • msg.sender属于 address payable类型.

转换与填充(padding)

uintY与 bytesX

  • 因为填充(padding)的时候, bytesX是右填充(低比特位补0),而 uintY是左填充(高比特位补0),二者直接转换可能会导致不符合预期的结果,所以现在当 bytesX和 uintY长度大小不一致(即X*8 != Y)时,不能直接转换,必须先转换到相同长度,再转换到相同类型.
  • 10进制数值不能直接转换成 bytesX类型, 必须先转换到与 bytesX相同长度的 uintY,再转换到 bytesX类型
  • 16进制数值如果长度与 bytesX不相等,也不能直接转换成 byteX类型

ABI

  • 字面值必须显式转换成类型才能使用 abi.encodePacked()
  • ABI编码器在构造外部函数入参和 abi.encode()会恰当地处理 bytes和 string类型的填充(padding),若不需要进行填充,请使用 abi.encodePacked()
  • ABI解码器在解析函数入参和 abi.decode()时,如果发现 calldata太短或超长,将直接抛出异常,而不是像之前自动填充(补0)和截断处理,从而有效地遏制了短地址攻击.
  • .call()族函数( .call(), .delegatecall(), .staticcall())和 哈希函数( keccak256(),sha256(), ripemd160())只接受一个参数 bytes,且不进行填充(padding)处理.
  • .call()空参数必须写成 .call("")
  • .call(sig,a,b,c)必须写成 .call(abi.encodeWithSignature(sig,a,b,c)),其他类推
  • keccak256(a,b,c)必须写成 keccak256(abi.encodePacked(a,b,c)),其他类推
  • 另外, .call()族函数之前只返回函数执行成功是否的 bool, 现在还返回函数执行的返回值,即 (bool,bytes memory). 所以之前 boolresult=<contract>.call(sig,a,b,c)现在必须写成 (boolresult,bytes memory data)=<contract>.call(sig,a,b,c).

不允许的写法

在之前版本的solidity编译,以下不允许的写法只会导致 warnings报警,现在将直接 errors报错.

  • 不允许声明0长度的定长数组类型.
  • 不允许声明0结构体成员的结构体类型.
  • 不允许声明未初始化的 storage变量.
  • 不允许定义具有命名返回值的函数类型.
  • 不允许定义非编译期常量的 constant常量. 如 uintconstant time=now;是不允许的.
  • 不允许 0X(X大写)做16进制前缀,只能用 0x.
  • 不允许16进制数和单位名称组合使用. 如 value=0xffether必须写成 value=0xff*1ether.
  • 不允许小数点后不跟数字的数值写法. 如 value=255.0ether不能写成 value=255.ether.
  • 不允许使用一元运算符 +. 例如 value=1ether不能写成 value=+1ether.
  • 不允许布尔表达式使用算术运算.
  • 不允许具有一个或多个返回值的函数使用空返回语句.
  • 不允许未实现的函数使用修饰符(modifier).
  • 不允许 msg.value用在非 payable函数里以及此函数的修饰符(modifier)里.

废弃的关键字/函数

  • years时间单位已弃用,因为闰年计算容易导致各种问题.
  • var已弃用,请用 uintY精确声明变量长度.
  • constant函数修饰符已弃用,不能用作修饰函数状态可变性, 请使用 view关键字.
  • throw关键字已弃用,请使用 revert(), require(), assert()抛出异常.
  • .callcode()已弃用,请使用 .delegatecall(). 但是注意,在内联汇编仍可使用.
  • suicide()已弃用, 请使用 selfdestruct().
  • sha3()已弃用,请使用 keccak256().

构造函数

  • 构造函数必须用 constructor关键字定义. 之前,并未强制要求,既可以用合约同名函数定义构造函数,也可以用 constructor关键字定义.
  • 不允许调用没有括号的基类构造函数.
  • 不允许在同一继承层次结构中多次指定基类构造函数参数.
  • 不允许调用带参数但具有错误参数计数的构造函数.如果只想在不提供参数的情况下指定继承关系,请不要提供括号.

其他

  • do...while循环里的 continue不再跳转到循环体内,而是跳转到 while处判断循环条件,若条件为假,就退出循环.这一修改更符合一般编程语言的设计风格.
  • 实现了C99风格的作用域:
  • 变量必须先声明,后使用.之前,是可以先使用,后声明,现在会导致编译报错.
  • 只能在相同或嵌套作用域使用.比如 if(){...}, do{...}while();, for{...}块内声明的变量,不能用在块外.
  • 变量和结构体的循环依赖递归限制在256层.
  • pure和 view函数在EVM内部采用 staticcall操作码实现(EVM版本>=拜占庭),而非之前的 call操作码,这使得状态不可更改(state changes disallowed)在虚拟机层面得到保证.

示例代码

其中 // Error...注释掉的代码在solidity版本<0.5.0均可以编译通过,但是在>=0.5.0均得换成 // Right ...的代码才能编译通过.

  1. pragma solidity >0.4.99 <0.6;
  2. contract A {
  3. function () external payable {}
  4. }
  5. contract B {
  6. function () external {}
  7. }
  8. interface ERC20 {
  9. // function transferFrom(address _from, address _to, uint256 _value) public; // Error, `interface` must declare with `external`
  10. function transfer(address _to, uint256 _value) external; // Right
  11. }
  12. contract F
  13. {
  14. address payable to_pay;
  15. address to;
  16. address owner;
  17. // uint constant time = now; // Error, value should be compile time constant.
  18. string constant time = "johnwick.io"; // Right
  19. modifier onlyOwner {
  20. require (msg.sender == owner);
  21. _;
  22. }
  23. // function () { // Error, fallback function MUST be `external`
  24. function () external { // Right
  25. }
  26. // function F() { // Error, consturctor function should use `constructor` keyword
  27. // constructor() { // Error, `constructor` should be `public`
  28. constructor () public { // Right
  29. }
  30. function payable_or_not() public {
  31. bool result;
  32. /* `address payable` VS `address` */
  33. // to_pay = new A(); // Error, `contract` should explicitly convert to `address`
  34. to_pay = address(new A()); // Right, fallback function of contract A is `payable`
  35. to = address(new A()); // Right
  36. to_pay.transfer(1); // Right, `transfer()` is member of `address payable`
  37. result = to_pay.send(1); // Right, `send()` is member of `address payable`
  38. to = address(new B()); // Right
  39. // to_pay = address(new B()); // Error, fallback function of contract B is not `payable`.
  40. // to.transfer(1 ether); // Error, `transfer()` is not member of `address`
  41. // result = to.send(1 ether); // Error, `send()` is not member of `address`
  42. bool success;
  43. bytes memory data;
  44. (success, data)= to.call.gas(0).value(1 ether)(""); // However, you can use `call("")` to send ether
  45. address john;
  46. john = to_pay; // Right, `address payable` can directly convert to `address`
  47. address payable wick;
  48. // wick = to; // Error, `address` can't directly convert to `address payable`
  49. wick = address(uint160(to)); // Right, `address` can forcibly convert to `address payable`
  50. wick.transfer(1 ether); // `wick` is `address payable` now
  51. }
  52. // struct dummy {} // Error, empty struct is not allowed.
  53. struct yummy {string food;} // Right
  54. // `external` function input parameter should explicitly declare `calldata` location.
  55. function transfer(address _to, uint _value, bytes calldata _data) pure external returns (bool success){
  56. // return; // Error, empty return statements for functions with one or more return values are now disallowed.
  57. }
  58. // `public` function input parameter should explicitly declare `memory` location.
  59. function try_some(bytes memory _data) public view returns(bool) {
  60. if(to == to_pay)
  61. {
  62. // throw; // Error, `throw` is deprecated.
  63. revert(); // Right, you should use `assert(),require(),revert()` to raise an exception.
  64. }
  65. bytes32 _hash;
  66. // _hash = sha3(_data); // Error, `sha3()` is deprecated.
  67. _hash = keccak256(_data); // Right
  68. uint256 _time;
  69. // _time = 1 years; // Error, `years` is deprecated, due to the leap year problem.
  70. _time = 366 days; // Right
  71. // var secs = _time * 3600; // Error, `var` is deprecated.
  72. uint secs; secs = _time * 0x3600; // Right
  73. int256 _value;
  74. // _value = 0xff wei; // Error, hex number with unit is not allowed now.
  75. _value = 0xff*1 wei; // Right
  76. // _value = 0Xff*1 wei; // Error, hex number prefix `0X` is not allowed now.
  77. // _value = 1. ether; // Error, dot followed without numbers is not allowed now.
  78. _value = 1.0 ether; // Right
  79. // _value = +1; // Error
  80. _value = -1;
  81. // bytes storage not_initial_bytes; // Error, `storage` without initialization is not allowed now.
  82. // uint[0] zero_array; // Error, fixed-size array of zero length is not allowed now.
  83. bytes32 b32;
  84. bytes20 b20;
  85. bytes1 b1;
  86. uint256 u256;
  87. uint160 u160;
  88. // b32 = bytes32(u160); // Error, can't directly convert `uintX` to `bytesY` of different size.
  89. b32 = bytes32(uint256(u160)); // Right, convert to the same size, then convert to the same type.
  90. u160 = uint160(bytes20(b32)); // Right
  91. b32 = bytes32(u256); // Right, both are the same size, then convert to the same type.
  92. // b1 = 255; // Error
  93. b1 = bytes1(uint8(255+360)); // Right, decimal number like `uintX` should convert to the same size, then the same type.
  94. // b16 = 0xff; // Error, hex number and `bytesX` are different size.
  95. b20 = bytes20(uint160(0xff)); // Right, same size, then same type.
  96. b1 = 0xff; // Right, hex number and `bytesX` are the same size
  97. }
  98. function im_not_payable() public returns (uint256 _value){
  99. // _value = msg.value; // Error, `msg.value` MUST be used in `payable` function.
  100. }
  101. function im_payable() public payable returns (uint256 _value){
  102. _value = msg.value; // Right, `msg.value` CAN be used in `payable` function.
  103. }
  104. // function not_impletement() public onlyOwner; // Error, function without implementation can't use `modifier`
  105. function kill(address payable _to) public {
  106. // suicide(_to); // Error, `suicide` is deprecated.
  107. selfdestruct(_to); // Right
  108. }
  109. function scope_() view public {
  110. uint count = 0 ;
  111. for (uint i=1 ; i<100; i++){
  112. count += i;
  113. }
  114. // i = now; // Error, not C99-Style scope
  115. uint i; i = now; // Right, but this is error in solidity < 0.5.0 due to variable redefinition.
  116. }
  117. event ShowCount(uint);
  118. function inf_loop() public {
  119. uint count = 0;
  120. do {
  121. if (count!=0) {
  122. continue;
  123. }
  124. count = 1; // <0.5.0, `continue` jump to this loop body, which results in infinite loop !!!
  125. }while((count++)<10); // >=0.5.0 `continue` jump to this condition check.
  126. emit ShowCount(count);
  127. }
  128. }