foundry分叉测试

分叉测试

Forge 支持使用两种不同的方法在分叉环境中进行测试:

  • 分叉模式(Forking Mode):通过forge test –fork-url 标志使用一个单独分叉进行所有测试。

  • 分叉作弊码(Forking Cheatcodes):通过 forking 作弊码 在 Solidity 测试代码中直接创建、选择和管理多个分叉。

以下值会更改以反映分叉时链的值:

1
2
3
4
5
6
7
8
block_number
chain_id
gas_limit
gas_price
block_base_fee_per_gas
block_coinbase
block_timestamp
block_difficulty

分叉模式

可以使用 –fork-block-number 指定要从中分叉的区块高度:

forge test –fork-url <your_rpc_url> –fork-block-number 1

假设要测试自己开发的 DApp 是否能正确与 Uniswap V3 的 WETH/USDC 池 交互。这个池已经在主网上存在,地址是 0x8ad5…,我不想自己部署一个假的池(因为逻辑复杂),也不想真的在主网上交易(贵 + 危险),所有解决方案是从主网分叉,本地启动anvil,根据RPC从主网指定区块拉取状态,也就是拉取WETH/USDC 池,然后就可以本地随意操作,不会消耗真实gas。

缓存(Caching)

如果同时指定了 –fork-url 和 –fork-block-number,那么该块的数据将被缓存以供将来的测试运行。

数据缓存在 ~/.foundry/cache/rpc// 中。 要清除缓存,只需删除目录或运行 forge clean。

也可以通过传递 –no-storage-caching 或通过配置 no_storage_caching 和 foundry.toml 完全忽略缓存 rpc_storage_caching

分叉作弊码

分叉作弊码可以编程方式进入分叉模式,而不是通过 forge CLI 参数配置分叉模式。

这些作弊码可以在逐个测试的基础上使用分叉模式,并在测试中使用多个分叉,每个分叉都通过其自己唯一的 uint256 标识符进行识别。

 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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {Test} from "forge-std/Test.sol";

contract ForkTest is Test {
    // fork id
    uint256 mainnetFork;
    uint256 optimismFork;

    //string MAINNET_RPC_URL = vm.envString("MAINNET_RPC_URL");
    //string OPTIMISM_RPC_URL = vm.envString("OPTIMISM_RPC_URL");

    // 每个test函数执行前创建两个不同的分叉
    function setUp() public {
        mainnetFork = vm.createFork(MAINNET_RPC_URL);
        optimismFork = vm.createFork(OPTIMISM_RPC_URL);
    }

    // 校验两个分叉id是否一致
    function testForkIdDiffer() public {
        assert(mainnetFork != optimismFork);
    }

    // 选择一个分叉并验证是否是激活的分叉
    function testCanSelectFork() public {
        vm.selectFork(mainnetFork);
        assertEq(vm.activeFork(), mainnetFork);
    }

    // 选择一个分叉并验证是否是激活的分叉
    function testCanSwitchForks() public {
        vm.selectFork(mainnetFork);
        assertEq(vm.activeFork(), mainnetFork);

        vm.selectFork(optimismFork);
        assertEq(vm.activeFork(), optimismFork);
    }

    function testCanCreateAndSelectForkInOneStep() public {
        // 创建新分叉并选择
        uint256 anotherFork = vm.createSelectFork(MAINNET_RPC_URL);
        assertEq(vm.activeFork(), anotherFork);
    }

    // 给分叉设置块编号
    function testCanSetForkBlockNumber() public {
        vm.selectFork(mainnetFork);
        // 将该 fork 的 block.number 设为 1,337,000
        vm.rollFork(1_337_000);

        assertEq(block.number, 1_337_000);
    }
}

持久账户分叉

在不同 fork 之间切换时,只有 msg.sender 和测试合约(ForkTest)的账户是持久的,跨分叉共享。其他所有账户的状态都是“每个 fork 独立”的。

可以用 vm.makePersistent(address) 把任意账户变成“全局持久账户”——它在所有 fork 中状态一致。

 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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

contract SimpleStorageContract {
    uint256 public value;

    function set(uint256 _value) public {
        value = _value;
    }
}

contract ForkTest is Test {

    uint256 mainnetFork;
    uint256 optimismFork;

    function setUp() public {
        mainnetFork = vm.createFork(MAINNET_RPC_URL);
        optimismFork = vm.createFork(OPTIMISM_RPC_URL);
    }

    function testCreateContract() public {
        vm.selectFork(mainnetFork);
        assertEq(vm.activeFork(), mainnetFork);

        //new 一个新对象并赋值
        SimpleStorageContract simple = new SimpleStorageContract();
        simple.set(100);
        assertEq(simple.value(), 100);

        vm.selectFork(optimismFork);

        //revert call,因为当前分叉不在simple,而在optimismFork
        simple.value();
    }

     function testCreatePersistentContract() public {
        vm.selectFork(mainnetFork);
        SimpleStorageContract simple = new SimpleStorageContract();
        simple.set(100);
        assertEq(simple.value(), 100);

        // 将simple分叉设为持久账户
        vm.makePersistent(address(simple));
        assert(vm.isPersistent(address(simple)));

        //选择optimismFork分叉
        vm.selectFork(optimismFork);
        assert(vm.isPersistent(address(simple)));

        //即使当前分叉是optimismFork,但是simple.value()依然不会报错,因为simple分叉是持久的,被共享了。
        assertEq(simple.value(), 100);
     }
}
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计