分叉测试
Forge 支持使用两种不同的方法在分叉环境中进行测试:
以下值会更改以反映分叉时链的值:
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);
}
}
|