Перейти к основному контенту
Change page

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

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

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

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

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

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

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

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

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

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

  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 резервная функция (opens in a new tab) выполняется, когда вызов функции не совпадает с функциями, указанными в контракте.

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

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

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

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

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

Механизм обновления №4: Шаблон стратегии

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

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

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

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

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

Механизм обновления №5: Шаблон «бриллиант»

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

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

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

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

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

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

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

Подробнее о шаблоне «бриллиант» (opens in a new tab).

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

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

Рекомендации по обновлению смарт-контрактов

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

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

  3. Сократите допущения о доверии путем децентрализации процесса внедрения обновлений. Возможные стратегии включают использование контракта кошелька с мультиподписью для контроля обновлений или требование к членам ДАО голосовать за одобрение обновления.

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

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

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

Ресурсы

Плагины обновлений ОпенЗеппелин — набор инструментов для развертывания и защиты обновляемых смарт-контрактов.

Руководства

Дополнительная литература