Foundry 作弊码

本章学习什么是作弊码,以及如何使用作弊码来测试智能合约。

推特@Hita_DAO  DiscordHitaDAO

代码github.com/hitadao/solidity

在大多数时候,仅仅通过输出信息来测试智能合约是不够的,有的时候还需要随时更改区块链的状态。

为了操纵区块链的状态,Foundry 附带了一组作弊码 Cheatcodes。 作弊码在测试时非常有用,可以显著地提高测试效率。

比如:合约的某项功能需要在合约部署 1 个小时后生效。这种情况下,我们就不能等待 1 个小时才进行测试,而是直接通过作弊码,直接跳过 1 小时。

再比如,需要模拟某个特定的巨鲸地址给我们转了一笔账。由于我们没有这个地址的私钥,无法进行测试。这时候,我们就可以通过作弊码模拟巨鲸转账。

Foundry 项目中,可以通过 Forge 标准库的 vm 库合约,轻松使用作弊码。

示例合约

下面,我们为只能由智能合约所有者调用的函数,编写一个测试用例:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "forge-std/Test.sol";

error Unauthorized();

contract OwnerOnly {
    address public constant owner = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
    uint256 public count;

    function increment() external {
        if (msg.sender != owner) {
            revert Unauthorized();
        }
        count++;
    }
}

contract OwnerOnlyTest is Test {
    OwnerOnly ownerOnly;

    function setUp() public {
        ownerOnly = new OwnerOnly();
    }

    function testIncrementAsOwner() public {
        ownerOnly.increment();
        assertEq(ownerOnly.count(), 1);
    }
}

我们来运行测试 forge test,发现无法通过测试。

[⠆] Compiling...
No files changed, compilation skipped

Ran 1 test for test/1.t.sol:OwnerOnlyTest
[FAIL. Reason: Unauthorized()] testIncrementAsOwner() (gas: 5446)
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 936.52µs (78.21µs CPU time)

Ran 1 test suite in 289.68ms (936.52µs CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)

Failing tests:
Encountered 1 failing test in test/1.t.sol:OwnerOnlyTest
[FAIL. Reason: Unauthorized()] testIncrementAsOwner() (gas: 5446)

Encountered a total of 1 failing tests, 0 tests succeeded

这是因为被测试的函数 increment,它的调用者地址必须是合约所有者 0x5B38...ddC4。

我们在测试用例合约 OwnerOnlyTest 的函数 setUp 中,创建了合约实例 ownerOnly。所以,ownerOnly 的所有者是 OwnerOnlyTest,而不是特定地址 0x5B38...ddC4。

由于我们没有 0x5B38...ddC4 的私钥,所以无法使用这个地址来创建 OwnerOnly 合约的实例。

在这种情况下,为了测试函数 increment() 的功能,我们就可以使用作弊码,伪装测试者的身份是 0x5B38...ddC4。

使用作弊码

下面,我们使用作弊码,模拟调用 increment 函数的地址为 0x5B38...ddC4,编写一个测试用例:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "forge-std/Test.sol";

error Unauthorized();

contract OwnerOnly {
    address public constant owner = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
    uint256 public count;

    function increment() external {
        if (msg.sender != owner) {
            revert Unauthorized();
        }
        count++;
    }
}

contract OwnerOnlyTest is Test {
    OwnerOnly ownerOnly;

    function setUp() public {
        ownerOnly = new OwnerOnly();
    }

    function testIncrementAsOwner() public {
        vm.prank(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4);
        ownerOnly.increment();
        assertEq(ownerOnly.count(), 1);
    }
}

我们来运行测试 forge test,测试顺利通过。

[⠆] Compiling...
[⠢] Compiling 1 files with 0.8.21
[⠰] Solc 0.8.21 finished in 1.86s
Compiler run successful!

Ran 1 test for test/1.t.sol:OwnerOnlyTest
[PASS] testIncrementAsOwner() (gas: 31636)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 12.05ms (2.65ms CPU time)

Ran 1 test suite in 320.19ms (12.05ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)

关于 vm 支持的所有作弊码,我们会在后面的内容中详细列出。