Solidity语言中关于回退函数的定义:
- 回退函数是一个不接受任何参数也不返回任何值的特殊函数;
- 如果在对合约的调用中,没有其它函数与给定的函数标识符匹配时,回退函数会被调用;
- 每当合约接收到以太币,且没有
receive
函数时,回退函数会被调用; - 一个合约中最多可以有一个回退函数。
Receive函数
Receive是一个接收以太币函数,一个合约中最多可以有一个 receive
函数。在对合约转账时会执行 receive
函数,例如通过 transfer()
、send()
或 call()
。如果 receive
函数不存在,那么 fallback
回退函数会被调用。receive
函数的声明语法如下:
receive () external payable { ... }
Receive函数没有 function
关键字,没有参数也没有返回值,且必须是 external
可见性(允许外部合约调用)并具有 payable
可支付属性。
Fallback函数
回退函数的声明语法如下:
fallback () external [payable]
其中:
- 回退函数没有
function
关键字; - 回退函数必须是
external
可见性,即允许被外部合约调用; - 如果回退函数需要接收以太币,则必须标记为
payable
关键字。
Fallback函数与Receive函数的区别是:Receive函数只在合约转账时调用,而Fallback函数除了可以在合约转账时调用外,在合约没有函数匹配或需要向合约发送附加数据时,也调用Fallback函数。
合约例子
下面是一个合约例子,用来演示回退函数的声明与用法。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
// 回退函数调用合约
contract Test {
uint public x;
// 发送到这个合约的所有消息都会调用此函数(因为该合约没有其它函数)。
// 向这个合约发送以太币会导致异常,因为 fallback 函数没有 `payable` 修饰符
fallback () external { x = 1; }
}
// 这个合约会保留所有发给他的以太币,无法返还
contract TestPayable {
uint public x;
uint public y;
// receive函数
// 纯转账调用这个函数
receive () external payable { x = 1; y = msg.value; }
// fallback函数
// 除纯转账外所有调用都调用这个函数
fallback () external payable { x = 2; y = msg.value; }
// 取合约余额
function getBalance() public view returns(uint) {
return address(this).balance;
}
}
// 调用合约
contract Caller {
// call Test
function callTest(Test test) public {
// 函数调用
(bool success, ) = address(test).call(abi.encodeWithSignature("nonExistingFunction()"));
require(success);
// 以太币转账(交易会失败)
payable(address(test)).transfer(1 ether);
}
// call TestPayable
function callTestPayable(TestPayable test) public payable {
bool success;
// 以太币转账 test.x = 1, test.y = 1
(success, ) = payable(address(test)).call{value: msg.value}("");
require(success);
// 函数调用 test.x = 2, test.y = 0
(success, ) = address(test).call(abi.encodeWithSignature("nonExistingFunction()"));
require(success);
// 以太币转账+函数调用 test.x = 2, test.y = 1
(success, ) = address(test).call{value: msg.value}(abi.encodeWithSignature("nonExistingFunction()"));
require(success);
}
}
合约执行
我们在Remix中编译、部署和运行这个合约例子。
1. 执行callTest()函数:
调用一个不存在的函数,输出结果如下图:
2. 执行callTestPayable()函数:
调用以太币转账,输出结果如下图:
调用一个不存在的函数,输出结果如下图:
调用一个不存在的函数并附加以太币转账,输出结果如下图: