区块链研究实验室|停止使用Solidity的transfer()函数

  • 时间:
  • 浏览:66
  • 来源:区块链技术网

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

这些合约将破裂,因为它们的fallback函数过去消耗的gas少于2300,现在它们将消耗更多。为什么2300gas是重要的? 如果通过Solidity的transfer()或send()方法调用合约的fallback函数,则它是合约的fallback函数。

自从transfer()引入以来,它通常被安全社区推荐,因为它有助于防止重入攻击。在gas成本不变的假设下,这一指导意见是有意义的,但事实证明这一假设是错误的。我们现在建议避免transfer()和send()。

气体成本可以也将改变

evm支持的每个操作码都有一个相关的gas成本。例如,sload从存储器中读取一个单词,目前(但不是很长时间)需要200气体。gas的价格不是随意的。它们旨在反映构成以太坊的节点上每个操作所消耗的底层资源。

EIP的动机部分:

 

操作价格与资源消耗(CPU时间、内存等)之间的不平衡有几个缺点:

  • 它可以用于攻击,通过填充区块与定价过低的操作,导致区块处理时间过长。

  • 定价过低的操作码会导致区块gas限值倾斜,有时区块气体快速完成,但其他类似gas使用的块体缓慢完成。

  • 如果操作平衡,我们可以最大限度地提高区块气限,并有一个更稳定的处理时间。

sload历来定价过低,而eip 1884纠正了这一点。

智能合约不能依赖气体成本

如果gas成本会发生变化,那么智能合约就不能依赖于任何特定的gas成本。

任何使用transfer()或send()的智能合约都会通过转发固定gas数量2300来严格依赖gas成本。

我们的建议是停止在代码中使用transfer()和send(),改为使用call():

contract Vulnerable {
    function withdraw(uint256 amount) external {
        // This forwards 2300 gas, which may not be enough if the recipient
        // is a contract and gas costs change.
        msg.sender.transfer(amount);
    }
}

contract Fixed {
    function withdraw(uint256 amount) external {
        // This forwards all available gas. Be sure to check the return value!
        (bool success, ) = msg.sender.call.value(amount)("");
        require(success, "Transfer failed.");
    }
}

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

可重入性?(Reentrancy )

希望您在看到上述代码时首先考虑过这个问题。引入transfer()和send()的全部原因是为了解决DAO臭名昭着的黑客攻击的原因。 其思想是2300 gas足以发出一个日志条目,但不足以发出一个可重入调用,然后修改存储。

不过,请记住,gas成本可能会发生变化,这意味着无论如何,这是解决重入问题的糟糕方法。今年早些时候,君士坦丁堡叉子被推迟了,因为降低gas成本导致先前安全的代码无法再进入。

如果我们不再使用transfer()和send(),我们将不得不以更健壮的方式防止重入。幸运的是,这个问题有很好的解决方案。

检查 - 效果 - 交互模式

消除重入错误的最简单方法是使用检查-效果-交互模式。这是一个可重入错误的典型例子:

1contract Vulnerable {
 2    ...
 3
 4    function withdraw() external {
 5        uint256 amount = balanceOf[msg.sender];
 6        (bool success, ) = msg.sender.call.value(amount)("");
 7        require(success, "Transfer failed.");
 8        balanceOf[msg.sender] = 0;
 9    }
10}

如果msg.sender是智能合约,它在第6行有机会在第7行发生之前再次调用withdraw()。在第二次调用中,balanceOf [msg.sender]仍然是原始金额,因此将再次传输。这可以根据需要重复多次以消耗智能合约。

检查 - 效果 - 交互模式的想法是确保所有交互(外部调用)最终发生。上述代码的典型修复方法如下:

 1contract Fixed {
 2    ...
 3
 4    function withdraw() external {
 5        uint256 amount = balanceOf[msg.sender];
 6        balanceOf[msg.sender] = 0;
 7        (bool success, ) = msg.sender.call.value(amount)("");
 8        require(success, "Transfer failed.");
 9    }
10}

请注意,在此代码中,余额在传输之前被清零,因此尝试对withdraw()进行可重入调用将不会使攻击者受益。

使用Reentrancy 保护

防止重入的另一种方法是明确检查和拒绝此类调用。这是一个简单版本的reentrancy 保护,你可以看到这个想法:

1contract Guarded {
 2    ...
 3
 4    bool locked = false;
 5
 6    function withdraw() external {
 7        require(!locked, "Reentrant call detected!");
 8        locked = true;
 9        ...
10        locked = false;
11    }
12}

使用此代码,如果尝试重入调用,第7行上的require将拒绝它,因为lock仍设置为true。

在OpenZeppelin的ReentrancyGuard合同中可以找到一个更复杂、更省gas的版本。如果从ReentrancyGuard继承,则只需使用nonReentrant修饰函数以防止重入。

请注意,此方法仅在显式将其应用于所有正确的函数时才保护您。由于需要在储存中保持一定的价值,这也增加了gas成本。

Vyper如何?

Vyper的send()函数使用与Solidity的transfer()相同的硬编码gas,因此也应避免使用。你可以改用raw_call。

Vyper内置了一个@nonreentrant()装饰器,其工作方式与OpenZeppelin的ReentrancyGuard类似。

总结

  1. 在假定gas成本不变的情况下,推荐transfer()是有意义的。

  2. gas成本并不恒定。智能合同对这个事实应该是健全的。Solidity的transfer()和send()使用硬编码的gas量。

  3. 应该避免这些方法。请改用.call.value(...)(“”)。

  4. 这就带来了重新进入的风险。请确保使用可用于防止重入漏洞的健壮方法之一。

  5. vyper的send()也有同样的问题。

猜你喜欢

如何在本地以太坊测试网络hardhat中使用pancakeswap?

笔者将pancake前端工程pancake-frontend[4]配置成本地的hardhat[5]测试网环境,方便大家一起学习

2022-01-11

大幅度提高ETH生态隐私性的思考

目前以太坊生态上混币业务主要是依赖于tornado.cash这种混币合约,但tornado.cash有一个大问题,但凡参与过混币的地址都是全网公开的。你只要参与混币,就可以给你的输入和输出地址都打上一个标签。虽然大家不知道具体的输入和输出地址的对应关系,但只要将全网所有和tornado.cash交互过的地址全部打上混币的标签,这也是对隐私性的极大威胁。

2022-01-10

一文说透以太坊上TVL最大的二层网络:Arbitrum

截至今日最新数据统计,以太坊扩容网络Arbitrum作为以太坊上TVL最大的二层网络,锁仓价值达到2.74亿美元。且在去年九月,Arbitrum宣布已部署以太坊主网测试Arbitrum One,超过250个团队申请接入开发者测试网。

2022-01-06

BTC、ETH、LTC的最小单位是什么?

比特币的创始人名为中本聪(Satoshi Nakamoto),比特币的最小单位是聪(Satoshi,简称 SAT)。1 个比特币等于 1 亿聪,这个单位换算是 BitcoinTalk 论坛上的用户们在 2011 年讨论后商定下来的。

2022-01-06

区块链红利吃饱后,这个巨头又想"征服"元宇宙?

据12月26日消息,百度与英伟达(NVIDIA)已达成协议,双方合作共建AI元宇宙。另外,在今日举行的百度AI开发者大会上,英伟达全球副总裁暨亚太区总裁 Raymond Teh将受邀出席,并发表主题演讲。

2021-12-27