总结与改进
还记得refundAll这个给投票者退款的方法么?在这里有一点需特别注意。refundAll通过数组迭代来向用户支付退款,但前提是每笔交易都成功,但凡有一笔交易失败将导致所有退款操作回滚,这意味着该循环将永远不会完成。
外部调用合约都有可能失败,为了减少这些损失最好把外部调用逻辑和内部逻辑分开:由收款方负责发起调用该方法。比如让用户自己拿回保证金而不是直接发送给他们,千万注意设好撤回资金的额度。
function withdraw() external checkVoter{
var voter = voters[msg.sender];
if(this.balance >= bail && finishFlag && !voter.isRefund){
if(msg.sender.send(bail)){
voter.isRefund = true;
}
}
}
比如在这里,废弃原来的refundAll方法改用withdraw,当发送退款成功时再标记isRefund为true。
至此第二个实例的主要部分就已经分析完成了,因为智能合约的安全非常重要,有必要简单介绍下几个著名的漏洞事件,希望读者以史为鉴。
DAO事件
DAO是一个数字分散的自治组织,也是一种由投资者主导的风险投资基金。2016年6月17日,黑客在智能合约中发现了一个漏洞,他可以从DAO中提出ether。 在袭击的前几个小时内,360万ETH被盗,相当于当时的7000万美元。
在这个漏洞攻击中,攻击者能够在智能合约更新其余额之前多次“请求”智能合约(DAO)转账ether(re-entry攻击)。 事实上,当DAO智能合约创建时,开发人员没有考虑到恶意递归调用的可能性,和智能合约先发送ETH资金,然后更新内部余额的缺陷。
Parity 钱包
由于Parity客户端发布的多重签名钱包智能合约存在严重漏洞,攻击者可以立即接管钱包(成为合约owner)并提取所有资金。原因在于合约代码中的基本漏洞未能正确限制外部调用。 在Solidity中,没有修饰符的方法被认为是public。 这意味着该方法可以从任何来源访问,包括对合同进行的外部交易调用。
黑客先向合约发起了一笔value为0的交易,里面包括在msg.data加上initWallet的调用:
function() payable {
// just being sent some cash?
if (msg.value > 0)
Deposit(msg.sender, msg.value);
else if (msg.data.length > 0)
_walletLibrary.delegatecall(msg.data);
}
而后调用了:
function initWallet(address[] _owners, uint _required, uint _daylimit) {
initDaylimit(_daylimit);
initMultiowned(_owners, _required);
}
重新初始化owner地址。