【问题标题】:Avoid using solidity's transfer()/send()?避免使用solidity的transfer()/send()?
【发布时间】:2020-10-06 17:22:18
【问题描述】:

我在 2019 年 9 月遇到了这个 article,关于避免使用solidity 的transfer()/send()。以下是文章中的推理:

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

这些合约将会失效,因为它们的后备功能过去消耗的 gas 少于 2300,而现在它们会消耗更多。为什么 2300 gas 很重要?如果通过 Solidity 的 transfer() 或 send() 方法调用合约的后备函数,它就是接收到的 gas 量。 1

自推出以来,安全社区通常推荐 transfer(),因为它有助于防止重入攻击。假设天然气成本不会改变,这一指导是有道理的,但事实证明这个假设是不正确的。我们现在建议避免使用 transfer() 和 send()。

remix中,有一条关于下面代码的警告信息:

  (bool success, ) = recipient.call{value:_amount, gas: _gas}("");

警告:

Low level calls: Use of "call": should be avoided whenever possible. It can lead to unexpected behavior if return value is not handled properly. Please use Direct Calls via specifying the called contract's interface. more

我不是执行智能合约和安全性的 gas 成本方面的专家。因此,我发布了这篇文章,并希望能得到有关它的想法和意见。

【问题讨论】:

  • 那么问题是什么?

标签: ethereum solidity


【解决方案1】:

从 Consensys 文章中,他们说使用 .call() 而不是 .transfer() 和 .send()。唯一的争论是这三个现在都发送了比 2300 更多的气体。从而使重入成为可能。

由此得出的另一个结论是,不管以上所有情况,使用checks-effects-interactions pattern 来防止重入攻击是很重要的。

【讨论】:

    【解决方案2】:

    首先,很高兴了解 Solidity 中的后备功能: 它没有名称,没有参数,没有返回值,可以定义为每个合约一个,但最重要的特点是它在合约上调用不存在的函数时调用,例如send或@ 987654323@ 或call.value()("")。因此,如果您想将 Ether 直接发送到作为合约地址的地址,则会调用目标合约的后备功能。 如果合约的fallback函数没有标记payable,如果合约接收到没有数据的普通以太币就会抛出异常。

    现在让我们看看重入攻击

    contract VulnerableContract {
        mapping(address => uint) public balances;
         
        function deposit() public payable {
            require(msg.value > 1);
            balances[msg.sender] += msg.value;
        }
         
        function withdraw(uint _amount) public {
            require(balances[msg.sender] >= _amount, "Not enough balance!");
            msg.sender.call.value(_amount)("");
            balances[msg.sender] -= _amount;
        }
         
        function getBalance() view public returns(uint) {
            return address(this).balance;
        }
         
        fallback() payable external {}
    }

    VuinerableContract 有一个withdraw 函数,它将以太币发送到调用地址。现在调用地址可能是这样的恶意合约:

     
    contract MaliciousContract {
        VulnerableContract vulnerableContract = VulnerableContract(0x08970FEd061E7747CD9a38d680A601510CB659FB);
         
        function deposit() public payable {
            vulnerableContract.deposit.value(msg.value)();
        }
         
        function withdraw() public {
            vulnerableContract.withdraw(1 ether);
        }
         
        function getBalance() view public returns(uint) {
            return address(this).balance;
        }
         
        fallback () payable external {
            if(address(vulnerableContract).balance > 1 ether) {
                vulnerableContract.withdraw(1 ether);
            }
        }
    }

    当恶意合约调用withdraw函数时,在减少恶意合约的余额之前,会调用它的fallback函数,它可以从易受攻击的合约中窃取更多的Ether。

    因此,通过将回退功能使用的 gas 量限制为 2300 gas,我们可以防止这种攻击。这意味着我们不能再将复杂而昂贵的命令放在回退函数中。

    查看此内容了解更多信息:https://swcregistry.io/docs/SWC-107

    【讨论】:

      猜你喜欢
      • 2022-12-17
      • 2022-07-22
      • 1970-01-01
      • 2021-05-11
      • 2012-04-18
      • 2014-10-17
      • 1970-01-01
      • 2022-11-15
      • 2023-01-15
      相关资源
      最近更新 更多