网络知识 娱乐 以太坊Solidity语言的Receive函数和Fallback回退函数详解

以太坊Solidity语言的Receive函数和Fallback回退函数详解

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()函数:

在这里插入图片描述

调用以太币转账,输出结果如下图:
在这里插入图片描述

调用一个不存在的函数,输出结果如下图:
在这里插入图片描述

调用一个不存在的函数并附加以太币转账,输出结果如下图:
在这里插入图片描述