Рефакторинг смарт-контрактов: аудит, gas-оптимизация и безопасность

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

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

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

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

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

Рефакторинг смарт-контрактов: где утекает газ и логика

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

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

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

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

Что меняется после переработки: скорость вызовов и расход газа

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

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

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

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

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

Подходы к переработке: замена, частичная правка, обёртка

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

Вариант Когда подходит Ограничения
Полная замена Логика устарела целиком, состояние компактное и переносится одним проходом Новый адрес: нужна миграция балансов, разрешений и истории; внешние интеграции обновляют ссылки вручную
Частичная правка логики Контракт уже собран из обновляемых модулей, проблема локализована в одном из них Недоступна для неизменяемого кода; после правки перепроверяется весь граф зависимостей модуля
Контракт-обёртка Ядро стабильно, меняются правила доступа, лимиты или формат событий Делегирующий вызов добавляет расход газа на каждом пути; нужен контроль слотов хранения и корректная инициализация

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

Этапы работы: от разбора логики до передачи заказчику

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

  1. Аудит кода и прав. Снимаем схему взаимодействия контрактов: внешние вызовы, проверки ролей, точки изменения параметров и вывода средств. Опасные места — неограниченные внешние вызовы, отсутствие проверок адресов — попадают в отчёт с оценкой последствий.
  2. Проектирование схемы. Описываем, как контракты будут связаны после переработки: что выносим в отдельное хранилище, какие роли получают доступ к административным функциям. Схему согласуем до первой правки.
  3. Переработка кода. Меняем логику по частям, каждое изменение — отдельная правка с описанием. Стандартные участки закрываем проверенными библиотеками (OpenZeppelin), а не самописными решениями.
  4. Тестирование изменений. Покрываем сценариями каждый переписанный путь: обычные операции, граничные суммы, повторные вызовы, отказ внешнего адреса. На тех же данных новая логика должна давать тот же результат, что старая.
  5. Проверка прав доступа. Прогоняем каждый метод от имени каждой роли — гость, пользователь, оператор, администратор. Запрещённые действия должен отклонять контракт, а не интерфейс.
  6. Независимая проверка. Изменённый код смотрит инженер, в переработке не участвовавший, плюс статический анализ на типовые классы уязвимостей.
  7. Приёмка работ. Передаём код, схему, отчёт о проверках и порядок отката — как вернуть предыдущее состояние, если после релиза что-то пойдёт не так.

Что остаётся у заказчика: код, схемы и регламенты

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

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

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

Кейс: переработка логики распределения выплат

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

Что изменили в логике

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

Метрики до и после

До: потолок в 90–110 получателей на вызов, раунд — до 12 ручных прогонов, откат любого прогона отменял предыдущие. После: пакет в 250 адресов проходит стабильно, стоимость одной выплаты снизилась на 45%, раунд закрывается одним проходом, доля отказов по лимиту газа — ноль на прогоне в 1 200 участников. Журнал событий даёт полный след по каждому начислению, а модуль реестра теперь меняется отдельно от расчётной логики и не затрагивает выплатной контур.

Чем рефакторинг отличается от написания с нуля

Рефакторинг смарт-контрактов — это переработка уже развёрнутого кода с сохранением адреса и накопленного состояния. Написание с нуля — это новый контракт и перенос в него данных и всех ссылок. Выбор между стратегиями определяется не объёмом правок, а тем, сколько внешних систем и балансов завязано на текущий адрес.

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

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

Вариант Когда подходит Ограничения
Переработка текущего контракта Логика рабочая, дефекты и издержки локальны, внешние интеграции менять нельзя Наследует структуру хранилища и принятые решения; часть улучшений недоступна
Новый контракт с миграцией Меняется модель данных, роли или порядок расчётов Требует переноса состояния и обновления адресов у всех, кто их использует; риск при замене выше

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

Технические сомнения перед стартом работ

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

Контракт уже развёрнут — обновление ещё возможно?

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

Как обновить логику, не перенося состояние?

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

Как проверить, что поведение не изменилось?

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

Кто имеет право обновлять контракт?

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

Где заканчивается зона ответственности подрядчика?

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

Обсудим задачу: вернёмся с оценкой и планом работ

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

Чтобы вернуться с предметной оценкой, а не вилкой «плюс-минус», достаточно прислать:

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

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

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