区块链安全

区块链安全第 5 课 —— Solidity 新版本的新特性

在学习合约的继承时候,测试了书上的代码示例如下,但是书上应该是指定 Solidity 0.4.x 的编译版本,我尝试在较高版本进行编译,就出现了问题。下面来逐一解析问题,修改代码,顺便更加深入的理解一些概念。

问题 1:fallback () 回退函数

报错是因为在较高版本(大概是 0.6 以后版本),无名函数只能用 fallback () 表示,而不再使用 function 关键字。这种无名函数没有参数,无返回值。fallback 函数在一个合约里最多只能有一个,在合约调用没有匹配到函数签名,或者调用没有带任何数据时被自动调用。
接收以太坊的函数固定写法就是 fallback () external paybale(){}
public 和 external 都是表示函数范围的关键字,那为什么使用 external 而不使用 public 呢?对于public函数,每次调用时Solidity会将参数copy到内存中;而调用external函数,则可以直接读取calldata。内存分配在EVM中是非常昂贵的,而读取calldata则相对廉价很多。所以调用 public 函数的 gas 比 external 要多。
综上,作为最佳实践,如果一个函数仅仅需要外部调用,那么应该用external,如果一个函数需要内部和外部同时调用,那么使用public。而这个支付函数仅仅需要外面的调用者赚钱到此合约地址,所以仅仅需要外部调用,使用 external 关键字修饰。

  • 参考资料
  • https://docs.soliditylang.org/en/v0.7.4/contracts.html#fallback-function
  • https://tsaiyee.com/blog/2018/01/07/solidity-external-public-best-practices/

所以综上,将此处修改为:

问题2:强转 address payable

改完之后依然有报错,提示:

TypeError: "send" and "transfer" are only available for objects of type "address payable", not "address".
--> contracts/test.sol:32:9:

看上去是说,address 对象的 transfer() 方法只能被 address payable 类型调用,而不能被 address 类型调用。这是为什么呢?
在 Solidity 0.5.0 之前的版本中,address 是有 payable 属性的,因而也有 transfer 和 send 成员函数,可以对其转币。
而在 Solidity 0.5.0 以及更高版本中,有两种 address 数据类型:address  address payable。address 失去了隐含的 payable 属性,只能将币转给声明为 payable 的地址。

  • address:一个地址长度为20字节,也就是uint160(以太坊地址也是20字节)。
  • address payable:版本0.5.0开始,引入的新地址,应付地址,也是20字节。
  • address payable 相比 address,多了两个成员(函数)transfersend。其中的区别为,普通的address不能发送Ether,而address payable可以发送Ether。

而 msg.sender 是一个 address,没有 payable 属性。我们需要对其强转为 address payable:payable(msg.sender)。
所以最终将代码修改如下:

问题3:构造及自毁函数处的定义及强转

改完了上面两处。依然有报错,位置在自毁函数处。报错为:

TypeError: Invalid type for argument in function call. Invalid implicit conversion from address to address payable requested.
--> contracts/test.sol:25:22:

报错的意思是:入参实参的类型不对,入参是 address 类型,而期待的入餐类型是 address payable 类型。这个入参 owner 是通过继承 owned 合约继承来的,我们在父合约处把它改为正确的类型:

要注意在构造函数处,因为 owner 是 address payable 类型,而 msg.sender 是 address 类型,所以需要将 msg.sender 强转为 address payable 类型。

最终代码

最终代码如上,因为我也是个新手,所以也不知道是不是符合最佳实践。不过是编译过了而已。

比较好奇最终调用自毁函数会有什么效果,尝试了一下:

发现合约地址还能访问到,只是多了 destroy method 的交易触发记录。应该是以后这些函数都无法再使用了。但是很快我第二次调用 destroy 方法成功:

所以目前还不清楚自毁意味着什么,留待以后继续理解。大家可以理解一下最终的代码,因为里面实现了合约的继承,所以之后继承不再在博文里面描述,因为已经学过了。