Contents

Solidity中的fallback和receive函数

Receiver

1
2
3
receive() external payable {
 	//...
}

基本作用

让合约地址可以以纯转账的方式接收以太, 直接当向合约地址发送以太的时候, receive函数将被调用

比如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity ^0.8.4;

import "hardhat/console.sol";

contract Callee {
    receive() external payable {
        console.log(
            "[LOG] got some money, from:",
            msg.sender,
            ", value:",
            msg.value
        );
    }

    function getBalance() public view returns (uint) {
        return address(this).balance;
    }
}
 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
const { expect } = require('chai');
const { ethers } = require('hardhat'); // https://docs.ethers.io/v5/
const { loadFixture } = require("@nomicfoundation/hardhat-network-helpers");

describe('ReceiveEther Contract Test Suits', function () {

    // https://hardhat.org/tutorial/testing-contracts#reusing-common-test-setups-with-fixtures
    // loadFixture将在第一次运行设置,并在其他测试中快速返回到该状态。
    async function deployContractFixture() {
        const [owner, addr1] = await ethers.getSigners();
        const facotry = await ethers.getContractFactory("Callee");
        const contract = await facotry.deploy();
        return { contract, owner, addr1 };
    }

    it("测试1", async function () {
        const { contract, owner, addr1 } = await loadFixture(deployContractFixture);

        let balance = await contract.getBalance();
        expect(balance).to.equal(0);

        //send ether to contract
        await owner.sendTransaction({
            to: contract.address,
            value: ethers.utils.parseEther("0.01"),
            gasLimit: 50000
        });

        balance = await contract.getBalance();
        expect(balance).to.equal(ethers.utils.parseEther("0.01"));
    });

});

输出:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
❯ npx hardhat test ./test/receive.test.js                                            
Compiled 1 Solidity file successfully


  ReceiveEther Contract Test Suits
[LOG] got some money, from: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 , value: 10000000000000000
    ✔ 测试1 (1044ms)


  1 passing (1s)

如果没有receive函数 (或其替代品, 见下面的fallback的附加作用), 则执行会报错

1
2
3
  1) ReceiveEther Contract Test Suits
       测试1:
     Error: Transaction reverted: function selector was not recognized and there's no fallback nor receive function

注意

这里说的是让合约地址可以**以纯转账的方式**接收以太 而不是说合约地址上可以不存钱,

比如调用payable函数就不是"纯转账", 这时候是不需要receive函数的

1
2
3
4
5
6
7
8
    function giveMoney() external payable {
        console.log(
            "[LOG] giveMoney, from:",
            msg.sender,
            ", value:",
            msg.value
        );
    }

Fallback

1
2
3
fallback() external {
	//...
}

1
2
3
fallback() external payable{
	//...
}

基本作用:

当对合约调用一个不存在的函数时, 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
// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity ^0.8.4;

import "hardhat/console.sol";


contract Callee {
	
		fallback() external {
        console.log("[LOG] you called one function which not exist ");
    }
    function getBalance() public view returns (uint) {
        return address(this).balance;
    }
}

contract Caller {
    function callFunctionDoesNotExist(address _addr) public {
        (bool success, ) = _addr.call(
            abi.encodeWithSignature("doesNotExist()")
        );
        console.log("[LOG] address call return: ", success);
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17

const { ethers } = require('hardhat'); 

describe('Caller Contract Test Suits', function () {

    it("测试调用不存在的函数", async function () {
        let facotry = await ethers.getContractFactory("Caller");
        let callerContract = await facotry.deploy();

        facotry = await ethers.getContractFactory("Callee");
        let receiveContract = await facotry.deploy();

        await callerContract.callFunctionDoesNotExist(receiveContract.address);

    });

});

输出

1
2
3
4
5
6
7
8
❯ npx hardhat test ./test/caller.test.js                                             
Compiled 1 Solidity file successfully


  Caller Contract Test Suits
[LOG] you called one function which not exist 
[LOG] address call return:  true
    ✔ 测试调用不存在的函数 (1080ms)

附加作用(不推荐的)

当没有receive函数, 并且 fallback() 为 payable时 (也就是fallback() external payable), 充当receive的角色.

2300gas

receivefallback 由于并非用户明确调用, 所以gas是定死的2300, 那么不要在他们的函数体中做高消耗gas的操作, 记记日志就不错了:)

DMEO代码

https://github.com/yinhui1984/imagehosting/blob/main/images/1669789488023678000.7z

下载后, 调用makefile: make init 来安装依赖

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16

.PHONY:  test

init:
	@echo "Initializing the project"
	npm init -y
	npm install hardhat
	npm install @nomicfoundation/hardhat-toolbox

compile:
	npx hardhat compile

test:
	@echo  "\033[31m运行使用js编写的测试用例\033[m" 
	@# 后面跟文件路径可以只运行某个测试文件, 比如 npx hardhat test ./test/ZombieAttack.test.js 
	npx hardhat test