合约编程误区

介绍常见的合约编程错误带来的风险。

继承覆盖关键变量

pragma solidity >=0.4.22 <0.6.0;
contract owned {
address public owner;
constructor() public {
owner = msg.sender;
}
modifier onlyOwner {
require(msg.sender == owner, "owner calls only!");
_;
}
}
contract XXX_POS is owned {
address private owner;
function distributeDividends(uint256 amount)
public
onlyOwner
returns (bool)
{
require(address(this).balance >= amount, "!invalid withdraw amount");
owner.transfer(amount);
return true;
}
}

如上合约代码,关键功能性函数已忽略。

看似 onlyOwner modifier 工作正常,且整个合约没有修改 owner 的入口,那么按照逻辑,只有合约作者可以通过调用 distributeDividends 获得收益。

然而实际执行中,合约的余额并没有发送给 owner, 而是被发到了 T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb 这样一个地址。通过调用 owner() 检查,并没有发现 owner 被修改。

问题出在 address private owner; 一句,该私有变量申明通过合约继承链,屏蔽掉了父合约 owned 中的真正的 owner 变量。所以在 owner.transfer() 中使用的地址是一个未初始化的 address 类型,值为 "410000000000000000000000000000000000000000", 用 base58check 编码后正好是 T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb.

检查合约继承链中的所有变量定义,避免覆盖关键变量。

使用可预测变量获取随机数

pragma solidity >=0.4.22 <0.6.0;
contract RandGuess {
function genRandom() internal view returns (uint256) {
return uint256(keccak256(abi.encodePacked(block.difficulty, now)));
}
function guess(uint8 value) public payable {
require(msg.value == 1 trx);
if (value == uint8(genRandom())) {
msg.sender.transfer(2 trx);
}
}
}

如上合约代码,是一个简单的猜数字游戏。

uint256(keccak256(abi.encodePacked(block.difficulty, now))) 多见于以太坊合约的随机数生成,然而, 对于 TRON 来说,block.difficulty 恒为 0, 且 now(即 block.timestamp) 可准确预测。

最终结果是,攻击者可以提前计算随机数值,选择在合适的时间点发送交易,达到控制随机数的目的。

检查所有随机数生成源头,避免可预测的随机数。

相关细节参考: 波场Solidity智能合约安全实践:合约内随机数的实现.