Skip to main content

fork-testing

Use when testing against live protocol state on mainnet or L2 forks. Covers vm.createFork, account impersonation, block pinning, token dealing, and patterns for DeFi integration testing.

소스 정보

저장소
ccashwell/evm-cortex
최근 소스 활동
2026년 4월 10일 16:31
감지된 SKILL.md 언어
영어
스타
131
포크
18

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
fork-testing
description
Use when testing against live protocol state on mainnet or L2 forks. Covers vm.createFork, account impersonation, block pinning, token dealing, and patterns for DeFi integration testing.
# Fork Testing Patterns ## Overview Fork testing runs your tests against a snapshot of a live blockchain. This lets you test interactions with real deployed contracts (Aave, Uniswap, Chainlink) without deploying mocks. ## Basic Fork Setup ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {Test, console2} from "forge-std/Test.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract ForkTest is Test { uint256 mainnetFork; // Mainnet addresses address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address constant AAVE_POOL = 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2; function setUp() public { // Pin to a specific block for reproducibility mainnetFork = vm.createFork(vm.envString("ETH_RPC_URL"), 19_500_000); vm.selectFork(mainnetFork); } function test_forkIsActive() public view { assertEq(vm.activeFork(), mainnetFork); assertEq(block.number, 19_500_000); } } ``` ## RPC Configuration In `foundry.toml`: ```toml [rpc_endpoints] mainnet = "${ETH_RPC_URL}" arbitrum = "${ARBITRUM_RPC_URL}" optimism = "${OPTIMISM_RPC_URL}" base = "${BASE_RPC_URL}" ``` Usage: `vm.createFork("mainnet", blockNumber)` ## Impersonating Accounts ```solidity // Impersonate a whale to get tokens address USDC_WHALE = 0x47ac0Fb4F2D84898e4D9E7b4DaB3C24507a6D503; // Binance function test_impersonateWhale() public { vm.prank(USDC_WHALE); IERC20(USDC).transfer(address(this), 1_000_000e6); assertEq(IERC20(USDC).balanceOf(address(this)), 1_000_000e6); } ``` ## Dealing Tokens ```solidity // deal() writes directly to storage — works for standard ERC20s function test_dealTokens() public { deal(USDC, address(this), 1_000_000e6); assertEq(IERC20(USDC).balanceOf(address(this)), 1_000_000e6); // For tokens with non-standard storage (e.g., USDT): deal(USDC, address(this), 1_000_000e6, true); // true = adjust totalSupply } // For complex tokens where deal() fails, impersonate a holder: function _getTokens(address token, uint256 amount, address to) internal { address whale = _findWhale(token); vm.prank(whale); IERC20(token).transfer(to, amount); } ``` ## DeFi Integration Test Pattern ```solidity contract AaveForkedTest is Test { IPool constant pool = IPool(0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2); function setUp() public { vm.createSelectFork("mainnet", 19_500_000); } function test_supplyAndBorrow() public { address user = makeAddr("user"); // Give user WETH collateral deal(WETH, user, 10 ether); vm.startPrank(user); // Supply WETH as collateral IERC20(WETH).approve(address(pool), 10 ether); pool.supply(WETH, 10 ether, user, 0); // Borrow USDC against WETH pool.borrow(USDC, 5_000e6, 2, 0, user); assertEq(IERC20(USDC).balanceOf(user), 5_000e6); // Check health factor (,,,,,uint256 hf) = pool.getUserAccountData(user); assertGt(hf, 1e18, "should be healthy"); vm.stopPrank(); } function test_liquidation() public { address borrower = makeAddr("borrower"); address liquidator = makeAddr("liquidator"); // Setup underwater position using price manipulation _setupUnderwaterPosition(borrower); // Liquidate deal(USDC, liquidator, 10_000e6); vm.startPrank(liquidator); IERC20(USDC).approve(address(pool), 10_000e6); pool.liquidationCall(WETH, USDC, borrower, 5_000e6, false); vm.stopPrank(); // Verify liquidator received collateral assertGt(IERC20(WETH).balanceOf(liquidator), 0); } } ``` ## Multi-Fork Testing Test cross-chain interactions: ```solidity function test_crossChainState() public { uint256 mainnetFork = vm.createFork("mainnet"); uint256 arbitrumFork = vm.createFork("arbitrum"); // Check mainnet state vm.selectFork(mainnetFork); uint256 mainnetBalance = IERC20(USDC).totalSupply(); // Check Arbitrum state vm.selectFork(arbitrumFork); address ARB_USDC = 0xaf88d065e77c8cC2239327C5EDb3A432268e5831; uint256 arbBalance = IERC20(ARB_USDC).totalSupply(); // Contracts deployed on a fork persist only on that fork vm.selectFork(mainnetFork); MyBridge bridge = new MyBridge(); // bridge only exists on mainnetFork } ``` ## Price Manipulation for Testing ```solidity // Mock Chainlink feed to simulate price crash function _crashPrice(address feed, int256 newPrice) internal { // Find the storage slot for the latest answer // Or use vm.mockCall: vm.mockCall( feed, abi.encodeWithSelector(AggregatorV3Interface.latestRoundData.selector), abi.encode(uint80(1), newPrice, block.timestamp, block.timestamp, uint80(1)) ); } ``` ## Running Fork Tests ```bash # Run all fork tests forge test --fork-url $ETH_RPC_URL # Run with specific block forge test --fork-url $ETH_RPC_URL --fork-block-number 19500000 # Only fork tests (convention: separate test directory) forge test --match-path "test/fork/*" --fork-url $ETH_RPC_URL # Cache RPC calls for speed forge test --fork-url $ETH_RPC_URL --fork-block-number 19500000 # Foundry auto-caches in ~/.foundry/cache/rpc/ ``` ## Checklist - [ ] Pin fork to specific block number for reproducibility - [ ] Use `vm.envString()` for RPC URLs (never hardcode) - [ ] Prefer `deal()` over whale impersonation when possible - [ ] Label all addresses for readable stack traces - [ ] Cache fork data by pinning block numbers - [ ] Test both happy path and failure modes (liquidation, oracle failure) - [ ] Mock external calls only when necessary (prefer real state) - [ ] CI uses cached RPC or a dedicated node for reliability - [ ] Document which mainnet block and why it was chosen
GitHub에서 보기