Настройка автоматичного тестування смарт-контрактів
Автоматичне тестування — це те, що відділяє контракти з $10K TVL від контрактів з $100M TVL. Не тому що аудит замінює тести, а тому що без тестового покриття аудит обходиться у 3–5 раз дороже та знаходить менше проблем: аудитор витрачає час на розуміння базового поведінки контракту замість глибокого аналізу edge cases.
Хороша тестова інфраструктура — Foundry для швидких unit/fuzz тестів + coverage звіти + CI pipeline, який не дозволяє мёржити код з падаючими тестами.
Foundry як основний інструмент
Foundry — сучасний стандарт для тестування Solidity. Тести пишуться на Solidity (не JavaScript), що дає прямий доступ до EVM internals без ABI-слою. Компіляція та запуск тестів займають секунди проти хвилин у Hardhat для великих проектів.
# Установка
curl -L https://foundry.paradigm.xyz | bash
foundryup
# Структура проекту
forge init my-project
# src/ - контракти
# test/ - тесты
# script/ - деплой скрипти
# lib/ - залежності (git submodules)
Базова структура тесту
// test/Token.t.sol
pragma solidity ^0.8.20;
import {Test, console} from "forge-std/Test.sol";
import {MyToken} from "../src/MyToken.sol";
contract TokenTest is Test {
MyToken token;
address alice = makeAddr("alice");
address bob = makeAddr("bob");
function setUp() public {
token = new MyToken(1_000_000e18);
token.transfer(alice, 1000e18);
}
function test_transfer_success() public {
vm.prank(alice);
token.transfer(bob, 100e18);
assertEq(token.balanceOf(bob), 100e18);
assertEq(token.balanceOf(alice), 900e18);
}
function test_transfer_revertIfInsufficientBalance() public {
vm.prank(alice);
vm.expectRevert("ERC20: transfer amount exceeds balance");
token.transfer(bob, 2000e18);
}
}
Ключові vm cheat codes.
Fuzz-тестування
Fuzz-тестування — автоматична генерація тест-кейсів. Foundry запускає функцію тисячи разів з різними випадковими вхідними даними, ищучи нарушення інваріанту.
Invariant-тестування
Invariant тести — більш потужний інструмент. Foundry випадково викликає будь-які функції контракту у будь-якому порядку та перевіряє що інваріант ніколи не нарушається.
Fork тестування
Fork тести працюють з реальним mainnet state, що дозволяє тестувати інтеграцію з реальними Uniswap, Aave, Chainlink.
Coverage та якісні метрики
# Генерація coverage звіту
forge coverage --report lcov
# Детальний звіт у terminal
forge coverage --report summary
Coverage показує % рядків/функцій/гілок охоплених тестами. Цільові показатели для production контрактів:
| Тип контракту | Line coverage | Branch coverage |
|---|---|---|
| Core logic (vault, AMM) | >95% | >90% |
| Peripheral (router, helper) | >85% | >75% |
| Admin/governance | >90% | >85% |
Але coverage — не все. 100% line coverage при відсутності assertion-ів в тестах ніщо не гарантує. Потрібно считати кількість assert* та vm.expectRevert на функцію.
CI/CD інтеграція
# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
- name: Run tests
run: forge test --fork-url ${{ secrets.ETH_RPC_URL }} -vvv
- name: Check coverage
run: |
forge coverage --report summary | tee coverage.txt
python3 -c "
import re, sys
with open('coverage.txt') as f:
content = f.read()
match = re.search(r'Lines.*?(\d+\.\d+)%', content)
if match and float(match.group(1)) < 90:
print(f'Coverage {match.group(1)}% < 90%')
sys.exit(1)
"
Додаткові інструменти
Slither (статичний аналіз, Trail of Bits): знаходить відомі паттерни вразливостей. Запускається за секунди, інтегрується в CI.
Echidna (property-based fuzzer, Trail of Bits): більш потужний fuzzer для специфічних інваріантів.
Halmos (formal verification): symbolically перевіряє всі можливі шляхи виконання. Знаходить вразливості, які statistical fuzzing пропускає.
Оптимальна комбінація для більшості проектів: Foundry (unit + fuzz + invariant) + Slither (CI) + coverage gate. Echidna та Halmos — для критичних компонентів.
Настройка повної тестової інфраструктури: 1–2 тижні для проекту середнього розміру. Написання comprehensive тестів для існуючої codebase — 3–6 тижнів залежно від розміру та складності.







