避免使用solidity的transfer/ send?

问题描述

我在2019/9年这个article碰到过避免使用实体transfer()/send()的情况。这是文章的原因:

It looks like EIP 1884 is headed our way in the Istanbul hard fork. This change increases the gas cost of the SLOAD operation and therefore breaks some existing smart contracts.

Those contracts will break because their fallback functions used to consume less than 2300 gas,and they’ll Now consume more. Why is 2300 gas significant? It’s the amount of gas a contract’s fallback function receives if it’s called via solidity’s transfer() or send() methods. 1

Since its introduction,transfer() has typically been recommended by the security community because it helps guard against reentrancy attacks. This guidance made sense under the assumption that gas costs wouldn’t change,but that assumption turned out to be incorrect. We Now recommend that transfer() and send() be avoided.

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

我不是执行智能合约和安全性方面的天然气成本专家。因此,我发布了这篇文章,希望能对此发表想法和评论

解决方法

在 Consensys 文章中,他们说使用 .call() 而不是 .transfer() 和 .send()。唯一的争论是,这三个现在都发送了比 2300 多的 gas。因此可以重入。

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

,

首先,很高兴了解Solidity中的后备功能: 它没有名称,没有参数,没有返回值,可以将其定义为每个合约,但是最重要的功能是在合约上调用不存在的函数(例如send)时调用它或transfercall.value()("")。因此,如果您想直接将Ether发送到合同地址,则将调用目标合同的后备功能。 如果该合同的后备功能未标记为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函数,该函数将Ether发送到呼叫地址。现在,呼叫地址可能是诸如以下这样的恶意合同:

 
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);
        }
    }
}

当恶意合约调用撤回功能时,在减少恶意合约的余额之前,将调用它的后备功能,因为它可以从易受攻击的合约中窃取更多的以太币。

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

有关更多信息,请查看此:https://swcregistry.io/docs/SWC-107