19. 接收ETH receive() 和 fallback()
Solidity
支持两种特殊的回调函数,receive()
和fallback()
,他们主要在两种情况下被使用:
- 接收ETH
- 处理合约中不存在的函数调用(代理合约proxy contract)
注意⚠️:在solidity 0.6.x版本之前,语法上只有 fallback()
函数,用来接收用户发送的ETH时调用以及在被调用函数签名没有匹配到时,来调用。 0.6版本之后,solidity才将 fallback()
函数拆分成 receive()
和 fallback()
两个函数。
这里主要讲接收ETH的情况。
接收ETH函数:receive() external payable { ... }
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() external payable { ... }
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()
。
其余情况都是触发fallback()
,此时fallback()
必须为payable
。
receive()
和payable fallback()
均不存在的时候,向合约直接发送ETH
将会报错(你仍可以通过带有payable
的函数向合约发送ETH
)。
Remix 演示
- 首先在 Remix 上部署合约 “Fallback.sol”。
- “VALUE” 栏中填入要发送给合约的金额(单位是 Wei),然后点击 “Transact”。
- 可以看到交易成功,并且触发了 “receivedCalled” 事件。
- “VALUE” 栏中填入要发送给合约的金额(单位是 Wei),“CALLDATA” 栏中填入随意编写的
msg.data
,然后点击 “Transact”。 - 可以看到交易成功,并且触发了 “fallbackCalled” 事件。
习题
-
fallback(or receive)函数不能在合约内部调用
-
vitalik想部署一个能接收ETH和msg.data的合约,那么他部署的合约中必须含有fallback函数
-
假设存在如下合约,现在vitalik向该合约发起一笔低级交互,value=100Wei,msg.data=0xaa,如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LVIaPjuZ-1680009419173)(null)]
则会报错:
error:'Fallback' function is not defined,value和msg.data均发送失败
-
假设存在如下合约,现在vitalik想调用该合约中不存在的函数,他在calldata中输入函数选择器,并将value设置为1ETH,如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lAFsmYrn-1680009419189)(null)]
则会报错:
error:'Fallback' function is not defined,value和msg.data均发送失败