Миграция Solidity-контракта: новый экземпляр и состояние
Мы создали несколько отдельных контрактов библиотеки. OwnedCourseCatalog меняет модель издателя, EditorialCatalog разделяет роли, а Governor управляет новым маршрутом вызова. Ни один из них не заменяет состояние старого CourseCatalog автоматически. Новый адрес начинает собственную историю, даже если название и часть исходника совпадают.
Миграция — это договор перехода приложения между экземплярами. Для реестра нужно решить, какие курсы перенести, какой блок считать исходным снимком, что делать с изменениями после него и кто получит новые полномочия. Для токенов потребуется иной договор, поскольку список владения и права держателей отличаются от нескольких редакционных записей.
Снимок реестра
Базовый CourseCatalog не перечисляет ключи mapping сам по себе. Список courseId берётся из известного каталога приложения и истории событий, а значения читаются из состояния на выбранной точке. События помогают обнаружению, но последнее событие без учёта выбранного блока не заменяет точный снимок.
Для нашей лабораторной библиотеки можно подготовить такой собственный документ. Значения H1 и H2 обозначают полные ненулевые bytes32 в реальном файле; здесь это читаемая схема, а не исполняемый calldata.
source: сеть A, адрес OldCatalog, блок B
destination: сеть A, адрес NewCatalog
course 17: contentHash H1, active true
course 18: contentHash H2, active false
список курсов: 17, 18
история старых событий: сохраняется отдельно
Сеть, адрес и блок обязательны для воспроизводимого смысла снимка. «Текущее состояние старого каталога» меняется со временем. Если другой редактор публикует H3 во время переноса, некоторые страницы могут прочитать значения до изменения, а другие после. Это не единый доказанный снимок.
Перенос активной и отозванной записи
В базовом новом CourseCatalog нет importState. Его разрешённый publisher может вызвать publish(17,H1), чтобы получить активную запись. Для отозванной записи 18 с ненулевым H2 возможна последовательность publish(18,H2), затем deactivate(18). Полученное состояние совпадёт по двум полям, но история событий будет другой.
Новая история покажет первоначальную публикацию и последующий отзыв в моменты миграции. Она не станет старой историей того же курса в OldCatalog. Поэтому интерфейс должен хранить ссылку на прежний экземпляр и явно объяснять происхождение перенесённого значения.
Также между publish и deactivate отозванная запись может временно быть активной, если это две независимые транзакции. Если такое окно недопустимо, нужен другой маршрут: отдельная ограниченная миграционная модель, атомарный пакет через подходящего издателя либо подготовка непубличного назначения до переключения интерфейса. Выбор является проектным требованием.
Завершение импорта
Нельзя оставить общий публичный import, который произвольный адрес использует после запуска. Если отдельная модель имеет загрузчик снимка, определяют полномочие, проверку данных и момент закрытия. Пожизненный административный перезаписывающий метод даёт иной договор доверия, чем ограниченное первоначальное состояние.
При переносе прав не копируйте только названия ролей. AccessControl хранит назначение отдельно для каждого экземпляра и роль администрирования каждой роли. Одинаковый hash PUBLISHER_ROLE не назначает тех же субъектов. Нужно документировать конкретные accounts и граф управления новой системы.
Иначе новая команда может получить возможность publish, но потерять возможность восстановить издателя после revoke. Либо прежний bootstrap-admin сохранит косвенный контроль, который ожидалось прекратить. Снимок данных и корректная структура полномочий проверяются как отдельные результаты миграции.
Изменения во время перехода
Предположим, исходный блок B содержит H1, а после него старый publisher опубликовал H2. План может запрещать дальнейшие изменения старой модели по организационному договору, учитывать их дополнительным дельта-переносом либо объявлять B окончательной точкой. Нужно выбрать один вариант, а не назвать пропущенный H2 случайной мелочью.
У immutable publisher базового CourseCatalog нет встроенной pause. Поэтому заявление «старый контракт остановлен» требует конкретного основания: например, выбранный контракт-издатель больше не исполняет публикации по своему интерфейсу. Простое удаление кнопки из сайта не делает on-chain метод недоступным уполномоченному субъекту.
После переключения приложения сохраняйте старый адрес доступным для чтения истории. Прежние ссылки и receipt не меняют целевой экземпляр из-за обновления JavaScript. Для публичного учебного сайта полезно показать, какой реестр является действующим сейчас и какой относится к предыдущему этапу примера.
Токены требуют отдельного решения
Миграция LibraryPass не переносит NFT простым назначением того же tokenId в новой коллекции. Это другой объект с новым контрактным адресом. Даже одинаковое число 1 и та же картинка не делают его тем же токеном. Нужно решить, выдаётся ли новый объект, как предотвращается повторное получение и что означает старое владение.
Для ERC-20 копирование балансов при открытых передачах может создать несогласованный снимок. Если R переводит единицы Q после выбранного блока, а новый токен выдаётся по прежнему балансу, продукт должен объяснить право на обе системы и правила их дальнейшего использования. Это предметная политика, а не универсальная функция стандарта.
Нельзя мигрировать allowance или операторские разрешения, просто предположив, что пользователь хотел дать их новой коллекции. Адрес spender и область экземпляра были частью прошлого решения. Новое разрешение требует предусмотренного нового действия пользователя либо заранее согласованного механизма с точными границами.
Отчёт о переходе
Сохраните исходный блок, список ключей, значения, новые адреса, параметры конструкторов, субъект переноса и его операции. Отдельно перечислите то, что не переносится: старые события, прежние transaction hashes, идентичность токенов и подписи для старого домена. Это помогает не обещать невозможное сохранение всей системы одним копированием.
Для самостоятельного разбора перенесите мысленно активный курс 17 и отозванный 18, затем добавьте публикацию H3 между снимком и переключением. Определите действующее значение по выбранной политике и объясните, почему новая история не совпадает со старой. Реальные контракты и миграционные операции сейчас не запускаются; мы подготавливаем осмысленный будущий маршрут.
При переключении сайта обновляется конфигурация чтения, а старые URL учебных материалов сохраняют своё назначение. Адрес реестра является данными интеграции, не обязательной частью permalink каждой статьи. Так можно объяснить миграцию примера, не уничтожая прежние поисковые ссылки и не переписывая исторические receipt.
Для самостоятельного уточнения: types.html, AccessControl.sol (5.4.0), proxies.
Оглавление курса. Листинги, расчёты и наблюдения здесь разобраны как ожидаемое поведение; компиляция, учебные программы и проверки при написании не запускались.