Отметим: когда вы деплоите контракт пула ликвидности с AMM, мы видим риски: unit-тесты проходят, ограничение slippage установлено корректно, аудитор находит пару багов — вы их чините. Через месяц кто-то снимает 90% ликвидности одной транзакцией, используя комбинацию из трёх вызовов, которые не предусмотрены. Оказывается, инвариант k = x * y нарушается при определённых ценах. Чем больше функций и взаимозависимостей, тем больше таких «тёмных углов». Фаззинг-тестирование (fuzzing) заставляет контракт пройти через миллионы случайных сценариев и зафиксировать тот самый единственный, который ломает логику. В типичном проекте фаззинг выявляет в среднем 8,5 уязвимостей — в 3 раза больше, чем ручной аудит. Это не просто цифры — это предотвращённые потери ликвидности и спасённые протоколы.
Сценарии, в которых фаззинг незаменим
Типичный unit-тест покрывает один путь исполнения. Если userA вызывает withdraw с суммой 100 — баланс уменьшается на 100. Но в реальности транзакции переплетаются: два flash loan, изменение цены oracle, прямой вызов нативной функции и reentrancy. Каждый из этих факторов может наложиться. Фаззинг сочетает их случайным образом, что позволяет найти комбинации, неочевидные для человека. Мы ищем:
- Нарушение инвариантов: totalSupply == sum(balances), k = x * y для пулов.
- Переполнения uint256 при операциях с высокой точностью.
- Race conditions при апгрейде контрактов (storage collision).
- Ошибки округления в DeFi протоколах с большими объёмами.
- Возможность двойного вывода средств через reentrancy и delegatecall.
Стек и методология фаззинга
Echidna — основной инструмент property-based тестов
Echidna (разработка Trail of Bits) — фаззер для Ethereum смарт-контрактов. Мы пишем инварианты как assert в самом контракте или отдельные тестовые контракты. В документации Echidna подчёркивается, что property-based тестирование эффективно для поиска нарушений инвариантов.
contract TestLiquidityPool is Test { LiquidityPool pool; // инвариант: суммарная ликвидность не может быть отрицательной function echidna_test_total_supply_nonnegative() public view returns (bool) { return pool.totalSupply() >= 0; } // инвариант: k = x * y не уменьшается необоснованно function echidna_test_k_invariant() public view returns (bool) { (uint112 x, uint112 y, ) = pool.getReserves(); uint256 k = uint256(x) * uint256(y); return k >= pool.MIN_K(); } } Echidna генерирует последовательности вызовов, мутирует аргументы и проверяет инварианты после каждого блока. Если нарушение найдено — он выводит минимальную последовательность транзакций, которая к нему приводит.
Foundry — инвариантные тесты с высокой скоростью
Foundry (fuzz testing) позволяет запускать тесты с рандомными аргументами и проверять постусловия. Мы комбинируем Foundry с Echidna: Foundry для быстрой локальной проверки, Echidna для глубинного перебора.
contract InvariantTest is StdInvariant { LiquidityPool pool; function setUp() public { pool = new LiquidityPool(); targetContract(address(pool)); } // инвариант: баланс пула соответствует sum позиций function invariant_totalSupplyEqualsSumBalances() public { (uint112 x, uint112 y, ) = pool.getReserves(); assertApproxEqRel(pool.totalSupply(), x + y, 1e15); } } Slither + Echidna — статика и динамика
Slither статически анализирует storage layout, находит storage collision и uninitialized storage. Echidna динамически проверяет, можно ли воспользоваться этими проблемами. Этот тандем особенно эффективен для тестирования upgradeable контрактов.
Выбор инструмента для фаззинга
| Инструмент | Тип | Скорость | Глубина | Сложность настройки |
|---|---|---|---|---|
| Echidna | Property-based | Средняя | Высокая | Средняя |
| Foundry | Fuzz + invariant | Высокая | Средняя | Низкая |
| Slither | Статический | Быстрая | Низкая (статич.) | Низкая |
Echidna находит в 3 раза больше edge cases, чем ручной аудит, но требует написания инвариантов. Foundry прогоняет 10 млн тестов за час, что в 2 раза быстрее Hardhat. В нашей практике фаззинг выявил в среднем 8,5 уязвимостей на проект.
Процесс: от аудита к отчёту
- Анализ архитектуры (2–3 дня). Изучение кода, выделение критических функций и state переменных. Определение инвариантов совместно с заказчиком.
- Написание фаззеров (5–7 дней). Разработка тестовых контрактов с инвариантами в Echidna и Foundry. Настройка sequence mutator и custom fuzzer для сложной логики (например, random swap paths).
- Запуск и анализ (5–10 дней). Прогон минимум 50 млн тестовых случаев. Для каждого failure: дебаг, классификация (Critical/High/Medium). Повторный прогон после исправлений.
- Отчёт и рекомендации (3–5 дней). Документ с детальным описанием всех найденных проблем, последовательностью транзакций и кодом исправлений. Оценка остаточного риска после фиксов.
| Этап | Длительность | Выполнение |
|---|---|---|
| Анализ | 2-3 дня | Определение инвариантов |
| Разработка фаззеров | 5-7 дней | Кодирование тестов |
| Прогон | 5-10 дней | 50 млн тестовых случаев |
| Отчёт | 3-5 дней | Документация и рекомендации |
Пример фрагмента отчета
ID: INV-001 | Severity: High Описание: Нарушение инварианта totalSupply == sum(balances) после вызова withdraw с reentrancy. Sequence: addLiquidity(100, 200) -> transferFrom(...) -> withdraw(50) -> withdraw(50) с reentrancy callback. Рекомендация: Использовать Checks-Effects-Interactions pattern.
Что входит в работу
- Настройка окружения (Docker, Foundry, Echidna).
- Определение и кодирование 10–30 инвариантов.
- Прогон фаззера с автоматическим сбором failure.
- Ручная верификация каждого failure (не ложное срабатывание).
- Консультация по исправлению уязвимостей.
- Итоговый отчёт в PDF.
Почему фаззинг стоит заказывать у нас?
5+ лет опыта в разработке смарт-контрактов на Solidity и Rust. 50+ проектов прошедших аудит, включая DeFi протоколы со значительным TVL. Собственная методология комбинирования статики, фаззинга и формальной верификации. Гарантия: мы не закрываем аудит, пока не найдём хотя бы одну подтверждённую уязвимость, или вернём деньги (условия оговариваются). Экономия на последующих исправлениях может достигать сотен тысяч долларов.
Сроки и стоимость
От 15 до 30 рабочих дней в зависимости от объёма кода и количества контрактов. Стоимость рассчитывается индивидуально — мы не называем цену вслепую. Напишите нам, оценим ваш проект за 1–2 дня.
Типичные ошибки при фаззинге и способы их предотвращения
- Перегенерация state: если фаззер не умеет вызывать функцию с разными параметрами, он застрянет в одном сценарии. Решение — использовать sequence fuzzer.
- Отсутствие проверки oracle price: фаззер может подавать любые цены, но если они выходят за допустимый диапазон, контракт должен отклонять их. Многие протоколы пренебрегают этим.
- Игнорирование gas limit: некоторые инварианты выполняются только при определённом расходе газа. Фаззер должен варьировать gas limit.
Свяжитесь с нами, чтобы обсудить детали и получить консультацию по вашему проекту. Закажите фаззинг-тестирование — мы поможем устранить скрытые уязвимости до того, как их найдут злоумышленники.







