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

Struct в Solidity: структура записи публикации

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

В этом уроке оформим Course, рассмотрим чтение полей и полный переход публикации. Мы не добавляем название, цену или список читателей: эти сведения пока относятся к внешней модели сайта. Небольшая структура позволяет увидеть причину выбранного устройства.

Группа связанных полей

Определение структуры создаёт новый тип составного значения:

struct Course {
    bytes32 contentHash;
    bool active;
}

contentHash содержит отпечаток текущего выпуска, active сообщает его активность. Порядок полей относится к определению типа и позднее будет важен для интерфейса getter и расположения данных. Само определение не создаёт курс 17 и не назначает издателя. Оно описывает форму записи, которую затем использует mapping.

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

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

Создание значения

В публикации используется выражение:

courses[courseId] = Course(contentHash, true);

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

courses[courseId] = Course({
    contentHash: contentHash,
    active: true
});

Оба фрагмента выражают ту же базовую публикацию. Именованная форма полезна, когда структура становится длиннее: имя каждого поля находится рядом с присваиваемым выражением. Это не дополнительная проверка параметров. Нулевой отпечаток по-прежнему должен быть отклонён до присваивания.

Если курс уже существовал, запись полностью заменяется. Поэтому publish(17,H2) после деактивации даёт H2,true. Нельзя считать эту функцию операцией только первого создания. Мы сохраняем такую семантику специально: издатель может исправить выпуск и вернуть его активность.

Чтение и частичное изменение

Структура позволяет обращаться к конкретному полю. В деактивации меняется только активность:

if (!courses[courseId].active) revert NotActive();
courses[courseId].active = false;

Здесь сохранение отпечатка происходит благодаря отсутствию операции, которая его заменяет. Если вместо этого написать delete courses[courseId], оба поля вернутся к стандартным значениям. Получится другая модель: интерфейс уже не отличит отключённый выпуск от отсутствующей записи по текущим полям. Такая замена не является косметическим упрощением.

Для getter удобны фиксированные поля. Публичный mapping структуры предоставляет соответствующий интерфейс чтения, который возвращает доступные поля записи. Клиент должен декодировать их по верному ABI, а не пытаться извлечь объект JavaScript по имени Solidity-структуры. ABI-кодирование имеет собственную форму и порядок.

Свойства допустимых состояний

Составная модель помогает сформулировать ожидаемое свойство: активная запись имеет ненулевой отпечаток. Это не автоматически следует из типа структуры. Оно возникает из проверки publish и из того, что остальные допустимые методы не создают нулевой активный выпуск.

Рассмотрим мысленные последовательности. После первой публикации H1,true свойство выполнено. После отключения H1,false оно также не нарушено. После повторной публикации H2,true снова выполнено. Если будущий административный метод отдельно присваивает active=true для отсутствующего курса, свойство может нарушиться, хотя первоначальные методы остались прежними.

Операция Hash после Active после Значение
До первой публикации Нули false Нет выпуска
publish(17,H1) H1 true Первый выпуск
deactivate(17) H1 false Выпуск отключён
publish(17,H2) H2 true Новый текущий выпуск

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

Расширение без скрытой смены правил

Если понадобятся версии, нельзя просто добавить номер и предположить, что история уже появилась. Нужно решить, сохраняется ли каждый прошлый отпечаток, может ли издатель исправить прежнюю запись и как выбирать текущий выпуск. В этой серии такая модель будет введена отдельно как CourseCatalogV2, чтобы не менять базовый контракт незаметно.

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

Теперь у нас есть небольшой составной тип с точным назначением. Он объединяет два связанных значения, позволяет полностью публиковать новый выпуск и частично отключать существующий. Следующий шаг — связать такие записи с номерами через mapping и внимательно разобрать отсутствие ключа. Структуры Solidity описывают ограничения и доступные формы.

Расширение третьим полем

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

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

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

Для упражнения сравните публикацию новой структуры целиком с изменением только contentHash. В первом варианте active явно становится true; во втором прежнее false может сохраниться после отключения. Обе строки выглядят как обновление hash, но дают разные переходы. Именно объединение полей в Course позволяет заметить, что публикация включает и правило активности.

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

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