教程区块链区块链技术ch1313.2 本地开发网络与主网分叉

本页目录

在本地就能模拟 Uniswap 的 40 亿 TVL?主网分叉(Mainnet Fork)让你把真实合约状态复制到本地 HRE,用假 ETH 测试真实交互。这是 DeFi 开发者的必备工具。


13.2.1 本地网络的类型

网络状态来源与主网关系用途
Hardhat Network空(或用户预制)独立基础单元测试
Anvil / Ganache空(或 fork 后)独立或 fork快速验证
Mainnet Fork主网状态快照复制主网,独立运行集成测试、漏洞复现
Testnet(Sepolia)真实共识,真实 ETH公共测试环境预生产验证

主网分叉的原理

sequenceDiagram
    participant Dev as 开发者
    participant Local as 本地 Hardhat/Anvil
    participant Mainnet as 主网 RPC
    participant Arch as 归档节点
    
    Dev ->> Local: npx hardhat --fork mainnet
    Local ->> Mainnet: 请求区块 19,000,000 状态
    Mainnet -->> Local: 返回账户/合约/存储
    Local ->> Arch: 请求历史状态(archive RPC)
    Arch -->> Local: 返回历史 storage slots
    
    Note over Dev,Local: 本地:相同的合约地址、<br/>相同的状态、相同的余额——<br/>但所有 ETH/操作都是本地的!
    
    Dev ->> Local: 调用 Uniswap.swap()
    Local -->> Dev: 返回值(来自本地副本)

本地节点懒加载(on-demand):只在被访问时从主网拉取状态,大幅节省带宽和启动时间。


13.2.2 主网分叉的 TypeScript 启动配置

typescript
/**
 * Hardhat 主网 Fork 配置(TS 模拟,展示核心参数)
 */
interface ForkConfig {
  url: string;           // 主网 RPC(Infura / Alchemy / 自建)
  blockNumber?: number;  // 指定分叉区块,固定以获得可重复性
  enabled: boolean;
}

const forkConfig: ForkConfig = {
  url: "https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY", // 需要归档访问
  blockNumber: 19000000,  // 固定在某一天的区块
  enabled: true,
};

// 启动后,你可以:
// 1. 与真实 Aave 的智能合约交互
// 2. 用 impersonate 模拟大户进行测试
// 3. 修改任何状态(不会上传)

interface ImpersonateAPI {
  // 模拟某地址的签名
  async impersonateAddress(address: string): Promise<void>;
  // 给该地址置入 ETH(本地网络)
  async setBalance(address: string, balance: bigint): Promise<void>;
}

// 示例:模拟「巨鲸」进行测试
async function testWhale(fork: ImpersonateAPI) {
  const vitalikAddr = "0xd8dA6BF26964aF9D7aEd9e03E53415D3aD4803d1";
  const aavePool = "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4e2";
  
  await fork.impersonateAddress(vitalikAddr); // 模拟 Vitalik 的签名
  await fork.setBalance(vitalikAddr, 1000n * 10n**18n); // 给 1000 ETH
  
  // 现在可以使用 "Vitalik" 的身份调用 Aave 池
  console.log("激活了 Vitalik 测试身份");
}

13.2.3 时间操纵与状态控制

主网开发网络的核心超能力:

graph TB
    subgraph Superpowers["本地网络超能力"]
        T[时间旅行]
        M[挖矿控制]
        I[身份模拟]
        S[状态重置]
        G[Gas 任意设]
    end
    
    T --> |"evm_increaseTime"| EVM1[快进 7 天]
    T --> |"evm_mine"| EVM2[强制出一个新块]
    T --> |"evm_setNextBlockTimestamp"| EVM3[精确设置下一区块时间]
    I --> |"impersonate"| EVM4[任何人都可以签名]
    S --> |"evm_snapshot"| EVM5[快照+回滚]
    
    style Superpowers fill:#e3f2fd
typescript
/**
 * 时间旅行测试:测试 Uniswap 的 7 天 TWAP
 */
interface EVMTimeTravel {
  async increaseTime(seconds: number): Promise<void>;
  async mine(): Promise<void>;
  async snapshot(): Promise<number>;
  async revert(id: number): Promise<void>;
}

async function test7DayTWAP(evm: EVMTimeTravel) {
  // 初始快照
  const snap = await evm.snapshot();
  
  // Day 0: 记录价格
  const price0 = await getPrice();
  
  // 快进 7 天(不需要等 7 天!)
  await evm.increaseTime(7 * 24 * 60 * 60); // 604,800 秒
  await evm.mine(); // 确认
  
  // Day 7: 检查 TWAP
  const twap = await get7DayTWAP();
  console.assert(twap > 0, "TWAP should accumulate 7 days");
  
  // 回滚,不影响其他测试
  await evm.revert(snap);
}

function getPrice(): Promise<number> { return Promise.resolve(1000); }
function get7DayTWAP(): Promise<number> { return Promise.resolve(990); }

// 模拟
const mockEVM: EVMTimeTravel = {
  async increaseTime(s) { console.log(`Jumped ${s}s`); },
  async mine() { console.log("Mined block"); },
  async snapshot() { return 1; },
  async revert(id) { console.log("Reverted to snapshot", id); },
};
test7DayTWAP(mockEVM).then(() => console.log("Time travel test done"));

13.2.4 Fork 测试的安全边界

注意说明
需要归档 RPC普通节点只保存最近 128 个区块的 state;fork 过往状态需要 eth_getStorageAt 到任意旧区块
状态是只读的初始副本本地修改不会影响主网
真实时间≠区块时间本地可以 0 间隔出矿,与主网出块率不同
预言机价格不更新Chainlink 喂价不会自动更新,需手动调整

> ← 13.1 开发框架 | 前往 → 13.3 测试策略与 Gas 报告 |*

评论

0

评论加载中…

发表评论

0/2000