Перейти к содержимому

Статьи

Чек-лист готовности смарт-контракта к аудиту

30 проверок перед аудитом смарт-контракта: область и документация, тесты, фаззинг- и инвариант-тесты Foundry, разбор результатов Slither, контроль доступа, интеграции и развёртывание.

Инженерная команда sigmacode.io8 мин чтения

На этой странице (9)
  1. 1. Область аудита и документация
  2. 2. Гигиена кода и статический анализ
  3. 3. Тесты
  4. 4. Фаззинг и инвариант-тестирование
  5. 5. Контроль доступа и обновляемость
  6. 6. Внешние интеграции
  7. 7. Развёртывание и эксплуатация
  8. 8. Логистика аудита
  9. Как мы можем помочь

Аудит смарт-контракта дорог и жёстко ограничен по времени. Аудиторы получают фиксированное число дней, и каждый час, потраченный на то, чтобы понять, что код должен делать, догнать «движущийся» коммит или написать тесты, которые вы не написали, — это час, не потраченный на логику, которая действительно может привести к потере средств. В итоге отчёт о находках заполняется отсутствующим NatSpec, плавающими прагмами и непротестированными путями revert вместо проблем, за обнаружение которых вы платите.

Хорошо подготовленная кодовая база получает более глубокий аудит за тот же бюджет. Этот чек-лист собирает то, что мы подготавливаем перед передачей кода внешнему аудитору: 30 конкретных проверок по документации, гигиене кода, тестированию, контролю доступа, интеграциям, развёртыванию и логистике. Он не заменяет аудит — он гарантирует, что время аудита уходит туда, где это важно.

1. Область аудита и документация#

Аудиторы могут проверять поведение только относительно понятного им замысла, поэтому замысел должен быть записан.

  • Заморозьте хеш коммита. Передайте аудиторам один помеченный тегом коммит и не меняйте проверяемый код в течение аудита. Исправления идут в отдельной ветке и проверяются в раунде fix-review.
  • Опубликуйте список файлов в области аудита с nSLOC. Перечислите каждый контракт в области с его путём и нормализованным числом строк исходного кода и явно укажите, что вне области (библиотеки, моки, скрипты, ранее аудированные файлы). Аудиторы оценивают и планируют на основе nSLOC, поэтому точный подсчёт избавляет обе стороны от сюрпризов.
  • Напишите обзор архитектуры. Одна-две страницы с описанием контрактов, того, как они вызывают друг друга, какие контракты хранят средства, и основных пользовательских сценариев. Простая диаграмма вызовов и потоков токенов экономит аудиторам часы обратного инжиниринга.
  • Опишите поведение и инварианты прозой. Опишите, что должна делать каждая внешняя функция, и свойства, которые должны выполняться всегда, например «сумма балансов пользователей равна totalAssets» или «только таймлок может менять комиссии». Эти утверждения становятся основой и для ревью аудиторов, и для ваших собственных инвариант-тестов.
  • Задокументируйте известные проблемы и принятые риски. Перечислите уже известные вам ограничения и осознанные проектные решения — например, компромиссы централизации или неподдерживаемые типы токенов. Это убирает их из отчёта о находках и показывает аудиторам, где вы уже всё продумали.

2. Гигиена кода и статический анализ#

Шум в кодовой базе стоит времени аудита и порождает малозначимые находки, поэтому уберите его до того, как его увидят аудиторы.

  • Зафиксируйте компилятор и добейтесь нуля предупреждений. Используйте точную pragma solidity 0.8.x; вместо плавающей ^0.8.0 и сделайте так, чтобы версия, число прогонов оптимизатора, via_ir и evm_version в foundry.toml совпадали с тем, что вы будете развёртывать. Каждое предупреждение компилятора должно быть исправлено или осознанно объяснено.
  • Зафиксируйте и перечислите зависимости. Заблокируйте OpenZeppelin Contracts и каждую другую библиотеку на точный релиз — например, подмодуль с тегом v5.x или точная версия в package.json. Перечислите все зависимости в документации к аудиту и пометьте любые скопированные или изменённые файлы.
  • Удалите мёртвый код, TODO и отладочный вывод. Уберите неиспользуемые функции, закомментированный код, импорты console.log и остатки тестовых хелперов из контрактов в области аудита. Открытые TODO сигнализируют о незавершённой логике и будут отражены в отчёте как таковые.
  • Заполните NatSpec для внешних и публичных функций. У каждой внешней и публичной функции должны быть @notice, @param, @return и, где уместно, заметка о контроле доступа и revert'ах. Используйте кастомные ошибки и эмитьте события для каждого изменения состояния, важного вне чейна.
  • Запустите Slither и разберите каждую находку. Прогоните slither на замороженном коммите и классифицируйте каждую находку как исправленную, ложноположительную или принятую — с обоснованием в одну строку. Второй инструмент, например Aderyn, часто ловит другие паттерны, а предоставление разобранного вывода показывает аудиторам, какие автоматические находки уже обработаны.

3. Тесты#

Тесты показывают аудиторам, как предполагается использовать код, и позволяют им быстро писать proof-of-concept.

  • Покройте каждую внешнюю функцию и опубликуйте отчёт. Для каждой внешней и публичной функции нужен хотя бы один тест счастливого пути. Сгенерируйте отчёт о покрытии строк и веток через forge coverage и объясните любые непокрытые ветки вместо того, чтобы их скрывать.
  • Тестируйте пути revert и граничные случаи. Проверяйте, что неавторизованные вызовы, невалидные параметры, нулевые суммы и граничные значения откатываются с ожидаемой кастомной ошибкой через vm.expectRevert. Непротестированные пути revert — то место, где прячутся ошибки валидации и контроля доступа.
  • Запускайте форк-тесты против реальных интеграций. Если протокол взаимодействует с внешними контрактами — DEX, кредитными рынками или оракулами, — тестируйте на форке мейннета или L2 с зафиксированными номерами блоков. Моки доказывают лишь, что ваш код работает с вашими предположениями о другой стороне.

4. Фаззинг и инвариант-тестирование#

Фаззинг находит комбинации входных данных, о которых никто не подумал, а инвариант-тесты проверяют, что система остаётся консистентной на последовательностях вызовов.

  • Фаззьте каждую функцию с числовым вводом. Пишите фаззинг-тесты Foundry для сумм, меток времени, расчётов долей и математики комиссий и ограничивайте входы через bound(), а не чрезмерный vm.assume. Направление округления и переполнение на экстремальных значениях — типичные цели.
  • Пишите стейтфул-инвариант-тесты с хендлерами. Используйте инвариант-тестирование Foundry с контрактами-хендлерами, которые вызывают систему в реалистичных последовательностях от нескольких акторов. Проверяйте платёжеспособность, сохранение ценности и свойства контроля доступа после каждой последовательности вызовов.
  • Держите записанные инварианты и тестовый набор синхронизированными. Каждый инвариант из вашей спецификации должен соответствовать инвариант-тесту, и каждый инвариант-тест должен отслеживаться до спецификации. Запускайте набор с осмысленным числом прогонов и глубиной в CI, а не только локально с настройками по умолчанию.

5. Контроль доступа и обновляемость#

Привилегированные функции и обновления могут переместить или заблокировать любой актив в системе, поэтому модель доверия должна быть явной.

  • Перечислите каждую привилегированную роль и функцию. Задокументируйте каждую роль, какие функции она может вызывать, кто её держит и каков худший сценарий при компрометации её ключа. Это раздел о допущениях доверия, который аудиторы попросят первым.
  • Поместите административную власть за мультисиг и таймлок. Критические параметры и обновления должны контролироваться мультисигом вроде Safe, с TimelockController или эквивалентной задержкой для изменений, затрагивающих средства пользователей. Задокументируйте порог подписантов и задержку.
  • Проверьте лейаут хранилища для обновляемых контрактов. Для прокси используйте обновляемые контракты OpenZeppelin v5 с namespaced-хранилищем ERC-7201 и валидируйте обновления инструментарием OpenZeppelin upgrades. Приложите diff лейаута хранилища между версиями, если обновление входит в область аудита.
  • Защитите инициализаторы и функции обновления. Вызывайте _disableInitializers() в конструкторе имплементации, корректно используйте модификаторы initializer и reinitializer и ограничивайте _authorizeUpgrade для UUPS-прокси (ERC-1822). Незащищённый контракт имплементации — находка, которую аудиторам не должно приходиться сообщать.

6. Внешние интеграции#

Каждый внешний вызов — это предположение о чужом коде, и каждое такое предположение должно быть записано и протестировано.

  • Обрабатывайте устаревание и сбои оракулов. Проверяйте updatedAt относительно heartbeat фида, отклоняйте нулевые и отрицательные цены и на L2 проверяйте фид аптайма секвенсора. Решите, что делает протокол, когда оракул недоступен, и задокументируйте это.
  • Учитывайте особенности токенов. Укажите, какие типы токенов поддерживаются, и обрабатывайте или явно исключайте токены с комиссией на перевод, ребейзинговые, с числом знаков не 18 и нестандартные ERC-20. Используйте SafeERC20 для переводов и измеряйте разницу балансов там, где важна фактически полученная сумма.
  • Нанесите на карту поверхности реентерабельности. Перечислите каждый внешний вызов и перевод токенов, следуйте checks-effects-interactions и используйте ReentrancyGuard (или ReentrancyGuardTransient с EIP-1153) там, где состояние общее. Учитывайте межфункциональную и read-only реентерабельность, а также коллбэки от токенов ERC-721, ERC-1155 и ERC-777.
  • Учитывайте MEV и фронтраннинг. Добавляйте лимиты проскальзывания и дедлайны к свопам и депозитам, защищайте хранилища ERC-4626 от инфляционной атаки первого депозитора и проверяйте любую логику, зависящую от порядка транзакций. Задокументируйте, какие риски упорядочивания вы принимаете.

7. Развёртывание и эксплуатация#

Аудированный код настолько же безопасен, насколько безопасен способ его развёртывания и эксплуатации.

  • Сделайте скрипты деплоя воспроизводимыми, а параметры — задокументированными. Деплойте скриптами Foundry из замороженного коммита, держите каждый параметр конструктора и инициализатора в конфиге под версионным контролем и включайте скрипты в область аудита или хотя бы в передачу материалов. Корректный контракт, развёрнутый с неверными параметрами, всё равно сломан.
  • Отрепетируйте на тестнете с верифицированными исходниками. Прогоните точный скрипт развёртывания на тестнете и на форке мейннета, верифицируйте исходники в блок-эксплорере и проверьте, что роли оказались у предусмотренных адресов. Дайте аудиторам адреса в тестнете, чтобы они могли взаимодействовать с реальным развёртыванием.
  • Подготовьте паузу, ранбук и мониторинг. Решите, кто что может ставить на паузу, напишите короткий ранбук по инцидентам и настройте оповещения об изменениях ролей, обновлениях, крупных выводах и состояниях паузы. Аудиторы будут проверять аварийные пути, поэтому они должны существовать до аудита.

8. Логистика аудита#

Хорошая логистика поддерживает движение аудита и гарантирует, что раунд исправлений действительно используется.

  • Назначьте технический контакт и канал связи. Выделите одного разработчика, знающего кодовую базу и способного отвечать на вопросы в течение часов, и договоритесь об общем канале на время аудита. Каждый вопрос без ответа тормозит ревью.
  • Зарезервируйте время на исправления и раунд fix-review. Запланируйте ёмкость разработчиков сразу после получения отчёта и заранее договоритесь, что аудиторы проверяют исправления. Непроверенные исправления могут внести новые баги уже после подписания аудита.
  • Раскройте предыдущие аудиты и ревью. Поделитесь более ранними отчётами аудита, статусом их исправлений и любыми внутренними ревью. Тогда аудиторы смогут сфокусироваться на том, что изменилось, и проверить, не регрессировали ли прежние находки.

Как мы можем помочь#

Мы предлагаем Audit-Readiness Sprint с фиксированным объёмом длительностью одну-две недели: модель угроз, анализ пробелов в тестовом наборе, фаззинг- и инвариант-тесты Foundry, разбор Slither, приоритизированный список исправлений и пакет передачи аудиторам с областью, документацией и известными проблемами. Мы не аудиторская фирма, и спринт не заменяет внешний аудит; он готовит ваш код так, чтобы аудиторы могли тратить время на логику. Подробности — на нашей странице цен и в разделе услуг Blockchain & Web3.

Если хотите бесплатную оценку для вашей кодовой базы, расскажите нам о своём проекте. Мы с удовольствием подпишем взаимный NDA до того, как вы поделитесь каким-либо кодом.

Есть проект в голове?

Расскажите, что строите. В рабочие дни senior-инженер отвечает в течение часа — затем вы получаете честную оценку, понятный объём и предложение с фиксированной ценой или по вехам, обычно за несколько рабочих дней.

Сначала написать? Напишите нам

Вы будете говорить напрямую с Ing. Ismet Mesic, Tech lead.