китаев.tech

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 и создайте проект:

bash
curl -L https://foundry.paradigm.xyz | bash
foundryup

forge init foundry-forge-test-demo --no-commit
cd foundry-forge-test-demo

Замените файл src/Counter.sol:

solidity
// 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:

solidity
// 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();
    }
}

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

bash
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(), чтобы каждый тест начинался с одинакового состояния.


Что дальше

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