Перейти к содержанию
Learning Platform
Глоссарий Troubleshooting
Урок 04.10 · 20 мин
Продвинутый
Upgradesset_codeProxyMigrationTactSecurity

Паттерны обновляемых контрактов

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

Смарт-контракты в TON могут обновлять свой собственный код. Это критическая возможность: после деплоя контракт живёт на блокчейне, но может содержать баги, требовать новых функций или должен адаптироваться к изменениям протокола. При этом адрес контракта — его “идентичность” в экосистеме — должен сохраняться: NFT-коллекции ссылаются на адрес, DEX-пулы зарегистрированы по адресу, пользователи знают адрес.

В этом уроке мы разберём, как работают обновления на TON, какие паттерны существуют и какие риски несёт обновляемость.


Как работают обновления на TON

Адрес не меняется

Адрес контракта = hash(initial_code + initial_data). Ключевое слово — initial: адрес вычисляется из начального кода и данных при деплое. Когда контракт обновляет свой код через set_code(), адрес остаётся прежним.

set_code и set_data

TON предоставляет два примитива для обновления контракта:

ОперацияФаза примененияКогда вступает в силу
set_code()Action phaseПосле текущей транзакции (отложено)
set_data()Compute phaseНемедленно в текущей транзакции
Транзакция обновления: Compute Phase vs Action Phase
Compute Phase
1. Проверить отправителя
2. set_data(новые_данные) ← немедленно
3. set_code(новый_код) →
отложено
Action Phase
4. Применить новый код
После транзакции: контракт имеет новый код И новые данные
WARNING

Критическое различие: set_data() применяется немедленно (в compute phase), а set_code() откладывается до action phase. Если транзакция завершится ошибкой в action phase (например, не хватит газа), данные будут откачены вместе с кодом. Но если в compute phase вы изменили данные под новый формат, а action phase не выполнился — контракт может оказаться в несогласованном состоянии.


Паттерны обновлений

Базовое обновление

Простейший паттерн: администратор отправляет сообщение с новым кодом, контракт проверяет отправителя и применяет обновление.

Базовое обновление контракта
Администратор
Контракт
├── Проверить: sender == admin?
├── set_code(новый_код)
├── set_data(новые_данные)
└── OK: код обновлён

Преимущества: простота реализации. Недостатки: требует доверия к администратору, нет времени для реакции пользователей.

Отложенное обновление (Timelocked)

Двухэтапный процесс с временной задержкой:

Отложенное обновление (Timelocked)
Администратор
Этап 1
Запрос обновления
Сохранить pending_code
pending_until = now() + DELAY
⏳ DELAY (например, 24 часа) ⏳
Администратор
Этап 2
Подтверждение
Проверить: now() ≥ pending_until?
set_code(pending_code)
OK: код обновлён

Преимущества: пользователи видят запрос обновления и могут вывести средства, если не согласны. Используется в production-контрактах с пользовательскими средствами.

Горячее обновление (Hot Upgrade)

Для контрактов, которые постоянно получают и обрабатывают сообщения (DEX-пулы, оракулы), простое обновление проблематично: между вызовом set_code() и его применением в action phase контракт всё ещё работает на старом коде.

Решение — горячее обновление через регистр C3:

Горячее обновление:

1. Получить новый код
2. setTvmRegisterC3(новый_код)  ← активировать НЕМЕДЛЕННО
3. Вызвать функцию миграции      в этой же транзакции
4. set_code(новый_код)          ← зафиксировать для будущих транзакций

setTvmRegisterC3() заменяет текущий код контракта прямо в compute phase — следующий вызов функции уже выполнит новый код. Это позволяет запустить функцию миграции из нового кода в той же транзакции.

NOTE

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


Миграция хранилища

При обновлении кода контракта часто меняется формат хранилища (persistent data). Старые данные нужно преобразовать в новый формат:

Миграция хранилища
Старый формат
version: 1
admin: Address
balance: uint128
миграция
Новый формат
version: 2
admin: Address
balance: uint256 ← расширен
last_updated: uint32 ← новое

Важные правила миграции:

  1. Миграция выполняется в той же транзакции, что и обновление кода — нельзя оставить контракт с новым кодом и старыми данными
  2. Если миграция не удалась (ошибка, нехватка газа) — контракт может стать неработоспособным: новый код не понимает старый формат данных
  3. Тестирование миграции критически важно — проверяйте на тестнете с реальными данными перед mainnet-обновлением

Безопасность обновлений

Контроль доступа

Кто может обновлять контракт — ключевой вопрос безопасности:

УровеньМеханизмПрименимость
Одиночный adminПроверка sender == admin_addressПрототипы, тестнет
MultisigТребуется N из M подписейProduction
DAO/GovernanceГолосование держателей токеновДецентрализованные протоколы

Рекомендации

  1. Timelock для production — всегда добавляйте задержку между запросом и применением обновления
  2. Аудит всех set_code/nativeSetCode вызовов — любой путь к обновлению кода должен быть проверен
TIP

В M10-L06 (Audit Checklist) описаны проверки безопасности для обновляемых контрактов: верификация контроля доступа, проверка путей обновления, анализ миграционных функций.

  1. Газ для action phase — если не хватит газа на выполнение set_code() в action phase, обновление не применится, хотя compute phase мог изменить данные
  2. Ревизия контракта — после обновления кода необходимо повторно верифицировать контракт в эксплорере
TIP

Подробнее о верификации обновлённых контрактов см. в M11-L03 (Contract Verification). После каждого обновления необходимо загрузить новый исходный код для верификации.


Трейты обновлений в Tact

DANGER

Внимание: трейты обновления в Tact (Upgradable, DelayedUpgradable) явно отмечены как НЕ готовые для production-использования из-за потенциальных несоответствий в хранилище данных. Информация ниже приведена в образовательных целях.

Tact предоставляет экспериментальные трейты для упрощения обновлений:

Upgradable

Прямое обновление с счётчиком версий:

  • Администратор отправляет новый код и данные
  • Трейт проверяет права доступа (только owner)
  • Применяет nativeSetCode() и nativeSetData()
  • Увеличивает счётчик версии

DelayedUpgradable

Двухэтапное обновление с таймаутом:

  • Этап 1: администратор запрашивает обновление (код сохраняется, запускается таймер)
  • Задержка: настраиваемый период ожидания
  • Этап 2: администратор подтверждает обновление после истечения таймера

Этот трейт реализует паттерн отложенного обновления (timelocked), описанный выше.

WARNING

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


Прокси-паттерны

В Ethereum прокси-контракты — основной способ обновления (ERC-1967, UUPS, Beacon). Причина: EVM не имеет встроенного механизма обновления кода, поэтому используется delegatecall — вызов кода другого контракта в контексте текущего.

На TON прокси-паттерны менее распространены, потому что set_code() нативно поддерживается:

АспектEthereum ProxyTON set_code
Механизмdelegatecall к implementationПрямая замена кода
СложностьВысокая (storage layout, collision)Средняя (миграция данных)
Gas overheadДополнительный вызовНет overhead
Нативная поддержкаНет (паттерн поверх EVM)Да (примитив TVM)

Тем не менее, прокси-паттерн может использоваться на TON для специфических случаев: например, разделение хранилища и логики для контрактов с очень большим состоянием или для систем с множеством экземпляров одной логики (NFT-коллекции уже используют подобную архитектуру, где item-контракты делегируют часть логики коллекции).


Ключевые выводы

  1. set_code() откладывается до action phase, а set_data() применяется немедленно — это критическое различие, влияющее на порядок операций при обновлении
  2. Три паттерна обновления: базовое (простое), отложенное (с timelock для безопасности), горячее (через C3 для немедленной активации)
  3. Миграция данных обязательна при изменении формата хранилища — если миграция не удалась, контракт может стать неработоспособным
  4. Контроль доступа — ключевой аспект безопасности: только admin/multisig должен иметь возможность обновлять контракт
  5. Tact-трейты Upgradable и DelayedUpgradable экспериментальны — не используйте в production из-за потенциальных проблем с сериализацией данных
  6. Прокси-паттерны менее актуальны на TON, чем в Ethereum, благодаря нативной поддержке set_code

Частые ошибки

  1. Оставляют функцию обновления кода без ограничений: любой, кто получит доступ к ключу владельца, может заменить логику контракта на вредоносную.
  2. Не предусматривают миграцию данных при обновлении кода: новый код может ожидать другой формат хранилища, что приведёт к crash контракта.
  3. Забывают о прокси-паттерне: при обновлении кода через set_code() адрес контракта не меняется, но все ссылки на старый code_hash становятся невалидными.
  4. Не документируют механизм обновления для пользователей, что подрывает доверие и может привести к обвинениям в rug pull.

Проверка знанийKnowledge check
Почему set_code() применяется не сразу, а откладывается до action phase? Какой риск это создаёт при обновлении?
ОтветAnswer
set_code() откладывается до action phase по архитектуре TVM: изменение кода во время compute phase нарушило бы выполнение текущего кода. Риск: если set_data() (немедленный) изменит формат хранилища под новый код, а set_code() не выполнится (например, из-за нехватки газа в action phase), контракт останется со старым кодом, но данными в новом формате — и станет неработоспособным. Поэтому важно обеспечить достаточно газа для action phase и тестировать обновления на тестнете.

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 10