Библиотеки Solidity: повторное использование правил
Ненулевой номер и ненулевой отпечаток используются в нескольких учебных моделях. Чтобы не копировать одинаковую проверку бесконечно, её можно оформить библиотекой. Но библиотека должна сохранить ясное назначение: проверять значения, а не незаметно назначать издателя или обращаться к внешним сервисам.
Создадим ReleaseRules и отдельный инспектор ReleaseInspector. Это самостоятельные файлы. Базовый CourseCatalog из обзора не заменяется, поэтому другие курсы продолжают читать тот же исходник. Урок объясняет композицию и границу библиотечного помощника.
Чистая операция над входом
// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
library ReleaseRules {
function valid(uint256 courseId, bytes32 digest)
internal
pure
returns (bool)
{
return courseId != 0 && digest != bytes32(0);
}
}
internal делает этот помощник частью внутренней композиции использующего кода. pure соответствует отсутствию чтения состояния: ответ определяется двумя параметрами. У библиотеки нет поля издателя и нет постоянного журнала, поэтому она не знает, кто вправе публиковать курс.
При входе 17,H1 с ненулевым hash ожидается true. При нулевом номере либо отпечатке — false. Это характеристика допустимости значений, а не успешной транзакции. Издатель всё равно должен пройти собственную проверку роли в вызывающем контракте.
Полный потребитель правила
// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
import {ReleaseRules} from "./ReleaseRules.sol";
contract ReleaseInspector {
function inspect(uint256 courseId, bytes32 digest)
external
pure
returns (bool)
{
return ReleaseRules.valid(courseId, digest);
}
}
Перед нами полный отдельный файл инспектора. Путь импорта относится к соседнему собственному исходнику, а не к установленной внешней зависимости. Для результата нужно сохранить оба файла. Если скопировать только ReleaseInspector.sol, импорт не будет найден в таком проекте.
Инспектор не пишет mapping и не создаёт публикацию. Он полезен для объяснения одного правила и предварительной проверки параметров. Сайт может выполнять аналогичную проверку вне цепочки, но окончательный изменяющий метод всё равно проверяет собственный вход. Нельзя доверить корректность контрактного состояния одному предварительному UI-помощнику.
Using for и форма вызова
Язык допускает привязку библиотечных функций через using. Это удобный способ записи, но он не добавляет тайное поведение к значению. Например, можно определить библиотеку, где первый параметр — проверяемый hash, и затем вызвать метод в объектоподобной форме.
library DigestRules {
function nonzero(bytes32 digest) internal pure returns (bool) {
return digest != bytes32(0);
}
}
contract DigestInspector {
using DigestRules for bytes32;
function inspect(bytes32 digest) external pure returns (bool) {
return digest.nonzero();
}
}
Здесь digest.nonzero() означает применение объявленного помощника. У bytes32 не появилось постоянное поле и новая идентичность. Для начинающего читателя явная форма ReleaseRules.valid(...) часто прозрачнее. Обе записи следует выбирать по читаемости, а не ради ощущения языка классов.
Не всякая библиотека является чистой
Solidity-библиотеки могут иметь разные формы функций, включая работу с переданной ссылкой на состояние. Поэтому из слова library не следует автоматическая безопасность или отсутствие изменений. Нужно читать сигнатуры, области данных и используемые внешние вызовы.
Особое внимание требуется к публичным и внешним библиотечным функциям, привязке адресов и механизму исполнения. Их устройство связано с использованием кода библиотеки в контексте вызывающего контракта. Наш пример специально остаётся во внутренних pure-функциях, чтобы сначала понять повторное использование без дополнительной модели размещения.
При работе с известной внешней библиотекой нужно сохранять точную версию. В следующих курсах OpenZeppelin Contracts 5.4.0 станет отдельной зависимостью, а не файлом «из последних исходников» без реквизитов. Правило версии относится и к безопасности, и к воспроизводимости конструкторов и методов.
Когда вынесение оправдано
Если правило используется только в двух строках одного небольшого контракта, отдельная библиотека может сделать проект сложнее. Появляется ещё один файл и переход при чтении. Вынесение полезно, когда операция самостоятельна, имеет понятное имя и повторно используется в нескольких проверяемых местах.
Например, правила сериализации выпуска могут со временем включать схему и домен. Тогда библиотека поможет удержать один договор кодирования. Но любое изменение алгоритма отпечатка затронет совместимость прежних данных. Общая функция делает изменение централизованным, а не автоматически безболезненным.
| Помощник | Что проверяет | Что не решает |
|---|---|---|
| ReleaseRules.valid | Ненулевые значения | Адрес издателя |
| DigestRules.nonzero | Ненулевой hash | Соответствие файлу |
| Инспектор | Предварительный ответ | Постоянную публикацию |
По этой таблице видно, почему библиотечный код не должен получать слишком широкое имя вроде securePublish. Наше правило узкое и честно названное. Сложная безопасность складывается из входа, роли, состояния и взаимодействий, а не из наличия файла в папке library.
Мы получили собственную библиотеку чистого правила и потребителя, который явно не меняет реестр. Следующий урок рассмотрит наследование как другой механизм организации кода и покажет, почему общий базовый тип нужно отличать от интерфейса чужого экземпляра. Библиотеки Solidity описывают доступные формы.
Зависимость от исходника помощника
Внутренняя библиотека входит в рассмотрение кода использующего контракта. Если изменить valid так, чтобы он принимал нулевой hash, старое название ReleaseRules не предупредит читателя о новой семантике. Поэтому собственные помощники тоже хранятся под управлением версий и читаются при обновлении.
Прежде чем объединить два похожих правила, нужно убедиться, что они действительно одинаковы. Например, базовый реестр запрещает номер 0, а отдельный инспектор документа может использовать 0 как специальный корневой объект. Общая библиотека с одним условием тогда не обслуживает оба требования без ясного параметра или раздельных функций.
Слишком общая функция с множеством переключателей часто становится труднее двух коротких правил. Она заставляет каждый вызов объяснять, какой режим включён. Для первой лаборатории библиотека имеет одно определённое назначение и возвращает одно логическое значение.
Проверки полномочий не следует без нужды растворять в проверках формы. Значения может проверить pure-помощник, адрес отправителя относится к контексту действия. Если функция неожиданно читает msg.sender, это уже другой договор и другая зависимость. Название и сигнатура должны помочь заметить различие.
Для дополнительного задания сохраните в двух проектах один и тот же источник ReleaseRules, затем измените правило только в одном. Ожидаемые ответы инспекторов будут различаться. Внешний вид файла и одинаковое имя библиотеки не доказывают равенство кода. Точные версии и артефакты нужны и для таких маленьких зависимостей. В отчёте укажите конкретное изменённое условие, чтобы следующий читатель мог объяснить расхождение без угадывания.
Оглавление курса. Листинги, расчёты и наблюдения здесь разобраны как ожидаемое поведение; компиляция, учебные программы и проверки при написании не запускались.