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
آخر نشاط في المصدر
١٠ أبريل ٢٠٢٦ في ١٦:٣١
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
١٣١
التفرعات
١٨

خيارات التثبيت

يُحدَّد 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