总结与改进

还记得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地址。

results matching ""

    No results matching ""