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

Библиотеки 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, затем измените правило только в одном. Ожидаемые ответы инспекторов будут различаться. Внешний вид файла и одинаковое имя библиотеки не доказывают равенство кода. Точные версии и артефакты нужны и для таких маленьких зависимостей. В отчёте укажите конкретное изменённое условие, чтобы следующий читатель мог объяснить расхождение без угадывания.

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

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