回退函数 「在 0.6 版本之后失效,变成 receive和fallback」
- 无名称
- 无参数
- 无返回值
- 一个合约只能有一个回退函数
- 当给合约转以太币的时候,需要
payable
回退函数
- 如果调用合约没有匹配上任何函数,就会调用回退函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| pragma solidity ^0.4.18;
contract Test{
uint public x;
function() public payable{ x = 1; } }
contract Call{
constructor() public payable{}
function() public payable{ }
function tansferTest(address addr) public { addr.transfer(1 ether); } }
|
上面的函数在执行 call.transferTest
的时候会失败。
因为,回退函数在接收以太币的时候,仅有 2300 gas
来执行,下面的操作超过 2300
- 写存储
- 创建合约
- 执行一个外部函数调用
- 发送
ether
receive()和fallback(),他们主要在两种情况下被使用:
- 接收ETH
- 处理合约中不存在的函数调用(代理合约
proxy contract
)
注意:在 solidity 0.6.x
版本之前,语法上只有 fallback()
函数,用来接收用户发送的 ETH
时调用以及在被调用函数签名没有匹配到时,来调用。
0.6
版本之后,solidity
才将 fallback()
函数拆分成 receive()
和 fallback()
两个函数。
我们这一讲主要讲接收 ETH
的情况。
接收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
执行更复杂的逻辑。
我们可以在 receive()
里发送一个 event
,例如:
1 2 3 4 5 6
| event Received(address Sender, uint Value);
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
:
1 2 3 4
| fallback() external payable{ emit fallbackCalled(msg.sender, msg.value, msg.data); }
|
receive和fallback的区别
receive
和 fallback
都能够用于接收ETH
,他们触发的规则如下:
1 2 3 4 5 6 7 8 9 10 11 12
| 触发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)。
ps: 2023-2-24
,如果 msg.data
为空,且 receive
不存在,此时,只有 fallback
会报错,而不是图中所示。所以,receive
和 fallback
都需要实现。
案例
成功 「receive」
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| pragma solidity ^0.8.0;
contract test{ constructor() payable{
}
function transferEth(address payable T) public{ T.transfer(100); }
function getBlanace() public view returns(uint256){ return address(this).balance; } }
contract callfunction { event reveiveCalled(address); event fallbackCalled(address,uint256,bytes);
receive() external payable{ emit reveiveCalled(msg.sender); }
fallback() external payable{ emit fallbackCalled(msg.sender, msg.value, msg.data); }
function getBlanace() public view returns(uint256){ return address(this).balance; } }
|
由于 remix
自带的链上操作,没有转账功能,所以,创建了一个合约进行转账。
- 先部署
test
「传入 1ETH
」
- 再部署
callfunction
- 执行
callfunction
的 getBlanace
- 执行
test
的 transferEth
(callfunction
的地址)
- 执行
callfunction
的 getBlanace
100
log
输出
- 说明执行的是
receive()
log
输出
1 2 3 4 5 6 7 8 9 10
| [ { "from": "0xb27A31f1b0AF2946B7F582768f03239b1eC07c2c", "topic": "0x794ae4db4fa93171e957bf40514fe3fd673f9c8a166e172f0ffb677b53b55648", "event": "reveiveCalled", "args": { "0": "0xcD6a42782d230D7c13A74ddec5dD140e55499Df9" } } ]
|
如果想要回退 ETH
可以使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| pragma solidity ^0.8.0;
contract test{ constructor() payable{
}
function transferEth(address payable T) public{ T.transfer(100); }
function getBlanace() public view returns(uint256){ return address(this).balance; } }
contract callfunction {
event reveiveCalled(address); event fallbackCalled(address,uint256,bytes);
receive() external payable{ emit reveiveCalled(msg.sender); revert(); }
fallback() external payable{ emit fallbackCalled(msg.sender, msg.value, msg.data); }
function getBlanace() public view returns(uint256){ return address(this).balance; } }
|
这样就不会向 callfunction
转移 ETH
了。
失败 「receive」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| pragma solidity ^0.8.0;
contract test{ constructor() payable{
}
function transferEth(address payable T) public{ T.transfer(100); }
function getBlanace() public view returns(uint256){ return address(this).balance; } }
contract callfunction {
uint256 x;
event reveiveCalled(address); event fallbackCalled(address,uint256,bytes);
receive() external payable{ x = 200; emit reveiveCalled(msg.sender); }
fallback() external payable{ emit fallbackCalled(msg.sender, msg.value, msg.data); }
function getBlanace() public view returns(uint256){ return address(this).balance; } }
|
由于赋予 x
,gas
超过了 2300
,所以,receive
会失败。
失败后
成功 「fallback」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| pragma solidity ^0.8.0;
contract test{ constructor() payable{
}
function transferEth(address payable T) public{ T.transfer(100); }
function getBlanace() public view returns(uint256){ return address(this).balance; } }
contract callfunction {
event fallbackCalled(address,uint256,bytes);
fallback() external payable{ emit fallbackCalled(msg.sender, msg.value, msg.data); }
function getBlanace() public view returns(uint256){ return address(this).balance; } }
|
执行操作如下
ps: msg.data
不是 inputs
log
输出
1 2 3 4 5 6 7 8 9 10 11 12
| [ { "from": "0x9396B453Fad71816cA9f152Ae785276a1D578492", "topic": "0x7bf8121d238f1338d4842c396807cbe93f5a2396e2b3cad1639735997867b1f5", "event": "fallbackCalled", "args": { "0": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", "1": "19", "2": "0xabcd" } } ]
|