Перейти к содержанию

Развёртывание контракта Solidity: экземпляр и адрес

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

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

Код один, состояния разные

Рассмотрим два экземпляра CourseCatalog. У первого издатель P1, у второго P2. Оба поддерживают publish и deactivate, но публикация курса 17 в первом не меняет запись во втором. Общий исходный текст задаёт правила перехода; постоянные данные принадлежат выбранному экземпляру.

Сеть 31337, адрес C1: publisher=P1, courses[17]=(H1,true)
Сеть 31337, адрес C2: publisher=P2, courses[17]=(0,false)
Сеть другая, адрес C1: независимое состояние либо отсутствие кода

Этот пример объясняет частую ошибку интерфейса. Разработчик меняет исходник, пересоздаёт локальную сеть, а браузер продолжает читать старый адрес. Даже если адрес выглядит знакомым, новый экземпляр не обязан содержать прежние записи. Интерфейс должен проверять выбранную сеть и наличие ожидаемого кода, а история редакционных действий — относиться к конкретному развёртыванию.

chainId служит идентификатором сети в используемых протоколах и подписях. В нашей лаборатории выбран 31337. Само число не доказывает, что RPC-узел является вашим локальным узлом: два независимых учебных окружения могут использовать такое же значение. Поэтому при локальной работе также известен источник соединения, а для публичного приложения нужны выбранные доверенные параметры сети.

Запись, которую читает клиент

Создадим документ deployment.json. Это схема ожидаемых полей, а не настоящий результат размещения:

{
  "schemaVersion": 1,
  "network": { "chainId": "31337", "environment": "local" },
  "contractName": "CourseCatalog",
  "contractAddress": null,
  "publisherAddress": null,
  "deploymentBlock": null,
  "deploymentTxHash": null,
  "compiler": "0.8.30",
  "evmVersion": "cancun",
  "abiArtifact": "CourseCatalog.json",
  "execution": "not_run"
}

Здесь null обозначает отсутствие полученного значения. Нулевой адрес был бы другим значением и легко попал бы в неподходящий вызов. Поэтому клиент сначала проверяет полноту записи и только затем разрешает сетевое действие. Уведомление «экземпляр ещё не задан» понятнее, чем неясная ошибка декодирования ответа по нулевому адресу.

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

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

Минимальные вопросы перед чтением

Представим запуск сайта, который намерен показать курс 17. Сначала он получает выбранный chainId. Затем убеждается, что адрес заполнен и корректно закодирован. Потом читает код по этому адресу и сверяет ожидаемую модель интерфейса. Наконец запрашивает издателя и запись курса. Такой порядок помогает отличать ошибки окружения от настоящего отсутствия курса.

Наблюдение Возможное объяснение
Не совпала сеть Выбрано другое окружение кошелька или RPC
По адресу нет кода Экземпляр не размещён в этой сети
Издатель отличается Подключён другой выпуск реестра
Getter не декодируется ABI либо адрес не соответствуют ожиданию
Курс имеет нулевой hash Запись не опубликована в выбранном экземпляре

Нужно сохранить осторожность в формулировках. Отсутствие кода не всегда позволяет сделать вывод о типе самого аккаунта в любой современной схеме. Для нашего конкретного обычного реестра оно означает, что ожидаемый контракт там не обнаружен. Мы проверяем требование приложения, а не пытаемся построить универсальный классификатор всех аккаунтов Ethereum.

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

Обновление конфигурации

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

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

Завершим мысленным упражнением. Скопируйте только contractAddress из документа коллеги, который работает в другом локальном окружении. Какие сведения потеряны? Сеть, источник RPC, аргумент издателя, номер блока и соответствующий артефакт. Именно поэтому корректная запись развёртывания является целостным документом, а адрес отдельно — лишь одной из её частей. В дальнейших уроках такой документ позволит проверить, что функция вызывается на нужном состоянии. Развёртывание контракта описывает этап, результат которого мы здесь моделируем.

Два локальных окружения с одним chainId

Представим две лаборатории на разных компьютерах. Обе используют 31337, но у каждого узла независимая история. Запись развёртывания первого компьютера нельзя автоматически считать результатом второго. Даже одинаковый адрес, полученный при похожей начальной последовательности, не подтверждает наличие нужного курса.

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

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

Оглавление курса. Листинги, расчёты и наблюдения здесь разобраны как ожидаемое поведение; компиляция, учебные программы и проверки при написании не запускались.

Оглавление серии