泰岳联盟链,智能合约中的安全

call调用

contract auction {
  address highestBidder;
  uint highestBid;
  function bid() {
    if (msg.value < highestBid) throw;
    if (highestBidder != 0)
      highestBidder.send(highestBid); // refund previous bidder
    highestBidder = msg.sender;
    highestBid = msg.value;
  }
}

由于最大堆栈深度为1024,因此新投标人可以始终将堆栈大小增加到1023,然后再进行呼叫bid(),这将导致send(highestBid)呼叫默默失败(即,先前的投标人将不会获得退款),但是新投标人仍将是最高的投标人。检查是否send成功的一种方法是检查其返回值:

if (highestBidder != 0)
  if (!highestBidder.send(highestBid))
    throw;

防止这两种情况的唯一方法是通过让接收者控制转移,将发送模式转换为撤回模式:

contract auction {
  address highestBidder;
  uint highestBid;
  mapping(address => uint) refunds;
  function bid() {
    if (msg.value < highestBid) throw;
    if (highestBidder != 0)
      refunds[highestBidder] += highestBid;
    highestBidder = msg.sender;
    highestBid = msg.value;
  }
  function withdrawRefund() {
    if (msg.sender.send(refunds[msg.sender]))
      refunds[msg.sender] = 0;
  }
}

为什么在合同上方仍然说“负面例子”?由于天然气技术的原因,合同实际上是可以的,但它仍然不是一个很好的例子。原因是不可能阻止作为发送的一部分在接收者处执行代码。这意味着在发送功能仍在进行中时,收件人可以回拨到withdrawRefund。那时,退款金额仍然相同,因此他们将再次获得退款,依此类推。在此特定示例中,它不起作用,因为接收者仅获得汽油津贴(2100gas),并且无法用此数量的gas执行另一次发送。下面的代码,虽然是容易受到这种攻击:

msg.sender.call.value(refunds[msg.sender])()

以下代码可以解决

contract auction {
  address highestBidder;
  uint highestBid;
  mapping(address => uint) refunds;
  function bid() {
    if (msg.value < highestBid) throw;
    if (highestBidder != 0)
      refunds[highestBidder] += highestBid;
    highestBidder = msg.sender;
    highestBid = msg.value;
  }
  function withdrawRefund() {
    uint refund = refunds[msg.sender];
    refunds[msg.sender] = 0;
    if (!msg.sender.send(refund))
     refunds[msg.sender] = refund;
  }
}

Gas的限制

一个区块中可以消耗多少天然气是有限制的。这个限制是灵活的,但是很难增加它。这意味着在所有(合理)情况下,合同中的每个功能都应保持在一定量的gas以下。以下是投票合同的不良示例:

contract Voting {
  mapping(address => uint) voteWeight;
  address[] yesVotes;
  uint requiredWeight;
  address beneficiary;
  uint amount;
  function voteYes() { yesVotes.push(msg.sender); }
  function tallyVotes() {
    uint yesVotes;
    for (uint i = 0; i < yesVotes.length; ++i)
      yesVotes += voteWeight[yesVotes[i]];
    if (yesVotes > requiredWeight)
      beneficiary.send(amount);
  }
}

合同实际上有几个问题,但是我想在这里强调的是循环问题:假设投票权重像令牌一样是可转让和可拆分的(以DAO令牌为例)。这意味着您可以创建任意数量的自己的克隆。创建此类克隆将增加tallyVotes函数中循环的长度,直到消耗的gas超过单个块中可用的gas为止。

这适用于使用循环的任何内容,也适用于合同中未明确显示循环的情况,例如,在存储内部复制数组或字符串时。同样,如果循环的长度是由调用者控制的,例如,如果您遍历作为函数参数传递的数组,则可以使用任意长度的循环。但是,切勿创建这样一种情况:环路长度受一方控制,而一方不是唯一遭受失败的一方。

附带说明一下,这就是为什么我们现在在DAO合同中拥有冻结帐户的概念的原因之一:投票权重是在投票开始时计算的,以防止循环陷入困境以及是否投票在投票期结束之前,权重是固定的,您可以通过只转移令牌然后再次投票来进行第二次投票。

Throw操作

row语句通常非常方便,可以在调用过程中恢复对状态所做的任何更改(或整个事务,具体取决于调用函数的方式)。但是,您必须知道,它还会导致所有gas都被消耗掉,因此很昂贵,并且有可能使对当前函数的调用停止。因此,我建议仅在以下情况下建议使用它:

1. send

如果某个函数不是要以当前状态或当前参数接收以太币,则应使用throw拒绝。由于gas和堆栈深度问题,使用throw是可靠地发送的唯一方法:接收者的回退功能可能有错误,该功能占用了太多的gas,因此无法接收以太坊,或者该功能可能是在恶意软件中调用的堆栈深度过高的上下文(可能甚至在调用函数之前)。

2.恢复调用函数的效果

如果在其他合同上调用函数,则永远无法知道它们是如何实现的。这意味着这些调用的效果也不知道,因此还原这些效果的唯一方法是使用throw。当然,如果您知道必须恢复效果,则应该始终写合同时不要一开始就调用这些函数,但是在某些用例中,事后才知道。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值