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.

Ir a la instalación

Datos de origen

Repositorio
ccashwell/evm-cortex
Última actividad en el origen
10 de abril de 2026 a las 16:31
Idioma detectado de SKILL.md
inglés
Estrellas
131
Forks
18

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
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
Ver en GitHub