合约实践:避免区块Gas限制导致问题

合约实践:避免区块Gas限制导致问题

区块限制

以太坊上的计算资源是有限的,单个区块可用的 Gas 是有个上限的,这就是“区块 Gas 上限”。 现在单个区块区块限制约为 990万。

矿工要收集交易并打包到区块里,因为矿工们可以得到 Gas 费用,理论上,他们会按gas price 排序,并尽可能高效利用区块的 Gas 空间,即让交易的 Gas Limit 总和尽可能接近区块 Gas 上限。一笔交易的 Gas 耗用量如果大于这个上限,是根本没法被打包的。

可能漏洞

当合约中存在依赖时间、依赖数据大小(如数组长度)的循环时,都可能会有潜在的漏洞。 随时间的延长,或数据的增大,gas 的消耗就可能对应的线性增长,很可能突破区块限制导致无法打包。

下面是一个有这样漏洞的合约:

pragma solidity ^0.5.20;
contract TerribleBank {
    struct Deposit {
        address depositor;
        uint256 amount;
    }
    Deposit[] public deposits;

    function deposit() external payable {
        deposits.push(Deposit({
            depositor: msg.sender,
            amount: msg.value
        }));
    }

    function withdrawAll() external {
        uint256 amount = 0;
        for (uint256 i = 0; i < deposits.length; i++) {
            if (deposits[i].depositor == msg.sender) {
                amount += deposits[i].amount;
                delete deposits[i];
            }
        }

        msg.sender.transfer(amount);
    }
}

如果 deposit 数组足够长,就会突破区块限制,无法打包, withdrawAll() 就再也没法被调用了。

而且攻击者很容易实现这一点——只需不断调用 deposit(),直至达到所需的数组长度。就会导致合约中所有的 ETH 都会被锁住。

解决措施

首先要尽可能避免使用这种循环,将依靠循环的线性增长的计算量尽可能转化为固定的计算量大小(或常量)。

如果没法做到第一步,那就要考虑限制循环的次数,即限制总数据的大小,把一个大数据分拆为多个分段的小数据。(即想办法限制单次的计算量大小)

比如依靠时间长度计算收益的质押合约, 可以采用设置质押有效期,比如设置质押有效期最长为一年, 一年到期之后,用户需提取再质押。

而上面的 deposits 过大的问题,可以尝试给 deposits 分段,分多次赎回,比如:

    function withdrawAll(uint part) external {
        uint256 amount = 0;
        for (uint256 i = part * 100; i < deposits.length && i < (part +1) * 100 ; i++) {
            if (deposits[i].depositor == msg.sender) {
                amount += deposits[i].amount;
                delete deposits[i];
            }
        }

        msg.sender.transfer(amount);
    }

本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

  • 发表于 2020-01-13 16:43
  • 阅读 ( 340 )
  • 学分 ( 34 )
  • 分类:智能合约

0 条评论

请先 登录 后评论
Tiny熊
Tiny熊

布道者

109 篇文章, 9472 学分