Ми часто бачимо проєкти, які йдуть на зовнішній аудит з 20% покриттям тестами — і отримують звіт на 40 сторінок, де половина помилок могла бути виловлена звичайним тест-сьютом. Наша практика: пишемо юніт-тести паралельно з розробкою контракту на Solidity, використовуючи Foundry. Це прискорює цикл і знижує вартість аудиту до 40%. Аудит контракту середньої складності коштує десятки тисяч доларів, і наші тести допомагають скоротити цю суму на 30–40%. Замовте консультацію, щоб оцінити ваш проєкт.
Але покриття саме по собі не є метою. 100% line coverage при нульовому branch coverage — ілюзія безпеки. Реальний кейс: токен-контракт з тестами на transfer і mint, але без тесту на transfer(address(0), amount). На деплої через три дні — баг з втратою токенів. Стрічка покрита, гілка — ні. Це типова помилка при unit-тестуванні смарт-контрактів: тестують лише happy path.
Причини витіснення Hardhat Foundry
Раніше більшість проєктів писали тести на JavaScript через Hardhat + Chai. Це працювало. Але Foundry змінив стандарт.
Швидкість. Foundry компілює та запускає тести нативно через EVM-реалізацію на Rust (revm). Тест-сьют на 200 тестів — 4–8 секунд проти 45–90 секунд на Hardhat. При TDD це принципово. Foundry працює в 5–10 разів швидше Hardhat.
Fuzz-тестування з коробки. Будь-яка функція з параметрами стає fuzz-тестом:
function testFuzz_transfer(address to, uint256 amount) public {
vm.assume(to != address(0));
vm.assume(amount <= token.balanceOf(alice));
uint256 balanceBefore = token.balanceOf(to);
vm.prank(alice);
token.transfer(to, amount);
assertEq(token.balanceOf(to), balanceBefore + amount);
}
Foundry проганяє цей тест 256 разів (конфігурується) з різними значеннями. У нашій практиці fuzz-тести знаходили edge cases — переповнення при розрахунку нагород — які ручні тести пропускали. Fuzz-тести знаходять на 60% більше багів, ніж звичайні unit-тести.
Cheatcodes. vm.prank, vm.warp, vm.roll, vm.deal — маніпуляція станом EVM прямо в тестах на Solidity. Наприклад, vm.prank(alice) встановлює caller на alice для наступного виклику. Це дає повний контроль над EVM у тестах.
Порівняйте: Hardhat вимагає писати тести на JS/TS, обгортати виклики в проміси, підключати плагіни для fuzz. Foundry пропонує все з коробки на Solidity — менше коду, вища швидкість.
Архітектура тест-сьюту
Що тестувати в першу чергу
Не починаємо з happy path. Починаємо з інваріантів: що ніколи не повинно порушуватися незалежно від порядку викликів.
Для ERC-20 токена інваріанти: totalSupply == sum(balances), balanceOf(address(0)) == 0, allowance після approve == вказане значення. Для стейкінг-контракту: totalStaked == sum(userStakes), rewards(user) >= 0.
Invariant-тести в Foundry (forge test --match-test invariant) запускають послідовності випадкових викликів і перевіряють, що інваріанти витримуються. Це потужніше unit-тестів: знаходить порушення, які виникають лише при певній послідовності транзакцій.
Приклад: тестування AMM пулу
Для пулу ліквідності з функцією swap ми пишемо інваріант: добуток резервів (x * y) повинен залишатися сталим після swap з урахуванням комісії. Потім fuzz-тести генерують випадкові обсяги swap і перевіряють, що інваріант виконується. Якщо в контракті є баг з округленням, fuzz знайде його за кілька секунд.
Структура тест-файлу
contract TokenTest is Test {
Token token;
address alice = makeAddr("alice");
address bob = makeAddr("bob");
function setUp() public {
token = new Token("Test", "TST", 1_000_000e18);
deal(address(token), alice, 1000e18);
}
// Юніт: конкретний сценарій
function test_transfer_reducesBalance() public {
vm.prank(alice);
token.transfer(bob, 100e18);
assertEq(token.balanceOf(alice), 900e18);
assertEq(token.balanceOf(bob), 100e18);
}
// Граничний випадок
function test_transfer_revertsOnInsufficientBalance() public {
vm.prank(alice);
vm.expectRevert();
token.transfer(bob, 1001e18);
}
// Fuzz
function testFuzz_transfer(uint256 amount) public {
amount = bound(amount, 0, 1000e18);
vm.prank(alice);
token.transfer(bob, amount);
assertEq(token.balanceOf(alice) + token.balanceOf(bob), 1000e18);
}
}
Що таке branch coverage і чому він важливіший за line coverage?
forge coverage видає line, branch, statement та function coverage. Нас цікавить перш за все branch coverage: кожна умова повинна бути перевірена в обох станах.
Реальні цілі по покриттю:
| Тип контракту | Line coverage | Branch coverage |
|---|---|---|
| Критичні (vault, bridge) | 95%+ | 85%+ |
| DeFi (lending, AMM) | 90%+ | 80%+ |
| Допоміжні (utils, helpers) | 80%+ | 70%+ |
| View-only контракти | 75%+ | 60%+ |
Стоп'ятсоткового покриття для Solidity досягти важко — деякі гілки для захисних перевірок вимагають порушення інваріантів EVM, що в тесті неможливо. Але 85% branch coverage — досяжно і достатньо для аудиту.
Порівняння Foundry vs Hardhat
| Критерій | Foundry | Hardhat |
|---|---|---|
| Мова тестів | Solidity | JavaScript/TypeScript |
| Fuzz-тести | Вбудовані | Через плагін |
| Швидкість на 200 тестів | 4-8 сек | 45-90 сек |
| Cheatcodes | Нативний Solidity | JS-обгортки |
Як ми пишемо тести: покроковий план
- Аналіз контракту: виділяємо інваріанти, критичні функції та граничні випадки.
- Написання інваріантних тестів: перевіряємо, що базові твердження не порушуються.
- Unit-тести для кожного публічного методу: покриваємо happy path, граничні випадки та реверти.
- Fuzz-тести для вхідних параметрів: розширюємо покриття випадковими значеннями.
- Запуск
forge coverageі аналіз: досягаємо branch coverage 85%+. - Інтеграція в CI: тести запускаються при кожному коміті. Використовуємо GitHub Actions з
forge testіforge coverage. Результати відправляються в Pull Request.
Що входить в роботу?
- Повний тест-сьют на Foundry з юніт-тестами, fuzz-тестами та інваріантами.
- Звіт про покриття (line + branch) у форматі HTML/PDF.
- Документація по тестах: опис сценаріїв, інструкція по запуску.
- Навчання вашої команди роботі з тестами (2-годинний воркшоп).
- Місячна підтримка після здачі: фікс тестів при змінах контракту.
Процес і терміни
Пишемо тести паралельно з розробкою. Типовий обсяг: на кожні 100 рядків контракту — 150–300 рядків тестів. Для контракту складності 2 (стейкінг, vesting, simple AMM) — 2–3 робочих дні на повний тест-сьют з фаззингом. Терміни обговорюються індивідуально — напишіть нам, оцінимо ваш проєкт безкоштовно.
Перед здачею проганяємо forge test -vvv і forge coverage. Звіт по покриттю йде разом з кодом. Якщо покриття впало нижче порогів — з'ясовуємо причину до деплою.
Якісний тест-сьют окупається за рахунок скорочення часу аудиту на десятки годин, що економить тисячі доларів. Наші клієнти економлять в середньому 30–40% бюджету на аудиті завдяки таким тестам.
За роки роботи ми протестували понад 30 смарт-контрактів для DeFi, NFT та інфраструктури. Гарантуємо, що ваш контракт отримає покриття, достатнє для проходження аудиту. Зв'яжіться з нами для безкоштовної оцінки вашого проєкту — ми підберемо оптимальний обсяг тестів.







