:2026-03-08 4:12 点击:1
在区块链开发的世界里,尤其是对于以太坊生态的开发者而言,“分叉”(Fork)是一个绕不开的核心概念,无论是想复现历史交易以调试智能合约、测试新协议提案,还是在主网上部署重大升级前的演练,都离不开对以太坊网络进行精确的“分叉”操作,而实现这一切的基石,正是以太坊分叉测试代码。
本文将深入探讨以太坊分叉测试代码的原理、常用工具、实践方法及其重要性,帮助开发者掌握这一关键技能。
在以太坊的语境下,“分叉”并非指网络分裂,而是指从以太坊主网的某个特定区块高度(或某个特定时间点)开始,复制一个完全一致的链上状态,这个复制的状态,就像是一个“时间机器”,可以让我们将环境瞬间拉回到那个时间点。
为什么需要这样做?
debug_traceTransaction)一步步追踪执行流程,而无需花费真实的Gas。编写以太坊分叉测试代码,通常离不开以下几个核心工具:
以太坊客户端:如 Geth、Nethermind、Besu 等,它们是运行以太坊网络的软件。Geth 是最常用、功能最丰富的客户端之一,提供了强大的内置 RPC API,使得分叉操作变得异常简单。
测试框架:
hardhat-fork 插件是其分叉功能的核心。forge 和 cast)而备受青睐,Foundry 的 fork 命令同样强大且高效。truffle develop 环境结合 Geth 的 RPC 来实现分叉测试。下面我们通过两个最流行的框架,来看一下分叉测试代码的具体实现。
确保你已安装 Hardhat,在你的项目中安装 hardhat-fork 插件:
npm install --save-dev hardhat npm install --save-dev @nomicfoundation/hardhat-toolbox
然后在 hardhat.config.js 中配置:
require("@nomicfoundation/hardhat-toolbox");
module.exports = {
solidity: "0.8.17",
networks: {
hardhat: {
forking: {
// 指定要分叉的以太坊节点URL,Infura 或 Alchemy
url: `https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY`,
// 可选:指定从哪个区块高度开始分叉
blockNumber: 15700000,
},
},
},
};
你可以编写一个测试脚本 test/fork-test.js:
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("Fork Test Example", function () {
it("Should check the balance of a mainnet address", async function () {
// 我们选择一个知名的以太坊地址进行测试
const address = "0x00000000219ab540356cBB839Cbe05303d7705Fa"; // Uniswap V2 Router
// 通过 ethers 获取该地址在分叉链上的余额
const balance = await ethers.provider.getBalance(address);
// 将余额转换为更易读的单位(Ether)
console.log(`Balance of ${address} is: ${ethers.formatEther(balance)} ETH`);
// 编写一个断言,我们预期这个地址的余额大于某个值
expect(balance).to.be.greaterThan(ethers.parseEther("1000"));
});
it("Should simulate a transaction on the forked chain", async function () {
// 假设我们有一个部署在分叉链上的测试合约
const SimpleContract = await ethers.getContractFactory("SimpleContract");
const simpleContract = await SimpleContract.deploy();
await simpleContract.waitForDeployment();
// 调用合约方法
const tx = await simpleContract.setValue(123);
await tx.wait();
// 验证结果
const value = await simpleContract.getValue();
expect(value).to.equal(123);
});
});
运行测试 npx hardhat test,Hardhat 就会自动连接到主网,从指定区块开始分叉,并执行你的测试代码。
Foundry 的方式更加“硬核”和直接,创建一个新的 Foundry 项目:
forge init my-fork-test cd my-fork-test
在 foundry.toml 中配置分叉节点:
[profile.default] src = "src" out = "out" libs = ["lib"] [rpc_endpoints] mainnet = "https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY"
在 test/ForkTest.t.sol 中编写测试合约:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.17;
import "forge-std/Test.sol";
contract ForkTest is Test {
function test_ForkAndCheckBalance() public {
// vm.createFork 创建一个指向 mainnet RPC 的分叉
uint256 forkId = vm.createFork("https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY");
// 切换到这个分叉
vm.selectFork(forkId);
// 地址:0x00000000219ab540356cBB839Cbe05303d7705Fa (Uniswap V2 Router)
address uniswapRouter = 0x00000000219ab540356cBB839Cbe05303d7705Fa;
uint256 balance = IERC20(address(0)).balanceOf(uniswapRouter); // 假设我们检查ETH余额
console.log("Balance of Uniswap Router:", balance);
// 断言余额大于 1000 ETH
assertGt(balance, 1000e18);
}
function test_SimulateTransaction() public {
// 同样创建分叉
uint256 forkId = vm.createFork("https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY");
vm.selectFork(forkId);
// 部署一个测试合约
SimpleContract simpleContract = new SimpleContract();
simpleContract.setValue(42);
// 验证调用结果
assertEq(simpleContract.getValue(), 42);
}
}
// 一个简单的测试合约
contract SimpleContract {
uint256 public value;
function setValue(uint256 _value) public {
value = _value;
}
function getValue() public view returns (uint256) {
return value;
}
}
// IERC20 接口,用于获取余额
interface IERC20 {
function balanceOf(address account) external view returns (uint256);
}
运行测试 forge test -- -vvv,你将看到 Foundry 在分叉的链上执行你的测试逻

本文由用户投稿上传,若侵权请提供版权资料并联系删除!