Foundry — forge test
Как написать Solidity-тест в Foundry, проверить state, event и revert, а затем запустить его через forge test.
Что делает forge test
forge test компилирует контракты и запускает тесты, написанные на Solidity. Тест вызывает контракт напрямую, без TypeScript, ethers и отдельного test runner.
Foundry удобен для проверки Solidity-логики: изменения storage, событий, reverts, modifiers и edge cases.
Пишем тест для Counter
Установите Foundry и создайте проект:
curl -L https://foundry.paradigm.xyz | bash
foundryup
forge init foundry-forge-test-demo --no-commit
cd foundry-forge-test-demoЗамените файл src/Counter.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Counter {
uint256 public value;
address public owner;
event Incremented(address indexed caller, uint256 newValue);
error NotOwner(address caller);
constructor() {
owner = msg.sender;
}
function increment() external {
if (msg.sender != owner) {
revert NotOwner(msg.sender);
}
value += 1;
emit Incremented(msg.sender, value);
}
}Замените файл test/Counter.t.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Test} from "forge-std/Test.sol";
import {Counter} from "../src/Counter.sol";
contract CounterTest is Test {
Counter private counter;
address private owner = address(0xA11CE);
address private user = address(0xB0B);
event Incremented(address indexed caller, uint256 newValue);
function setUp() public {
vm.prank(owner);
counter = new Counter();
}
function test_IncrementChangesState() public {
vm.prank(owner);
counter.increment();
assertEq(counter.value(), 1);
}
function test_IncrementEmitsEvent() public {
vm.expectEmit(true, false, false, true);
emit Incremented(owner, 1);
vm.prank(owner);
counter.increment();
}
function test_RevertsWhenCallerIsNotOwner() public {
vm.expectRevert(abi.encodeWithSelector(Counter.NotOwner.selector, user));
vm.prank(user);
counter.increment();
}
}Запустите тесты:
forge test -vvКак это работает
CounterTest is Test — подключает forge-std/Test.sol: assertions и cheatcodes через vm.
setUp() — выполняется перед каждым тестом и создаёт чистое состояние.
vm.prank(owner) — делает следующий вызов так, будто его отправил owner.
assertEq(counter.value(), 1) — сравнивает фактическое значение с ожидаемым.
vm.expectEmit(true, false, false, true) — включает проверку первого indexed topic и data события.
emit Incremented(owner, 1) — описывает событие, которое должен выпустить следующий вызов.
vm.expectRevert(...) — проверяет, что следующий вызов упадёт с конкретным custom error.
forge test -vv — запускает тесты и показывает больше логов, чем обычный forge test.
Частые ошибки
Функция теста не начинается с test → Forge запускает только функции с именами test..., например test_IncrementChangesState().
Пишете expectRevert после вызова → vm.expectRevert() должен стоять прямо перед вызовом, который должен упасть.
Создаёте контракт один раз на весь файл → используйте setUp(), чтобы каждый тест начинался с одинакового состояния.
Что дальше
- Foundry: fork тест и vm.prank — проверить контракт на состоянии реальной сети
- Custom errors в Solidity — писать reverts, которые удобно проверять в тестах
- События и emit — понять, как устроены события, которые проверяет
expectEmit