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

Модификаторы Solidity: проверка onlyPublisher

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

Разберём onlyPublisher как конструкцию языка, а не как магическое название. Если модификатор написан неправильно или не добавлен к одному из путей изменения, красивое имя не защищает состояние. Поэтому прочитаем его порядок исполнения и затем применим к обеим функциям.

Где выполняется тело

Определение из базового контракта:

modifier onlyPublisher() {
    if (msg.sender != publisher) revert Unauthorized();
    _;
}

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

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

Также символ _ не является обычным вызовом функции с именем underscore. Это специальное место подстановки предусмотренного тела. Непонимание этой детали легко приводит к неправильным рассуждениям о порядке и числе исполнений. Для учебного ограничения достаточно одного появления этого места.

Проверка роли и проверка входа

Применим модификатор:

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);
}

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

Если читатель передал нулевой courseId, первым встретится отказ Unauthorized. Если издатель передал тот же ноль, проверка роли пройдёт и возникнет InvalidCourse. Порядок полезен для диагностики и будущих сценариев проверки. Чтобы исследовать конкретное условие, нужно удовлетворить все более ранние требования.

Второй маршрут изменения

Деактивация получает тот же модификатор:

function deactivate(uint256 courseId) external onlyPublisher {
    if (!courses[courseId].active) revert NotActive();
    courses[courseId].active = false;
    emit Deactivated(courseId);
}

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

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

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

Модификатор не является библиотекой восстановления

Название onlyPublisher может создать ложное ощущение полного управления ролями. На самом деле здесь нет назначения нового издателя, аварийного восстановления и отдельных полномочий на отзыв. Эти задачи будут изучаться в другой модели через роли и владельца. Базовый адрес остаётся неизменяемым.

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

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

Сравнение ожидаемых действий

Предположим, курс 17 активен и содержит H1. Издатель может заменить выпуск на H2 и затем отключить. Читатель не может выполнить ни один из этих переходов. Повторное отключение издателем также не проходит, но уже по другой причине.

Действие Проверка роли Условие тела Результат
P публикует H2 Проходит Допустимый вход H2,true
R публикует H2 Отказ Не исследуется Нет изменений
P отключает активный 17 Проходит Активен H2,false
P отключает его снова Проходит Неактивен NotActive

Вместо запоминания всех строк достаточно видеть два слоя правил. Первый отвечает «кто», второй — «что сейчас допустимо». Такое разделение делает и пользовательские сообщения понятнее: недостаток полномочий отличается от уже выполненного отключения.

Результат урока — общий механизм адресной проверки, явно добавленный к обеим операциям. На следующей странице рассмотрим видимость функций, чтобы внутренний помощник не оказался случайно ещё одним общедоступным маршрутом. Модификаторы Solidity описывают точные правила подстановки тела.

Проверка при добавлении метода

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

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

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

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

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

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

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