一,接收ETH
solidity里支持两种特殊的回调函数,receive()和fallback(),他们主要在接收ETH和处理合约中不存在的函数调用。在solidity0.6版本之前,语法只有fallback()函数,用来接收用户发送的ETH时调用以及在被调用函数签名没有匹配到时,来调用。在0.6版本后,solidity才将fallback()函数拆分成receive()和fallback()两个函数。
receive()只用于处理接收ETH。一个合约最多有一个receive()函数,声明方式与一般函数不一样,不需要function关键词:receive() external payable{ }。receive()函数不能有任何的参数,不能返回任何值,必须包含external和payable。当合约接收ETH的时候,receive()会被触发。receive()最好不要执行太多的逻辑因为如果别人用send和transfer方法发送ETH时,gas会被限制,receive()太复杂可能会触发out of gas报错。如果用call就可以自定义gas执行更复杂的逻辑。我们可以在receive()里发送一个event,如:
// 定义事件
event Received(address Sender, uint Value);
// 接收ETH时释放Received事件
receive() external payable {
emit Received(msg.sender, msg.value);
}
有些恶意合约,会在receive()
函数(老版本的话,就是 fallback()
函数)嵌入恶意消耗gas
的内容或者使得执行故意失败的代码,导致一些包含退款和转账逻辑的合约不能正常工作,因此写包含退款等逻辑的合约时候,一定要注意这种情况。
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
我们将实现三种方法实现向ReceiveETH合约发送ETH。首先,先在发送ETH合约SendETH中实现payable的构造函数和receive(),让我们能够在部署时和部署后向和部署后向合约转帐。
contract SendETH {
// 构造函数,payable使得部署的时候可以转eth进去
constructor() payable{}
// receive方法,接收eth时被触发
receive() external payable{}
}
transfer
- 用法是
接收方地址.transfer(发送ETH数额)
。 transfer()
的gas
限制是2300
,足够用于转账,但对方合约的fallback()
或receive()
函数不能实现太复杂的逻辑。transfer()
如果转账失败,会自动revert
。
代码样例,注意里面的_to
填ReceiveETH
合约的地址,amount
是ETH
转账金额:
// 用transfer()发送ETH
function transferETH(address payable _to, uint256 amount) external payable{
_to.transfer(amount);
}
部署SendETH
合约后,对ReceiveETH
合约发送ETH,此时amount
为10,value
为0,amount
>value
,转账失败,发生revert。
send
- 用法是
接收方地址.send(发送ETH数额)
。 send()
的gas
限制是2300
,足够用于转账,但对方合约的fallback()
或receive()
函数不能实现太复杂的逻辑。send()
如果转账失败,不会revert
。send()
的返回值是bool
,代表着转账成功或失败,需要额外代码处理一下。
代码样例:
// send()发送ETH
function sendETH(address payable _to, uint256 amount) external payable{
// 处理下send的返回值,如果失败,revert交易并发送error
bool success = _to.send(amount);
if(!success){
revert SendFailed();
}
}
对ReceiveETH
合约发送ETH,此时amount
为10,value
为0,amount
>value
,转账失败,因为经过处理,所以发生revert
。
call
- 用法是
接收方地址.call{value: 发送ETH数额}("")
。 call()
没有gas
限制,可以支持对方合约fallback()
或receive()
函数实现复杂逻辑。call()
如果转账失败,不会revert
。call()
的返回值是(bool, data)
,其中bool
代表着转账成功或失败,需要额外代码处理一下。
// call()发送ETH
function callETH(address payable _to, uint256 amount) external payable{
// 处理下call的返回值,如果失败,revert交易并发送error
(bool success,) = _to.call{value: amount}("");
if(!success){
revert CallFailed();
}
}
我们介绍solidity
三种发送ETH
的方法:transfer
,send
和call
。
call
没有gas
限制,最为灵活,是最提倡的方法;transfer
有2300 gas
限制,但是发送失败会自动revert
交易,是次优选择;send
有2300 gas
限制,而且发送失败不会自动revert
交易,几乎没有人用它。