Контракт реестра на Solidity: переходы состояния
Завершим курс полным исходником первоначального реестра. Все определения находятся в одном файле: структура, постоянные поля, конструктор, модификатор, ошибки, события и два метода. Этот CourseCatalog сохраняет ту же модель, которая использовалась в обзоре ProfessorWeb и независимом курсе Foundry.
Отдельные инспекторы, viewer и журнал V2 помогали изучить язык, но не подставляются в этот файл автоматически. Такой способ завершения важен: читатель получает конкретный небольшой объект, а не набор несовместимых фрагментов, которые нужно догадаться соединить.
Один полный файл
Сохраним src/CourseCatalog.sol в закреплённом профиле Solidity 0.8.30 и EVM Cancun:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
contract CourseCatalog {
error Unauthorized();
error InvalidCourse();
error NotActive();
struct Course {
bytes32 contentHash;
bool active;
}
address public immutable publisher;
mapping(uint256 => Course) public courses;
event Published(uint256 indexed courseId, bytes32 contentHash);
event Deactivated(uint256 indexed courseId);
constructor(address publisher_) {
if (publisher_ == address(0)) revert Unauthorized();
publisher = publisher_;
}
modifier onlyPublisher() {
if (msg.sender != publisher) revert Unauthorized();
_;
}
function publish(uint256 courseId, bytes32 contentHash)
external
onlyPublisher
{
if (courseId == 0 || contentHash == bytes32(0)) {
revert InvalidCourse();
}
courses[courseId] = Course(contentHash, true);
emit Published(courseId, contentHash);
}
function deactivate(uint256 courseId) external onlyPublisher {
if (!courses[courseId].active) revert NotActive();
courses[courseId].active = false;
emit Deactivated(courseId);
}
}
У файла нет внешних импортов. Это упрощает первый разбор и позволяет явно видеть все пути записи. Конструктор задаёт издателя, публикация заменяет структуру, деактивация меняет флаг. Публичные getter возвращают данные, но не позволяют произвольно присвоить их снаружи.
Что допускает реестр
Допустимый издатель может объявить любой ненулевой номер с ненулевым hash. Из этого не следует, что номер присутствует в редакционной базе сайта: такая связь находится в процессе подготовки манифеста. Также контракт не проверяет качество файла, алгоритм расчёта или доступность ссылки. Он поддерживает ровно объявленные правила состояния.
Публикация уже существующего номера разрешена. Она назначает новый текущий отпечаток и снова активирует запись. Если передан прежний hash, действие тоже допустимо и может создать новое событие. Это означает, что журнал нумерует успешные действия, а не гарантированно уникальные файлы.
Отзыв активной записи сохраняет hash. Повторный отзыв отклоняется. Никогда не опубликованный курс имеет стандартные нули и не может быть отключён этим методом. Поэтому интерфейс видит три осмысленные формы: нет выпуска, активен, опубликован и отключён.
| Начальное состояние | Действие | Ожидаемое конечное состояние |
|---|---|---|
| Нули,false | publish(17,H1) от издателя | H1,true |
| H1,true | publish(17,H2) от издателя | H2,true |
| H2,true | deactivate(17) от издателя | H2,false |
| H2,false | publish(17,H3) от издателя | H3,true |
Эта карта относится к одному выбранному экземпляру. Другой адрес может иметь другой издатель и независимые записи. Поэтому получение контракта как исходного файла не заменяет запись развёртывания сайта.
Какие свойства мы ожидаем
Адрес publisher не меняется через доступные методы. Активная запись имеет ненулевой hash. Недопустимый адрес не меняет выпуск. Деактивация сохраняет отпечаток. Публикация всегда заканчивается активной записью с переданным значением. Эти свойства выводятся из правил кода и становятся требованиями к последующим проверкам.
Не следует считать их уже подтверждёнными измерениями. Компиляция, исполняемые примеры и тесты при написании не запускались. Подготовленный файл и таблица показывают ожидаемое поведение. Независимый курс Foundry учит оформлять проверки этих требований, сохраняя тот же основной объект.
Важна и область доказательства. Даже успешные проверки таких свойств не объяснят потерю ключа издателя или неправильное отображение страницы. Они относятся к конкретным методам и входам. Система дополнительно зависит от редакционного процесса, выбранного RPC, манифеста и реализации вычисления отпечатка.
Чего в первом контракте нет
Здесь нет оплаты, токенов, вызовов чужого кода, передачи издателя, прокси и автоматических действий по времени. Следовательно, не нужно придумывать для него сценарии возврата средств или storage-layout прокси. Эти темы становятся значимыми после отдельного введения соответствующих механизмов.
У простоты также есть ограничения. Утеря исходного издательского ключа не исправляется встроенным восстановлением. Перечисление всех ключей не доступно из mapping автоматически. Все прежние версии не читаются отдельным getter. Издатель может заменить текущий указатель. Каждое ограничение связано с сознательным выбором первой модели.
Если библиотеке нужны несколько редакторов, применяется другой контракт с ролями. Если требуется on-chain-история, используется отдельный CourseCatalogV2. Если нужен токеновый доступ, вводится самостоятельный договор владения и серверной выдачи. Нельзя соединить названия этих функций и считать исходный реестр универсальным продуктом.
Как сохранять результат работы
После самостоятельного выполнения читатель хранит исходник, настройки компиляции, артефакт интерфейса и запись фактического развёртывания. Отдельно сохраняется файл выпуска, для которого вычислен hash. В такой комплект не включают секретные ключи или фразу восстановления кошелька.
Для отчёта о публикации нужны сеть, адрес реестра, номер курса, объявленный отпечаток и сведения о результате действия. Если исполнение не выполнено, поля результата остаются пустыми. Честная незаполненная запись лучше выдуманного хеша транзакции: она показывает, какой этап ещё предстоит.
Мы завершили путь от модели EVM к небольшому реестру выпусков. Теперь можно читать каждую строку файла и объяснять её эффект, не теряя границу между контрактом и обычным сайтом. Следующий маршрут — роли, токены и управление; независимо можно изучать качество через Foundry и разбор безопасности. Общие правила языка доступны в документации Solidity 0.8.30.
Следующий контракт — отдельный договор
Расширяя проект, сначала определите, какое новое свойство требуется. Передача издателя относится к управлению; сохранение каждой версии — к истории; приём оплаты — к финансовому состоянию; токеновый доступ — к выдаче материалов. Эти задачи нельзя решить одним неопределённым полем secure.
Каждый новый внешний вызов меняет рассуждение о промежуточном состоянии. Каждый новый путь записи меняет карту полномочий. Каждая новая динамическая коллекция меняет вопрос пределов. Поэтому компактный базовый контракт полезен как точка сравнения: можно назвать, какая именно граница расширилась.
Для читателя, который продолжит курс прав, важно не потерять исходный файл. Новая модель с Ownable или AccessControl будет иметь своё имя, конструктор и правила восстановления. Старый immutable publisher не начнёт автоматически передаваться. История обычного старого экземпляра сохранится независимо от нового кода.
Последнее упражнение курса — объяснить публикацию 17,H1 человеку без знакомства с Solidity. Издатель объявляет отпечаток конкретного выпуска, реестр сохраняет это объявление, сайт доставляет файл, читатель сравнивает байты. Если объяснение добавляет оплату, личность или секретность, оно уже обещает свойства, которых в данном исходнике нет.
Оглавление курса. Листинги, расчёты и наблюдения здесь разобраны как ожидаемое поведение; компиляция, учебные программы и проверки при написании не запускались.