Аудит смарт-контрактов: где логика ломается до первого взлома
Смарт-контракт нельзя тихо исправить: после публикации логика исполняется ровно так, как написана, и ошибка в ней превращается в открытое приглашение. Аудит смарт-контрактов ищет не опечатки, а точки отказа, где один вызов выводит из протокола больше, чем вложено. Смотрим не только код, но и связи с оракулами цен, пулами ликвидности и кошельками пользователей.
Первая типовая точка — право вывода средств у одной роли. Если единственный адрес может забрать всю ликвидность без задержки, лимитов и второй подписи, цена ошибки в ключах равна цене всего пула. Такие права дробим на роли, добавляем очередь на исполнение и потолок разового вывода.
Вторая — порядок расчётов. Когда баланс обновляется после внешнего вызова, контракт успевает ответить на повторное обращение с прежним состоянием и выдать средства дважды. Тот же класс даёт округление в пользу вызывающего — на потоке транзакций это устойчивый риск вывода средств.
Третья — внешние вызовы без проверки результата: возвращённый отказ, устаревшая цена, пустой ответ проходят как успех, и протокол фиксирует сделку по неверным данным. Защита DeFi от взлома начинается с проверки каждого внешнего ответа и ограничения его по времени.
Базовые примитивы берём из проверенных библиотек — OpenZeppelin — и не пишем собственную арифметику там, где она уже прошла публичную проверку. Дальше разбираем, как эти уязвимости смарт-контрактов закрываются до релиза и что остаётся в отчёте.
Что меняется после проверки: снятые классы отказов и скорость правок
После проверки список открытых классов уязвимостей сокращается до нуля по критичным и высоким: повторный вход через внешние вызовы, обход контроля доступа к привилегированным методам, манипуляция ценой оракула на тонком пуле, ошибки округления при накоплении комиссий, зависимость результата от порядка транзакций. Каждый класс не просто назван — он подтверждён сценарием эксплуатации и закрыт правкой в конкретном методе.
Второй эффект — покрытие. Статический анализатор вроде Slither вылавливает формальные дефекты, но решающее значение имеют инварианты, которые прогоняются фаззингом на тысячах последовательностей вызовов: сохранность суммы балансов, монотонность индекса, недоступность вывода сверх доли. Доля путей, покрытых такими проверками, становится измеримой величиной, а не декларацией в отчёте.
Скорость выпуска правок меняется качественно: найденный сценарий атаки превращается в регрессионный тест, и исправленная версия проверяется повторным прогоном всего набора, а не ручным пересмотром изменённых строк. Путь от находки до версии с исправлением укладывается в один цикл правок вместо серии итераций «нашли — переписали — нашли снова».
Предсказуемость релизов держится на жёстком правиле: развёртывание не проходит при открытой критичной находке. Чек-лист проверок встроен в конвейер сборки, отчёт фиксирует статус по каждому классу уязвимостей, и решение о выпуске принимается по факту закрытия рисков, а не по ощущению готовности команды.
Аудит смарт-контрактов: ручной разбор и машинные проверки
Аудит смарт-контрактов не сводится к одному инструменту. Статический анализ контрактов быстро проходит весь код и вылавливает типовые дефекты — внешние вызовы до изменения состояния, незащищённый вывод средств, переполнения. Но анализатор не понимает замысел протокола: он не отличает намеренное делегирование прав от ошибки доступа, поэтому на каждый реальный дефект приходятся десятки ложных срабатываний анализаторов. Ручной разбор кода закрывает эту щель — проверяются экономика, порядок вызовов и граничные состояния по сценариям.
Форматы проверки отличаются по глубине и по тому, какой класс ошибок они вообще способны увидеть:
| Вариант проверки | Когда уместен | Ограничения |
|---|---|---|
| Ручной разбор кода | Логика протокола, экономика, права ролей, связки с внешними контрактами | Требует времени и квалификации, покрытие зависит от выбранных путей |
| Статический анализ контрактов | Первый проход по всей кодовой базе, поиск типовых уязвимостей, проверка в сборочном конвейере | Много ложных срабатываний, не видит логических ошибок |
| Формальная верификация | Критичные инварианты: сохранность балансов, лимиты выпуска, условия ликвидации | Нужна формальная спецификация; применима к ядру, а не ко всей системе |
| Повторная проверка после правок | Любое исправление, затрагивающее хранение состояния или внешние вызовы | Проверяет изменённые участки, требует оценки влияния на остальную систему |
На практике подходы идут связкой: машинный проход по всему репозиторию, ручной разбор критичных путей, формальная верификация ядра и повторная проверка после каждой правки. Так закрываются и типовые ошибки, и логические дефекты, которые инструмент не увидит в принципе.
Как проходит внедрение: от разбора кода до передачи отчёта
Проверка идёт последовательными этапами: сначала фиксируем, что контракт обязан делать, затем — как он может сделать не то. Без первых шагов аудит сводится к прогону готового набора правил, и часть путей атаки остаётся вне разбора.
- Сбор требований и границ. Уточняем логику, роли, места хранения средств и внешние вызовы; выписываем инварианты, которые нельзя нарушать ни при каких условиях.
- Схема контракта и ролей. Карта вызовов, прав и зависимостей между модулями показывает, где один участник может задеть чужие средства.
- Построчный разбор. Идём по схеме через код: арифметика и округления, проверки доступа, порядок обновления состояния, работа с внешними вызовами.
- Прогон сценариев атаки. Повторное вложение вызовов, манипуляция ценой, обгон транзакций, вывод сверх баланса — каждую гипотезу подтверждаем тестом, а не рассуждением.
- Исправление. С вашей командой вносим правки или отдаём точечные патчи, отмечая, где меняется логика, а не только защита.
- Контрольная проверка. Перепрогоняем сценарии и убеждаемся, что патч не открыл новый путь и не сломал инварианты из первого пункта.
- Передача отчёта. Находки с уровнем риска, путём воспроизведения и статусом исправления; при смене логики — повторная проверка после вывода в сеть.
Каждый этап оставляет след: видно, что проверено, что осталось и на чём основан вывод.
Что остаётся в проекте: конфигурации, схемы и регламенты
Аудит заканчивается не вердиктом «уязвимостей не найдено», а комплектом артефактов, с которым команда дальше работает без нас: ниже — что остаётся в репозитории после сдачи проверки.
- Отчёт о проверке — по каждому замечанию класс риска, путь исполнения, ссылка на функцию и минимальный сценарий воспроизведения. Формулировок «рекомендуем проверить» нет: либо конкретный вызов, либо пункт в отчёт не попал.
- Артефакты проверки — конфигурации статического анализа, сценарии фаззинга, инвариантные тесты и срезы покрытия. Комплект перезапускается одной командой, поэтому результат воспроизводим.
- Схемы доступа — карта ролей: кто выпускает активы, меняет параметры, замораживает операции и обновляет логику; роли с задержкой исполнения и кандидаты под мультиподпись отмечены отдельно.
- Политика изменений — какие правки требуют повторной проверки, а какие идут по упрощённому порядку; правила обновления логики через прокси и запрет на смену порядка полей в хранилище.
- Регламент повторных проверок — триггеры: правка расчётов, смена экономических параметров, расширение прав администратора, подключение внешнего источника данных.
- Документация по контракту — инварианты, событийная модель для мониторинга, перечень внешних вызовов и допущений, на которых держится логика.
- Журнал принятых рисков — замечания, осознанно оставленные без правок, с обоснованием и оценкой последствий: новой команде не придётся выяснять это заново.
Комплект снимает зависимость от подрядчика: следующую проверку можно провести своими силами или передать другой команде — точка входа одна.
Разбор случая: до и после устранения критичной уязвимости
Что было до исправления
Кейс проверки контракта начинался с двух аудитов у прежних подрядчиков: контракт собирался, статические анализаторы давали чистый отчёт. В логике вывода средств оставался повторный вызов — внешний контракт успевал вернуть управление раньше, чем обновлялся внутренний баланс. Класс описан в SWC Registry как взаимодействие до изменения состояния — ни один сканер его здесь не подсветил, потому что уязвимость проявлялась только в связке вызовов, а не внутри одной функции.
Что нашли и что изменили
Инженеры прошли поток вызовов от ввода средств до вывода и свели его в схему изменения балансов — на ней стало видно окно между списанием и записью. Сценарий инцидента с ликвидностью воспроизвели на локальной копии сети: серия вызовов вытягивала из пула больше, чем в него внесли, и делала это без единой ошибки в логах. Исправление свели к порядку «проверка — запись — внешний вызов», добавили защиту от повторного входа и регрессионный тест, который падает при возврате старого порядка операций.
| Показатель | До | После |
|---|---|---|
| Реакция на аномалию в пуле | часы | минуты |
| Доля путей вывода средств под тестами | 40% | 95% |
| Обнаружение класса «повторный вызов» | не выявлялся | закрыт тестом |
После устранения критичной уязвимости повторная проверка не нашла новых путей оттока, а мониторинг пула перевели на пороговые оповещения по объёму резервов. Откат на предыдущую версию контракта остался доступен — правки внесли так, чтобы состояние пула не требовало миграции.
Чем проверка перед запуском отличается от разбора после инцидента?
Проверка перед запуском — это работа с исходным кодом до того, как в контракте появились деньги. Инженеры идут по всем ветвям исполнения: права администратора, внешние вызовы, порядок обновления состояния, зависимости от внешних источников данных. Отдельно моделируются экономические сценарии — манипуляция курсом, вывод ликвидности, разгон стоимости токена.
Разбор после инцидента решает другую задачу: остановить потери и понять вектор. Порядок обратный — сначала следы транзакций в сети, потом восстановление последовательности вызовов, и только затем причина в коде. Если исходники недоступны или развёрнутый код отличается от них, анализировать приходится по байткоду.
Разница подходов — в целях, а не в инструментах. До запуска нужна полнота: покрыть все пути исполнения, описать классы риска и условия их реализации. После инцидента нужна скорость: два-три узла, через которые прошёл вывод средств, срочные меры — пауза, миграция, обновление контракта — и только затем полный разбор случившегося.
Повторная проверка обязательна в обоих режимах. Патч часто закрывает один вектор и открывает соседний, поэтому исправленный код проходит те же сценарии заново, а не просматривается на глаз. После инцидента найденные правила переносятся в превентивные проверки следующих релизов.
Частые технические сомнения перед началом работ
Сомнения перед аудитом редко касаются методологии — чаще спрашивают про границы работы: что реально проверяется в уже выложенном коде, где заканчивается автоматика и начинается ручной разбор, кто отвечает за находки после передачи. Ниже — ответы на то, что звучит на первом разборе.
Контракт уже развёрнут — проверять поздно?
Нет, хотя правки дороже. Сверяем код в сети с исходником, смотрим, обновляется ли логика через прокси и у кого права на изменение. Если дыра закрывается обновлением — готовим патч, если нет — считаем перенос состояния.
Автоматических анализаторов достаточно?
Нет. Они ловят шаблонные ошибки: обход проверок доступа, переполнения, неинициализированные переменные. Логику протокола так не увидеть — её разбираем вручную и прогоняем сценариями на копии сети.
Проверяете ли экономическую логику?
Да, отдельным треком: манипуляции с курсом, порядок ликвидаций, момент начисления вознаграждений, зависимость от внешнего источника цены. Корректный код с ошибочной экономикой теряет средства так же уверенно.
Что после правок по вашему отчёту?
Повторная проверка изменённых участков, а не всего кода заново: подтверждаем, что закрыты именно найденные пути и не появилось новых. Регрессионный набор тестов остаётся у команды.
Что с ответственностью после передачи?
Передаём отчёт с уровнями риска, тесты и схемы вызовов; ключами не владеем и протоколом не управляем. Отвечаем за методологию и воспроизводимость выводов, поэтому повторные проверки после каждого существенного обновления закладываем заранее.
Обсудим задачу: что уточнить перед началом проверки
Чтобы вернуться с оценкой сроков и планом работ, а не с уточняющими вопросами, нам достаточно нескольких вводных. Чем точнее описаны границы системы и модель ролей, тем меньше допущений в смете и тем выше шанс уложиться в дату запуска.
Что помогает обсудить задачу предметно:
- Границы проверки: какие контракты в периметре, а какие внешние вызовы — оракулы, мосты, пулы ликвидности — считаем доверенными и не разбираем.
- Схема ролей и доступов: у кого админ-функции, есть ли мультиподпись и задержка на критичные операции вроде вывода средств.
- История изменений и исходный код: как сопоставить репозиторий с развёрнутым в сети контрактом, чтобы логика совпадала со сборкой.
- Известные инциденты и допущения: что уже находили сами, что осознанно оставили как есть, какие места команда считает рискованными.
- Требования регуляторов и площадок: формат заключения, который примут партнёры или торговая площадка, и нужные разделы отчёта.
- Сроки запуска или следующего релиза: от даты зависит порядок работ — сначала критичные пути, затем остальная логика.
- Экономика протокола: как считаются доли, комиссии и ликвидации — расхождения в формулах чаще всего и открывают путь к потере средств.
После этого согласование объёма занимает один разговор: фиксируем, что входит в проверку, что остаётся за периметром, и в каком порядке выдаём результаты — по критичным путям раньше, чем по периферии.







