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
Создайте проект:
forge init foundry-fork-prank-demo --no-commit
cd foundry-fork-prank-demoЗамените файл test/WethFork.t.sol:
// 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);
}
}Запустите тест:
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().
Что дальше
- Foundry: forge test — базовый цикл Solidity-тестов
- WETH: deposit и withdraw — понять, как работает сам WETH
- ERC-20 transfer — разобрать обычный перевод токенов