Тестирование смарт-контрактов (интеграционные тесты)

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

Направления блокчейн-разработки

Часто задаваемые вопросы

Последние работы

  • Разработка сайта компании B2B ADVANCE
    Разработка сайта компании B2B ADVANCE
    1530
  • Разработка веб-приложения для компании FEEDME
    Разработка веб-приложения для компании FEEDME
    1354
  • Разработка веб-сайта для компании БЕЛФИНГРУПП
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    1070
  • Разработка интернет магазина для компании FURNORO
    Разработка интернет магазина для компании FURNORO
    1328
  • Разработка логотипа компании B2B Advance
    Разработка логотипа компании B2B Advance
    772
  • Разработка веб-приложения для компании Enviok
    Разработка веб-приложения для компании Enviok
    1074

Интеграционное тестирование смарт-контрактов и его слепые зоны

Модульные проверки подтверждают, что отдельный контракт работает по своей спецификации. Ценность протокола рождается на стыках — между токеном и хранилищем, между расчётом и выплатой, между логикой и внешним источником данных. Интеграционное тестирование смарт-контрактов проверяет связку целиком в том порядке вызовов, в котором её увидит сеть Ethereum внутри одной транзакции.

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

Отдельная зона — порядок вызовов и повторные входы. Если выплата уходит внешнему адресу до обновления баланса, получатель успевает вызвать функцию заново и вывести больше, чем ему разрешено. Такая защита проверяется только на уровне цепочки вызовов, а не одного контракта.

Пропущенные ветки сценариев — вторая типичная дыра. Проверяют успешный путь и полный отказ, но не частичный: сбой на середине откатывает транзакцию целиком, а внешний сервис уже получил событие и записал операцию в свою базу. Расхождение между сетью и учётной системой обнаруживается на сверке, то есть слишком поздно.

Инциденты после обновления стоят дороже всего: логику меняют в новом контракте, а смежные модули продолжают обращаться к старому, и часть состояния остаётся неучтённой. Разбираем, что именно проверяем на связке и как ловим эти расхождения до выката.

Что даёт проверка сквозных сценариев до выкладки в основную сеть

Сквозной сценарий прогоняет всю цепочку — от вызова публичного метода до изменения состояния и события, которое читает внешний сервис. Мы поднимаем изолированный стенд с локальной копией состояния основной сети, фиксируем номер блока и разворачиваем всю связку контрактов. Это даёт воспроизводимость: один и тот же набор транзакций на одном снимке состояния даёт идентичный результат у любого инженера и в любой сборочной среде.

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

Так снимаются целые классы отказов: расхождение прав между ролями, неверный порядок вызовов при переводах между контрактами, повторный вход, переполнение при расчёте долей, несовпадение событий и фактического состояния. Доля дефектов, доходящих до пользователя, падает — они ловятся там, где исправление занимает часы, а не разбор инцидента в основной сети.

Обновление проверяется тем же контуром: сценарий применяет новую версию к снимку текущего состояния и прогоняет миграцию. Видно, что старые данные читаются корректно, а время простоя при обновлении ограничивается одной транзакцией замены, а не поиском причины сбоя после неё.

Форматы прогонов: локальный стенд, закрытая сеть, основная сеть

Выбор окружения определяет, какие дефекты прогон вообще способен поймать. Локальный стенд даёт полную изоляцию тестовых данных и секунды на цикл, но не воспроизводит поведение реальных пулов ликвидности, оракулов и загрузки сети. Закрытая сеть с теми же контрактами и снимками состояния закрывает середину: поведение близко к боевому, а данные не пересекаются с рабочими.

Что сравниваем

Вариант Когда подходит Ограничения окружения
Локальный стенд Логика контракта, права доступа, границы значений, регресс после каждого изменения кода Нет реальной загрузки, внешние вызовы подменяются заглушками
Закрытая сеть Сценарии между несколькими контрактами, миграции состояния, условия, зависящие от времени Нужно поднимать и сопровождать узлы, состояние пересобирается под каждый прогон
Прогон в основной сети Финальная проверка перед запуском: реальные внешние вызовы, достоверная оценка расхода газа Транзакции необратимы, изоляции тестовых данных нет — только чтение или отдельные аккаунты

Основной критерий выбора — изоляция тестовых данных. Прогон в основной сети оправдан, когда проверяется стыковка с внешними контрактами и фактическая стоимость операций, а не логика. Закрытая сеть обычно становится рабочим режимом для интеграционных сценариев: она единственная даёт и достоверность, и возможность откатить состояние.

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

Этапы работ: от разбора сценариев до тестирования на стенде

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

  1. Аудит сценариев — собираем пользовательские пути и роли: кто вызывает метод, какие лимиты и состояния при этом проверяются, где ожидаются отказы. На выходе — перечень сценариев с приоритетом по риску.
  2. Карта вызовов — фиксируем связи между контрактами, токенами и внешними источниками данных: кто кого вызывает, какие значения передаёт и что возвращает. Карта сразу показывает места, где тест обязан проверять и побочные изменения состояния.
  3. Схема сценариев — каждый путь превращаем в набор шагов с ожидаемым состоянием до и после вызова, чтобы расхождение читалось точечно, а не как «упал весь прогон».
  4. Настройка окружения — стенд повторяет целевую сеть по параметрам комиссий, порядку подтверждений и версиям зависимостей; отдельно поднимаем тестовую сеть для проверок, которые локально не воспроизводятся.
  5. Прогон и разбор расхождений — запускаем сценарии пакетами, логируем входы, состояние и события, каждое расхождение доводим до причины: ошибка в контракте, в окружении или в самом ожидании теста.
  6. Критерии приёмки — до финального прогона согласуем, какие сценарии обязательны и какой результат считаем успешным, чтобы приёмка не превратилась в переговоры.
  7. Передача — отдаём набор тестов, конфигурации стенда и отчёт с покрытием путей, чтобы прогон повторялся у вашей команды без нас.

Артефакты на выходе: наборы сценариев, отчёты, регламенты

Передаём не папку с тестами, а рабочий контур: запускаемый набор сценариев, воспроизводимые данные и правила, по которым ваша команда сама прогоняет и обновляет проверки после каждой правки контрактов. Ниже — что физически остаётся у вас в репозитории и в документации.

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

Кейс: как ушёл класс отказов при выпуске обновления

Заказчик выпускал обновления связки из шести контрактов: токен, пул, оракул, модуль голосования и два вспомогательных. Модули тестировали по отдельности, а совместное поведение проверяли руками на тестовой сети — выпуск занимал двое суток, и примерно треть релизов приходилось откатывать, потому что несовместимость интерфейсов всплывала уже после публикации.

Что изменили

Собрали единый прогон: фикстуры разворачивают всю связку в изолированной сети, включая обновление с сохранением накопленного хранилища, поэтому проверяется не только новый код модуля, но и совместимость с уже записанными данными. Добавили проверки инвариантов — сумма долей, права ролей, лимиты выпуска — они прогоняются после каждой операции, а не один раз в конце. Сценарий отката на предыдущую версию стал частью прогона, а не аварийной процедурой по инструкции.

Разворачивание и запуск построены на Hardhat: те же сценарии выполняются у инженера локально и в сборке — расхождений между средами нет.

Метрика До После
Доля релизов с откатом каждый третий один из четырнадцати
Время полной проверки связки двое суток вручную около сорока минут
Время отката на предыдущую версию три-четыре часа до десяти минут одним сценарием

Теперь релиз не ждёт окна в конце недели: прогон стартует на каждое изменение и падает на несовместимости до публикации. Откат перестал быть инцидентом с ручными шагами — это тот же сценарий из прогона, выполненный на боевой сети.

Что делать, если связка уже развёрнута и работает в основной сети?

Живая система отличается тем, что остановить её для проверки нельзя: балансы и хранилище меняются под руками. Проверка строится на копии — снимок состояния на фиксированном блоке разворачивается в изолированном узле, где воспроизводится любая последовательность вызовов без риска для основной сети. Этот же снимок служит точкой сравнения: после прогона сверяем балансы, события и хранилище с ожидаемыми значениями.

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

Отдельно проверяем обратную совместимость обновлений: старые записи в хранилище должны читаться новой логикой, а выпущенные события — остаться понятными индексаторам и кошелькам. В обновляемых контрактах порядок полей и слоты хранения фиксируем заранее на схемах OpenZeppelin: новое поле добавляется только в конец. Так уходит класс отказов, при которых после обновления обнуляются балансы или ломаются права доступа.

Выпуск идёт поэтапно: снимок, теневой прогон на копии, ограниченный запуск на доле трафика с лимитами и мгновенным откатом. Между этапами держим метрики — доля успешных вызовов и расхождение с эмуляцией. Если расхождений нет, версия раскатывается на всю систему; если есть — откат занимает один вызов, и пользователи его не замечают.

Частые технические сомнения перед стартом

Перед стартом заказчики задают один и тот же набор вопросов, и почти все они про границы: что именно проверяем, на какой сети, как это отражается на работающем продукте. Ниже — предметные ответы на те, что чаще всего тормозят решение.

Чем это отличается от проверки отдельных модулей?

Модульные проверки изолируют одну функцию. Интеграционный сценарий проходит путь целиком: вызов контракта, перевод активов, запись события, новое состояние. Тут всплывают неверные разрешения, порядок вызовов и повторный вход — то, что изоляция не видит.

Нужна ли реальная сеть?

Основной объём прогоняем на локальной копии состояния сети: окружение поднимается из конфигурации за минуты, один сценарий идёт секунды. Реальный контур нужен на финальном прогоне — он подтверждает поведение с настоящими адресами и лимитами.

Как понять, что покрытия достаточно?

Считаем не строки, а ветви сценариев: успешный путь, отказ по правам, отказ по лимитам, граничные суммы, откат. Если ветвь может остановить вывод средств — она покрыта тестом.

Как это влияет на работающий продукт?

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

Обсудим задачу: с чего начнём и что нужно с вашей стороны

Первый созвон по интеграционному тестированию смарт-контрактов — технический, а не презентационный: разбираем архитектуру протокола, границы между контрактами и внешним миром и сценарии, где ошибка стоит дороже всего. Часа обычно хватает, чтобы понять объём — сколько контрактов в связке, какие из них обновляемые, что вызывается извне и какие роли вправе менять состояние.

Чтобы разговор был предметным, материалы стоит подготовить заранее. От заказчика нужны:

  • репозиторий или архив контрактов вместе с существующими тестами, даже частичными;
  • описание протокола: роли, права доступа, логика обновления контрактов;
  • адреса развёрнутых контрактов и тестовых сетей, к которым есть рабочий доступ;
  • перечень внешних интеграций — оракулы, мосты, пулы, кастодиальные сервисы — и их интерфейсы;
  • ключи и узлы для тестовых сетей плюс сборочное окружение, если сценарии должны идти в конвейере;
  • журнал инцидентов и известных дефектов: что уже ломалось и при каких условиях воспроизводилось;
  • требования к приёмке — по каким сценариям считаем покрытие достаточным.

Разговор заканчивается не «спасибо, подумаем», а планом работ: этапы, состав локального стенда и копий основной сети, перечень сценариев с приоритетами, формат отчётов и оценка объёма — по срокам и загрузке команды. Старт проекта — сразу после согласования плана. Опишите задачу — вернёмся с планом и оценкой.