两种特殊的回调函数receive()
和fallback()
,他们主要在两种情况下被使用:
- 接收ETH
- 处理合约中不存在的函数调用(代理合约proxy contract)
在合约收到ETH
转账时被调用的函数。一个合约最多有一个receive()
函数,不需要function
关键字:receive() external payable { ... }
。receive()
函数不能有任何的参数,不能返回任何值,必须包含external
和payable
。
注:receive()
最好不要执行太多的逻辑因为如果别人用send
和transfer
方法发送ETH
的话,gas
会限制在2300
,receive()
太复杂可能会触发Out of Gas
报错;如果用call
就可以自定义gas
执行更复杂的逻辑
fallback()
函数会在调用合约不存在的函数时被触发。可用于接收ETH,也可以用于代理合约proxy contract
。
receive
和fallback
都能够用于接收ETH
,他们触发的规则如下:
触发fallback() 还是 receive()?
接收ETH
|
msg.data是空?
/ \
是 否
/ \
receive()存在? fallback()
/ \
是 否
/ \
receive() fallback()
简单来说,合约接收ETH
时,msg.data
为空且存在receive()
时,会触发receive()
;msg.data
不为空或不存在receive()
时,会触发fallback()
,此时fallback()
必须为payable
。
receive()
和payable fallback()
均不存在的时候,向合约直接发送ETH
将会报错(你仍可以通过带有payable
的函数向合约发送ETH
)。
有三种方法向其他合约发送ETH
:transfer()
,send()
和call()
,其中call()
是被鼓励的用法(因为)transfer()
,send()
都有限制2300gas,call()
没有gas限制。
- 用法是
接收方地址.transfer(发送ETH数额)
。 transfer()
的gas
限制是2300
,足够用于转账,但对方合约的fallback()
或receive()
函数不能实现太复杂的逻辑。transfer()
如果转账失败,会自动revert
(回滚交易)。
// 用transfer()发送ETH
function transferETH(address payable _to, uint256 amount) external payable{
_to.transfer(amount);
}
- 用法是
接收方地址.send(发送ETH数额)
。 send()
的gas
限制是2300
,足够用于转账,但对方合约的fallback()
或receive()
函数不能实现太复杂的逻辑。send()
如果转账失败,不会revert
。send()
的返回值是bool
,代表着转账成功或失败,需要额外代码处理一下。
error SendFailed(); // 用send发送ETH失败error
// send()发送ETH
function sendETH(address payable _to, uint256 amount) external payable{
// 处理下send的返回值,如果失败,revert交易并发送error
bool success = _to.send(amount);
if(!success){
revert SendFailed();
}
}
- 用法是
接收方地址.call{value: 发送ETH数额}("")
。 call()
没有gas
限制,可以支持对方合约fallback()
或receive()
函数实现复杂逻辑。call()
如果转账失败,不会revert
。call()
的返回值是(bool, bytes)
,其中bool
代表着转账成功或失败,需要额外代码处理一下。
error CallFailed(); // 用call发送ETH失败error
// call()发送ETH
function callETH(address payable _to, uint256 amount) external payable{
// 处理下call的返回值,如果失败,revert交易并发送error
(bool success,) = _to.call{value: amount}("");
if(!success){
revert CallFailed();
}
}
- transfer和send都有2300gas限制,call没有限制(推荐使用)
- send和call 如果转账失败都需要手动
revert
,transfer不用