为何使用send()和transfer()无法向其他合约发送ETH?
核心原因
send()和transfer()方法仅会发送2300固定gas,而你的Receiver合约receive()函数中执行了payable(receiver).transfer(msg.value)操作,该操作消耗的gas远超2300,导致调用失败;call()默认会分配当前可用的全部gas,足以支撑该操作,因此成功。
详细解释
Solidity设计时为防范重入攻击,给send()和transfer()设置了2300 gas的上限,这个gas量仅能完成最基础的EVM操作(比如简单的状态变量修改)。但你的receive()函数里的转账操作本身需要消耗额外gas(包括修改账户余额、触发接收方的receive/fallback函数等),2300gas不足以覆盖这些消耗,所以交易回滚。
而call{value: msg.value}("")没有固定gas限制(除非手动指定),会使用当前交易剩余的全部gas,足够支撑receive()函数内的转账逻辑,因此能成功执行。
修复方案
如果要保留send()/transfer()的使用,需要修改Receiver合约的receive()函数,避免在其中执行高gas消耗的操作,或者将内部转账改用call()并指定足够gas:
修改后的Receiver合约:
pragma solidity ^0.8.0; contract Receiver { address receiver; uint public prize; address public owner; constructor() payable { receiver = msg.sender; owner = msg.sender; prize = 0.001 ether; } receive() external payable { require(msg.value >= prize || msg.sender == owner); // 改用call并指定足够gas,避免gas不足 (bool success, ) = payable(receiver).call{value: msg.value, gas: 200000}(""); require(success, "Internal transfer failed"); receiver = msg.sender; prize = msg.value; } }
最佳实践
当前Solidity官方推荐优先使用call()进行ETH转账,因为它更灵活,可以自定义gas量,也不会因为固定gas限制导致合法操作失败。
原问题背景
我刚学习Solidity,对ETH的发送方法存在疑问。编写了两个Solidity智能合约,通过Remix IDE(环境为Remix VM(Merge))部署,尝试以send()、transfer()、call()三种方式从Sender合约向Receiver合约发送1 ETH,结果仅call()方法成功,send()和transfer()均失败。
原合约代码:
pragma solidity ^0.8.0; contract Receiver { address receiver; uint public prize; address public owner; constructor() payable { receiver = msg.sender; owner = msg.sender; prize = 0.001 ether; } receive() external payable { require(msg.value >= prize || msg.sender == owner); payable(receiver).transfer(msg.value); receiver = msg.sender; prize = msg.value; } } contract Sender { constructor() payable{ } function sendWithSend(address payable _to) public payable{ bool success = _to.send(msg.value); require(success, "failed"); } function sendWithTransfer(address payable _to) public payable{ _to.transfer(msg.value); } function sendWithCall(address payable _to) public payable{ (bool sent, ) = _to.call{value: msg.value}(""); require(sent, "Failed" ); } }
尝试用每个函数发送1 ETH,预期Receiver合约最终收到3 ETH,但实际仅收到1 ETH,send()和transfer()多次尝试仍无法成功。
内容的提问来源于stack exchange,提问作者pwnstar

