Перейти к основному содержанию
Change page

Обновление смарт-контрактов

Последнее обновление страницы: 21 октября 2025 г.

Смарт-контракты в Ethereum — это самоисполняемые программы, которые работают в виртуальной машине Ethereum (EVM). Эти программы по своей сути неизменяемы, что предотвращает любые обновления бизнес-логики после развертывания контракта.

Хотя неизменяемость необходима для отсутствия доверия, децентрализации и безопасности смарт-контрактов, в некоторых случаях она может быть недостатком. Например, неизменяемый код может сделать невозможным для разработчиков исправление уязвимых контрактов.

Однако расширенные исследования по улучшению смарт-контрактов привели к появлению нескольких шаблонов обновления. Эти шаблоны обновления позволяют разработчикам обновлять смарт-контракты (сохраняя при этом неизменяемость), размещая бизнес-логику в разных контрактах.

Предварительные условия

Вы должны хорошо разбираться в смарт-контрактах, анатомии смарт-контрактов и виртуальной машине Ethereum (EVM). Это руководство также предполагает, что читатели знакомы с программированием смарт-контрактов.

Что такое обновление смарт-контракта?

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

Вы по-прежнему не можете изменить программу, развернутую по адресу в сети Ethereum. Но вы можете изменить код, который выполняется, когда пользователи взаимодействуют со смарт-контрактом.

Это можно сделать следующими способами:

  1. Создание нескольких версий смарт-контракта и перенос состояния (т. е. данных) из старого контракта в новый экземпляр контракта.

  2. Создание отдельных контрактов для хранения бизнес-логики и состояния.

  3. Использование прокси-шаблонов для делегирования вызовов функций от неизменяемого прокси-контракта к изменяемому контракту логики.

  4. Создание неизменяемого основного контракта, который взаимодействует с гибкими сателлитными контрактами и полагается на них для выполнения определенных функций.

  5. Использование алмазного шаблона (diamond pattern) для делегирования вызовов функций от прокси-контракта к контрактам логики.

Механизм обновления №1: миграция контракта

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

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

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

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

Подробнее о миграции контрактов. (opens in a new tab)

Механизм обновления №2: разделение данных

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

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

Между тем, контракт хранилища содержит состояние, связанное со смарт-контрактом, такое как балансы и адреса пользователей. Обратите внимание, что контракт хранилища принадлежит контракту логики и настраивается с адресом последнего при развертывании. Это предотвращает вызов контракта хранилища или обновление его данных неавторизованными контрактами.

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

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

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

Механизм обновления №3: прокси-шаблоны

Прокси-шаблон также использует разделение данных для хранения бизнес-логики и данных в отдельных контрактах. Однако в прокси-шаблоне контракт хранилища (называемый прокси) вызывает контракт логики во время выполнения кода. Это обратный метод разделения данных, где контракт логики вызывает контракт хранилища.

Вот что происходит в прокси-шаблоне:

  1. Пользователи взаимодействуют с прокси-контрактом, который хранит данные, но не содержит бизнес-логику.

  2. Прокси-контракт хранит адрес контракта логики и делегирует все вызовы функций контракту логики (который содержит бизнес-логику) с помощью функции delegatecall.

  3. После того как вызов перенаправляется в контракт логики, возвращенные из него данные извлекаются и возвращаются пользователю.

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

Из документации Solidity (opens in a new tab):

Существует специальный вариант вызова сообщения, называемый delegatecall, который идентичен вызову сообщения, за исключением того, что код по целевому адресу выполняется в контексте (т. е. по адресу) вызывающего контракта, а msg.sender и msg.value не меняют своих значений. Это означает, что контракт может динамически загружать код с другого адреса во время выполнения. Хранилище, текущий адрес и баланс по-прежнему относятся к вызывающему контракту, только код берется с вызываемого адреса.

Прокси-контракт знает, что нужно вызывать delegatecall всякий раз, когда пользователь вызывает функцию, потому что в него встроена функция fallback. В программировании на Solidity резервная функция (fallback function) (opens in a new tab) выполняется, когда вызов функции не соответствует ни одной из функций, указанных в контракте.

Чтобы прокси-шаблон работал, необходимо написать собственную резервную функцию (fallback function), которая определяет, как прокси-контракт должен обрабатывать вызовы функций, которые он не поддерживает. В этом случае резервная функция (fallback function) прокси-контракта запрограммирована на запуск delegatecall и перенаправление запроса пользователя к текущей реализации контракта логики.

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

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

Прокси-шаблоны — популярный метод обновления смарт-контрактов, поскольку они устраняют трудности, связанные с миграцией контрактов. Однако прокси-шаблоны сложнее в использовании и могут привести к критическим ошибкам, таким как столкновения селекторов функций (opens in a new tab), при неправильном использовании.

Подробнее о прокси-шаблонах (opens in a new tab).

Механизм обновления №4: шаблон «Стратегия»

Этот метод основан на шаблоне «Стратегия» (opens in a new tab), который поощряет создание программ, взаимодействующих с другими программами для реализации определенных функций. Применение шаблона «Стратегия» к разработке на Ethereum будет означать создание смарт-контракта, который вызывает функции из других контрактов.

Основной контракт в этом случае содержит основную бизнес-логику, но взаимодействует с другими смарт-контрактами («сателлитными контрактами») для выполнения определенных функций. Этот основной контракт также хранит адрес каждого сателлитного контракта и может переключаться между различными реализациями сателлитного контракта.

Вы можете создать новый сателлитный контракт и настроить основной контракт с новым адресом. Это позволяет вам изменять стратегии (т. е. реализовывать новую логику) для смарт-контракта.

Хотя шаблон «Стратегия» похож на ранее обсуждавшийся прокси-шаблон, он отличается тем, что основной контракт, с которым взаимодействуют пользователи, содержит бизнес-логику. Использование этого шаблона дает вам возможность вносить ограниченные изменения в смарт-контракт, не затрагивая основную инфраструктуру.

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

Механизм обновления №5: алмазный шаблон (diamond pattern)

Алмазный шаблон (diamond pattern) можно считать усовершенствованием прокси-шаблона. Алмазные шаблоны (diamond patterns) отличаются от прокси-шаблонов тем, что алмазный прокси-контракт может делегировать вызовы функций более чем одному контракту логики.

Контракты логики в алмазном шаблоне (diamond pattern) известны как фасеты (facets). Чтобы заставить работать алмазный шаблон (diamond pattern), вам нужно создать сопоставление в прокси-контракте, которое связывает селекторы функций (opens in a new tab) с адресами различных фасетов.

Когда пользователь совершает вызов функции, прокси-контракт проверяет сопоставление, чтобы найти фасет, ответственный за выполнение этой функции. Затем он вызывает delegatecall (используя резервную функцию (fallback function)) и перенаправляет вызов в соответствующий контракт логики.

Шаблон обновления «Алмаз» (diamond) имеет некоторые преимущества перед традиционными шаблонами обновления прокси:

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

  2. Все смарт-контракты (включая контракты логики, используемые в прокси-шаблонах) имеют ограничение по размеру в 24 КБ, что может быть ограничением, особенно для сложных контрактов, требующих большего количества функций. Алмазный шаблон (diamond pattern) позволяет легко решить эту проблему, разделив функции между несколькими контрактами логики.

  3. Прокси-шаблоны используют универсальный подход к контролю доступа. Субъект, имеющий доступ к функциям обновления, может изменить весь контракт. Но алмазный шаблон (diamond pattern) позволяет использовать модульный подход к разрешениям, при котором вы можете ограничить права субъектов на обновление определенных функций в смарт-контракте.

Подробнее об алмазном шаблоне (diamond pattern) (opens in a new tab).

Плюсы и минусы обновления смарт-контрактов

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

Вопросы, которые следует учитывать при обновлении смарт-контрактов

  1. Используйте безопасные механизмы контроля доступа/авторизации для предотвращения несанкционированных обновлений смарт-контрактов, особенно при использовании прокси-шаблонов, шаблонов стратегий или разделения данных. Примером является ограничение доступа к функции обновления, чтобы ее мог вызывать только владелец контракта.

  2. Обновление смарт-контрактов — это сложная деятельность, требующая высокого уровня осмотрительности для предотвращения появления уязвимостей.

  3. Уменьшите необходимость в доверии за счет децентрализации процесса внедрения обновлений. Возможные стратегии включают использование контракта кошелька с мультиподписью для контроля обновлений или требование к членам DAO голосовать за утверждение обновления.

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

  5. Рассмотрите возможность внедрения временных замков (timelocks) для защиты пользователей. Временной замок (timelock) — это задержка, применяемая к изменениям в системе. Временные замки (timelocks) можно комбинировать с системой управления с мультиподписью для контроля обновлений: если предлагаемое действие достигает необходимого порога одобрения, оно не выполняется до истечения заранее определенного периода задержки.

Временные замки (timelocks) дают пользователям время выйти из системы, если они не согласны с предлагаемым изменением (например, обновлением логики или новыми схемами комиссий). Без временных замков (timelocks) пользователям приходится доверять разработчикам в том, что они не будут вносить произвольные изменения в смарт-контракт без предварительного уведомления. Недостаток здесь в том, что временные замки (timelocks) ограничивают возможность быстрого исправления уязвимостей.

Ресурсы

OpenZeppelin Upgrades Plugins — набор инструментов для развертывания и защиты обновляемых смарт-контрактов.

Руководства

Дополнительные материалы

Была ли эта статья полезной?