китаев.tech

Foundry: fork тест и vm.prank

Как в Foundry создать mainnet fork, изменить локальный ERC-20 баланс и выполнить вызов от имени другого адреса через vm.prank.

Зачем нужен fork в Foundry

Fork-тест запускает Solidity-тест поверх состояния реальной сети. Вы можете читать mainnet-контракты, проверять интеграции и воспроизводить баги без транзакций в mainnet.

vm.prank меняет msg.sender для следующего вызова. В fork-тестах это удобно, когда нужно проверить поведение от имени whale, multisig, router или обычного пользователя.


Проверяем WETH transfer на fork

Создайте проект:

bash
forge init foundry-fork-prank-demo --no-commit
cd foundry-fork-prank-demo

Замените файл test/WethFork.t.sol:

solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Test} from "forge-std/Test.sol";

interface IERC20 {
    function balanceOf(address owner) external view returns (uint256);
    function transfer(address to, uint256 amount) external returns (bool);
}

contract WethForkTest is Test {
    IERC20 private constant WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);

    address private constant ALICE = address(0xA11CE);
    address private constant BOB = address(0xB0B);

    function setUp() public {
        vm.createSelectFork("https://ethereum-rpc.publicnode.com", 19_000_000);
    }

    function testTransferWethFromAliceWithPrank() public {
        deal(address(WETH), ALICE, 100 ether);

        uint256 amount = 5 ether;

        assertEq(WETH.balanceOf(ALICE), 100 ether);
        assertEq(WETH.balanceOf(BOB), 0);

        vm.prank(ALICE);
        bool success = WETH.transfer(BOB, amount);

        assertTrue(success);
        assertEq(WETH.balanceOf(ALICE), 95 ether);
        assertEq(WETH.balanceOf(BOB), amount);
    }
}

Запустите тест:

bash
forge test --match-contract WethForkTest -vv

Как это работает

vm.createSelectFork(url, blockNumber) — создаёт fork Ethereum mainnet и сразу делает его активным.

19_000_000 — фиксирует блок, чтобы тест не зависел от текущего состояния сети.

IERC20 — минимальный интерфейс WETH. Для теста нужны только balanceOf() и transfer().

deal(address(WETH), ALICE, 100 ether) — локально записывает ERC-20 баланс для ALICE на fork.

vm.prank(ALICE) — меняет msg.sender только для следующего вызова.

WETH.transfer(BOB, amount) — выполняется на реальном mainnet-коде WETH, но состояние меняется только в локальном fork.

Частые ошибки

Думаете, что deal() минтит токены в mainnet → он меняет storage только в локальном тесте.

Забыли pin block → fork будет брать свежий блок, и тест может стать медленнее или нестабильнее.

Используете vm.prank для нескольких вызововprank действует один раз. Для серии вызовов используйте vm.startPrank() и vm.stopPrank().


Что дальше

Материалы китаev.tech публикуются в образовательных целях и не являются инвестиционной рекомендацией. Примеры кода и описания протоколов — для обучения; использование в продакшне на ваш собственный риск. Дисклеймер и политика конфиденциальности.