长篇干货|以太坊智能合约 —— ***安全开发指南(附代码) (5)

Fallback函数在合约执行消息发送没有携带参数(或当没有匹配的函数可供调用)时将会被调用,而且当调用 .send() or .transfer()时,只会有2,300 gas 用于失败后fallback函数的执行*(译者注:合约收到Ether也会触发fallback函数执行)*。

如果你希望能够监听.send()或.transfer()接收到Ether,则可以在fallback函数中使用event*(译者注:让客户端监听相应事件做相应处理)*。谨慎编写fallback函数以免gas不够用。

// bad

function() payable { balances[msg.sender] += msg.value; }

// good

function deposit() payable external { balances[msg.sender] += msg.value; }

function() payable { LogDepositReceived(msg.sender); }

明确标明函数和状态变量的可见性

明确标明函数和状态变量的可见性。函数可以声明为 external,public, internal 或 private。

分清楚它们之间的差异,例如external 可能已够用而不是使用 public。对于状态变量,external是不可能的。明确标注可见性将使得更容易避免关于谁可以调用该函数或访问变量的错误假设。

// bad uint x; // the default is private for state variables, but it should be made explicit

functionbuy() { // the default is public

// public code }

// good uint private y;

function buy() external {    // only callable externally }

function utility() public {    // callable externally, as well as internally: changing this code requires thinking about both cases. }

function internalAction() internal {    // internal code }

将程序锁定到特定的编译器版本

智能合约应该应该使用和它们测试时使用最多的编译器相同的版本来部署。锁定编译器版本有助于确保合约不会被用于最新的可能还有bug未被发现的编译器去部署。智能合约也可能会由他人部署,而pragma标明了合约作者希望使用哪个版本的编译器来部署合约。

// bad pragma solidity ^0.4.4;

// good pragma solidity 0.4.4;

(译者注:这当然也会付出兼容性的代价)

小心分母为零 (Solidity < 0.4)

早于0.4版本, 当一个数尝试除以零时,Solidity 返回zero 并没有 throw 一个异常。确保你使用的Solidity版本至少为 0.4。

区分函数和事件

为了防止函数和事件(Event)产生混淆,命名一个事件使用大写并加入前缀(我们建议LOG)。对于函数, 始终以小写字母开头,构造函数除外。

// bad event Transfer() {}

function transfer() {}

// good event LogTransfer() {}

function transfer() external {}

使用Solidity更新的构造器

更合适的构造器/别名,如selfdestruct(旧版本为\'suicide)和keccak256(旧版本为sha3)。

像require(msg.sender.send(1 ether))``的模式也可以简化为使用transfer(),如msg.sender.transfer(1 ether)。

4 已知的攻击

竞态*

调用外部契约的主要危险之一是它们可以接管控制流,并对调用函数意料之外的数据进行更改。 这类bug有多种形式,导致DAO崩溃的两个主要错误都是这种错误。

重入

这个版本的bug被注意到是其可以在第一次调用这个函数完成之前被多次重复调用。对这个函数不断的调用可能会造成极大的破坏。

// INSECURE mapping (address => uint) private

userBalances;

function withdrawBalance() public {    uint amountToWithdraw =

userBalances[msg.sender];    if (!(msg.sender.call.value(amountToWithdraw)())) { throw; } // At this point, the caller\'s code is executed, and can call withdrawBalance again        userBalances[msg.sender] = 0;

}

(译者注:使用msg.sender.call.value()())传递给fallback函数可用的气是当前剩余的所有气,在这里,假如从你账户执行提现操作的恶意合约的fallback函数内递归调用你的withdrawBalance()便可以从你的账户转走更多的币。)

可以看到当调msg.sender.call.value()()时,并没有将userBalances[msg.sender] 清零,于是在这之前可以成功递归调用很多次withdrawBalance()函数。 一个非常相像的bug便是出现在针对 DAO 的攻击。

在给出来的例子中,***的方法是:使用 send() 而不是call.value()()。这将避免多余的代码被执行。

然而,如果你没法完全移除外部调用,另一个简单的方法来阻止这个攻击是确保你在完成你所有内部工作之前不要进行外部调用:

mapping (address => uint) private userBalances;

functionwithdrawBalance() public {

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zgxdjj.html