Solidity支持两种特殊的回调函数,receive()和fallback(),来接收 ETH,或者使用 payable关键字放入函数中来接收 ETH。今天主要说一说receive()和fallback()在两种情况下的使用:
- 接收ETH
- 处理合约中不存在的函数调用(代理合约proxy contract)
注意⚠️:在solidity 0.6.x版本之前,语法上只有 fallback() 函数,用来接收用户发送的ETH时调用以及在被调用函数签名没有匹配到时,来调用。 0.6版本之后,solidity才将 fallback() 函数拆分成 receive() 和 fallback() 两个函数。
接收ETH函数 receive
receive()只用于处理接收ETH。一个合约最多有一个receive()函数,声明方式与一般函数不一样,不需要function关键字:receive() external payable { ... }。receive()函数不能有任何的参数,不能返回任何值,必须包含external和payable。
当合约接收ETH的时候,receive()会被触发。receive()最好不要执行太多的逻辑因为如果别人用send和transfer方法发送ETH的话,gas会限制在2300,receive()太复杂可能会触发Out of Gas报错;如果用call就可以自定义gas执行更复杂的逻辑(这三种发送ETH的方法我们之后会讲到)。
我们可以在receive()里发送一个event,例如:
// 定义事件
event Received(address Sender, uint Value);
// 接收ETH时释放Received事件
receive() external payable {
emit Received(msg.sender, msg.value);
}
有些恶意合约,会在receive() 函数(老版本的话,就是 fallback() 函数)嵌入恶意消耗gas的内容或者使得执行故意失败的代码,导致一些包含退款和转账逻辑的合约不能正常工作,因此写包含退款等逻辑的合约时候,一定要注意这种情况。
回退函数 fallback
fallback()函数会在调用合约不存在的函数时被触发。可用于接收ETH,也可以用于代理合约proxy contract。fallback()声明时不需要function关键字,必须由external修饰,一般也会用payable修饰,用于接收ETH:fallback() external payable { ... }。
我们定义一个fallback()函数,被触发时候会释放fallbackCalled事件,并输出msg.sender,msg.value和msg.data:
// fallback
fallback() external payable{
emit fallbackCalled(msg.sender, msg.value, msg.data);
}
receive和fallback的区别
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)。
Remix 演示
- 首先在 Remix 上部署合约 "Fallback.sol"。
- "VALUE" 栏中填入要发送给合约的金额(单位是 Wei),然后点击 "Transact"。(这里Transact左边的输入框就是
msg.data,这里 我们不填东西)
3.可以看到交易成功,并且触发了 "receivedCalled" 事件。
4."VALUE" 栏中填入要发送给合约的金额(单位是 Wei),"CALLDATA" 栏中填入随意编写的msg.data,然后点击 "Transact"。
5.可以看到交易成功,并且触发了 "fallbackCalled" 事件。
总结
receive 的使用场景
- 当你想让你的合约能够接收以太币转账时,可以使用
receive函数。这样,任何发送到合约地址的以太币转账都会自动调用该函数。 - 当你希望合约在接收到以太币时执行某些操作时(例如更新状态变量、触发其他事件等),可以在
receive函数中添加相应的代码。(但不能太复杂,会消耗 gas ,超过2300后就会触发Out of Gas的报错)
fallback 的使用场景
- 处理无法识别的函数调用:如果你的合约不支持某个特定的函数调用,但你想在收到这样的调用时执行一些操作(例如记录日志、退回以太币等),可以在
fallback函数中添加相应的代码。 - 接收带有数据的以太币转账:如果发送到合约的交易不仅包含以太币转账,还包含一些数据(例如,使用合约地址作为ERC20代币的转账目标),这些数据将作为
fallback函数的参数。你可以在这个函数中处理这些数据。 - 安全考虑:由于
fallback函数可以处理所有无法识别的函数调用和数据,因此它也可能成为潜在的攻击点。因此,在设计fallback函数时要特别小心,确保它不会执行任何可能导致合约状态不安全或允许恶意用户执行恶意操作的操作。