Проверить смарт-контракт: выбираем сканер под ваши задачи

Анализ инцидентов безопасности в EVM-совместимых сетях показывает, что большинство эксплойтов DeFi-протоколов происходит из-за известных паттернов уязвимостей, которые можно детектировать еще на этапе компиляции.

Проверить смарт-контракт: выбираем сканер под ваши задачи

Описывая методологию того, как проверить выбираем сканер под ваши задачи информационно-аналитический хаб о блокчейн-технологиях web3 Cryptocoinlabs предлагает опираться на триаду: статический анализ, символьное исполнение и формальную верификацию. Ни один инструмент не обеспечивает абсолютной защиты, но правильная комбинация сканеров снижает вероятность критического сбоя до приемлемого минимума. Чтобы качественно проверить выбираем сканер под ваши задачи, учитывая архитектурную сложность смарт-контракта, необходимо разобрать внутреннюю механику работы каждого класса решений.

---

Архитектура безопасности: почему автоматизация — это только первый эшелон защиты

Автоматизированные сканеры оперируют набором правил и эвристик. Они эффективны для быстрого поиска стандартных уязвимостей, но пасуют перед сложной бизнес-логикой. Например, сканер легко обнаружит отсутствие модификатора `onlyOwner` у критической функции, но не сможет понять, что формула расчета процентной ставки в вашем лендинговом протоколе допускает манипуляцию ценовым оракулом.

Подобно тому, как при проектировании сложных систем инженеры используют специализированные измерительные приборы (подробнее о настройке звукового оборудования можно почитать на avtozvuk.cc), разработчики смарт-контрактов обязаны применять строгие математические методы анализа кода.

Уровень безопасности 100% недостижим только автоматизированными средствами. Любой автоматический аудит — это лишь синтаксический и частично семантический фильтр. Он отсекает грубые ошибки, освобождая время аудитора для ручного анализа архитектурных уязвимостей.

Класс инструментаПринцип работыСкорость работыЭффективность поиска логических ошибок
Статические анализаторы (Slither)Анализ AST и промежуточного представления (IR) без запуска кодаВысокая (секунды)Низкая (только известные паттерны)
Символьные исполнители (Mythril)Исследование путей выполнения с помощью математических формулСредняя (минуты/часы)Средняя (находит недостижимые состояния)
Формальная верификация (Certora)Доказательство математических инвариантов кодаНизкая (требует написания спецификаций)Высокая (в рамках заданной спецификации)

Основная проблема автоматизации — баланс между ложноположительными (false positives) и ложноотрицательными (false negatives) срабатываниями. Статические анализаторы склонны заваливать разработчика предупреждениями низкой критичности, в то время как инструменты символьного исполнения могут пропустить уязвимость из-за проблемы «взрыва состояний» (state explosion).

---

Статический анализ кода: возможности Slither и обнаружение 70+ типов уязвимостей

Slither — это статический анализатор Solidity-кода, написанный на Python. Он транслирует исходный код смарт-контракта в промежуточное представление под названием SlithIR. Это позволяет применять методы анализа потока данных (data-flow analysis) и строить граф управления (Control Flow Graph, CFG).

Инструмент из коробки детектирует более 70 типов уязвимостей. Ключевое преимущество Slither — скорость. Анализ кодовой базы объемом в несколько тысяч строк занимает менее пяти секунд.

```

// Пример уязвимого кода, который Slither детектирует мгновенно

function withdraw(uint256 amount) public {

require(balances[msg.sender] >= amount);

msg.sender.call{value: amount}(""); // Slither укажет на отсутствие ReentrancyGuard

balances[msg.sender] -= amount;

}

```

Slither строит дерево абстрактного синтаксиса (AST) и отслеживает зависимости между переменными. Это позволяет ему находить:

* Незащищенные функции записи: когда внутренние переменные состояния могут быть изменены внешними вызовами.

* Shadowing переменных: объявление локальных переменных, перекрывающих глобальные.

* Неиспользуемые переменные и мертвый код: оптимизирует оверхед по газу при деплое.

* Reentrancy (повторный вход): фиксирует состояние, когда внешний вызов происходит до изменения локального баланса.

Slither — это базовый гигиенический минимум. Если ваш контракт не проходит проверку этим анализатором, отправлять его в тестнет — безответственность.

Однако Slither не анализирует EVM-байткод напрямую. Он полагается на структуру компилятора Solidity. Если уязвимость кроется в специфике оптимизатора компилятора (как это было с некоторыми версиями Yul-оптимизатора), статический анализатор исходного кода ее не заметит.

---

Символьное исполнение и EVM-байткод: как Mythril находит скрытые логические ошибки

Mythril использует концепцию символьного исполнения (symbolic execution) для анализа байткода EVM. Вместо конкретных значений переменных (например, `uint256 amount = 100`) инструмент подставляет символические значения ($S_{amount}$) и строит систему алгебраических ограничений для каждого пути выполнения программы.

Инструмент запускает виртуальную машину (Laser EVM) и пытается найти входные данные, которые могут привести к выполнению нежелательных инструкций (например, `SELFDESTRUCT` или запись в произвольную ячейку памяти через `SSTORE`).

```

[Исходный байткод] -> [Laser EVM] -> [Символьное дерево путей] -> [SMT-решатель Z3] -> [Отчет об уязвимости]

```

Для решения систем ограничений Mythril использует SMT-решатель Z3. Это позволяет находить сложные логические цепочки, которые пропускает Slither. Например, если для вызова уязвимой функции требуется выполнить три последовательные транзакции с определенными параметрами, Mythril построит этот путь.

Недостатки Mythril:

1. Проблема взрыва состояний: количество путей выполнения растет экспоненциально с ростом ветвления кода (операторы `if`, циклы `for`). При глубокой вложенности инструмент зависает или завершает работу по таймауту.

2. Высокий уровень оверхеда: анализ одного контракта средней сложности может занимать от 15 минут до нескольких часов.

3. Сложность интерпретации результатов: отчеты Mythril часто содержат трассировки байткода, которые трудно сопоставить с исходным кодом на Solidity без детального знания структуры EVM.

---

Формальная верификация и мониторинг: когда стандартных сканеров становится недостаточно

Когда речь идет о протоколах, управляющих миллиардными объемами активов, стандартных сканеров недостаточно. Здесь применяется формальная верификация — математическое доказательство того, что код ведет себя в строгом соответствии с заданной спецификацией.

Лидером в этой области является Certora Prover. Разработчик пишет спецификацию на языке CVL (Certora Verification Language), описывая инварианты — правила, которые никогда не должны нарушаться.

*Пример инварианта:* «Сумма балансов всех пользователей всегда должна быть равна значению переменной `totalSupply`».

```cvl

// Пример спецификации Certora (CVL)

invariant totalSupplyEqualsSumOfBalances()

totalSupply() == sum(balances);

```

Certora транслирует контракт и спецификацию в математическую формулу. Если существует хотя бы один набор входных данных и состояний (даже теоретически невозможный при нормальной работе), нарушающий инвариант, система генерирует контрпример.

Для динамического контроля безопасности в реальном времени (runtime monitoring) применяется Forta. Это децентрализованная сеть агентов безопасности, которые сканируют каждый блок на предмет аномалий (например, резкое изменение баланса пула или нетипичный объем флеш-лоанов). Forta не предотвращает деплой уязвимого кода, но позволяет вовремя остановить контракт (pause), если атака уже началась.

Формальная верификация не гарантирует отсутствие багов в спецификации. Вы доказываете соответствие кода вашим математическим ожиданиям, но если сами ожидания ошибочны — контракт все равно взломают.

---

Интеграция в CI/CD: как выстроить пайплайн проверки контрактов перед деплоем

Автоматическая проверка должна быть непрерывной. Ручной запуск сканеров перед деплоем малоэффективен, так как разработчики склонны игнорировать предупреждения в спешке. Пайплайн должен быть интегрирован в GitHub Actions или GitLab CI.

Рекомендуемая архитектура пайплайна:

1. Компиляция и линтинг: проверка синтаксиса с помощью Solhint.

2. Модульное тестирование: покрытие кода тестами на базе Foundry или Hardhat (целевое покрытие — не менее 95%).

3. Статический анализ (Slither): блокирует слияние pull-request при обнаружении уязвимостей уровня High и Medium.

4. Символьное исполнение (Mythril): запускается на ночных сборках (nightly builds) из-за длительного времени работы.

5. Верификация контрактов: автоматическая публикация исходного кода на Etherscan через Sourcify или Blockscout для прозрачности.

Пример конфигурации шага для Slither в GitHub Actions:

```yaml

  • name: Run Slither Action

uses: crytic/slither-action@v0.3.0

with:

node-version: 16

sarif: results.sarif

fail-on: low

```

Использование флага `fail-on: low` гарантирует, что сборка упадет при обнаружении любой уязвимости с уровнем критичности от низкого и выше. Исключения (false positives) должны обрабатываться точечно с помощью аннотаций `// slither-disable-next-line` непосредственно в коде с обязательным документированием причин отключения правила.

---

Технический вердикт

Наш опыт аудита смарт-контрактов показывает: ни один сканер не является универсальным решением. Попытка заменить ручной аудит автоматическим сканированием — это гарантированный путь к потере средств.

1. Для ежедневной разработки: используйте Slither. Он дешев в интеграции, работает мгновенно и отсекает 90% детских ошибок разработки (shadowing, reentrancy в простых функциях).

2. Перед аудитом: запускайте Mythril на глубоком уровне сканирования (`--max-depth 20`). Дайте ему поработать несколько часов. Это позволит выявить скрытые переходы состояний, которые не видны при статическом анализе.

3. Для критически важных модулей (мосты, оракулы, управление ликвидностью): пишите математические инварианты и прогоняйте их через Certora или Manticore. Если математика инвариантов не сходится, код выпускать нельзя.