http://www.www.tnmanning.com

停止使用Solidity的transfer()函数


这两份智能合约除了gas的传输数量差异,其余都是相等的。



操纵价值与资源耗损(CPU时间、内存等)之间的不服衡有几个缺点:
 6        (bool success, ) = msg.sender.call.value(amount)("");
 8        require(success, "Transfer failed.");
 5
 5        uint256 amount = balanceOf[msg.sender];
Vyper如何?
        (bool success, ) = msg.sender.call.value(amount)("");
防备重入的另一种要领是明晰查抄和拒绝此类挪用。这是一个简朴版本的reentrancy 掩护,你可以看到这个想法:
 6        balanceOf[msg.sender] = 0;
 4    function withdraw() external {

contract Fixed {
这些合约将割裂,因为它们的fallback函数已往耗损的gas少于2300,此刻它们将耗损更多。为什么2300gas是重要的? 假如通过Solidity的transfer()或send()要领挪用合约的fallback函数,则它是合约的fallback函数。
 2    ...

    }


 7        require(!locked, "Reentrant call detected!");

但愿您在看到上述代码时首先思量过这个问题。引入transfer()和send()的全部原因是为了办理DAO污名昭着的黑客进攻的原因。 其思想是2300 gas足以发出一个日志条目,但不敷以发出一个可重入挪用,然后修改存储。
 3
 9        ...
我们的发起是遏制在代码中利用transfer()和send(),改为利用call():
Vyper的send()函数利用与Solidity的transfer()沟通的硬编码gas,因此也应制止利用。你可以改用raw_call。

自从transfer()引入以来,它凡是被安详社区推荐,因为它有助于防备重入进攻。在gas本钱稳定的假设下,这一指导意见是有意义的,但事实证明这一假设是错误的。我们此刻发起制止transfer()和send()。
 7        require(success, "Transfer failed.");
    function withdraw(uint256 amount) external {
11    }

5. vyper的send()也有同样的问题。 要害词:

假如msg.sender是智能合约,它在第6行有时机在第7行产生之前再次挪用withdraw()。在第二次挪用中,balanceOf [msg.sender]仍然是原始金额,因此将再次传输。这可以按照需要反复多次以耗损智能合约。
消除重入错误的最简朴要领是利用查抄-结果-交互模式。这是一个可重入错误的典规范子:
sload向来订价过低,而eip 1884更正了这一点。
 6    function withdraw() external {
        msg.sender.transfer(amount);
假如gas本钱会产生变革,那么智能合约就不能依赖于任何特定的gas本钱。
请留意,此要领仅在显式将其应用于所有正确的函数时才掩护您。由于需要在储存中保持必然的代价,这也增加了gas本钱。
 8        locked = true;
 2    ...
 7        (bool success, ) = msg.sender.call.value(amount)("");
在OpenZeppelin的ReentrancyGuard条约中可以找到一个更巨大、更省gas的版本。假如从ReentrancyGuard担任,则只需利用nonReentrant修饰函数以防备重入。
· 它可以用于进攻,通过填充区块与订价过低的操纵,导致区块处理惩罚时间过长。


 1contract Vulnerable {
查抄 - 结果 - 交互模式的想法是确保所有交互(外部挪用)最终产生。上述代码的典范修复要领如下:
1. 在假定gas本钱稳定的环境下,推荐transfer()是有意义的。
总结
contract Vulnerable {
 3

利用此代码,假如实验重入挪用,第7行上的require将拒绝它,因为lock仍配置为true。
 3
Vyper内置了一个@nonreentrant(<unique_key>)装饰器,其事情方法与OpenZeppelin的ReentrancyGuard雷同。



· 订价过低的操纵码会导致区块gas限值倾斜,有时区块气体快速完成,但其他雷同gas利用的块体迟钝完成。
 4    function withdraw() external {

        // This forwards 2300 gas, which may not be enough if the recipient

以太坊看起来EIP 1884正在伊斯坦布尔硬叉前进。这一变革增加了sload操纵的gas本钱,因此冲破了一些现有的智能合约。

        require(success, "Transfer failed.");



4. 这就带来了从头进入的风险。请确保利用可用于防备重入裂痕的结实要领之一。

12}


        // This forwards all available gas. Be sure to check the return value!
        // is a contract and gas costs change.
}
10        locked = false;
利用Reentrancy 掩护
气体本钱可以也将改变
}
    }

10}

 1contract Guarded {
 9    }
evm支持的每个操纵码都有一个相关的gas本钱。譬喻,sload从存储器中读取一个单词,今朝(但不是很长时间)需要200气体。gas的价值不是随意的。它们旨在反应组成以太坊的节点上每个操纵所耗损的底层资源。

EIP的念头部门:
任何利用transfer()或send()的智能合约城市通过转发牢靠gas数量2300来严格依赖gas本钱。
 1contract Fixed {
 9    }
 4    bool locked = false;

可重入性?(Reentrancy )
· 假如操纵均衡,我们可以最大限度地提高区块气限,并有一个更不变的处理惩罚时间。
查抄 - 结果 - 交互模式
 2    ...
 8        balanceOf[msg.sender] = 0;
 5        uint256 amount = balanceOf[msg.sender];
假如我们不再利用transfer()和send(),我们将不得不以更结实的方法防备重入。幸运的是,这个问题有很好的办理方案。

请留意,在此代码中,余额在传输之前被清零,因此实验对withdraw()举办可重入挪用将不会使进攻者受益。



2. gas本钱并不恒定。智能条约对这个事实应该是健全的。Solidity的transfer()和send()利用硬编码的gas量。
    function withdraw(uint256 amount) external {
不外,请记着,gas本钱大概会产生变革,这意味着无论如何,这是办理重入问题的糟糕要领。本年早些时候,君士坦丁堡叉子被推迟了,因为低落gas本钱导致先前安详的代码无法再进入。


3. 应该制止这些要领。请改用.call.value(...)(“”)。

智能合约不能依赖气体本钱
10}

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。