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

Foundry: тест создания экземпляра контракта

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

В нашем CourseCatalog адрес издателя передаётся явно, нулевое значение запрещено, а поле publisher объявлено immutable. Подготовим проверки этих требований и сравним два независимых экземпляра. Исходники добавляются в существующий CourseCatalogTest; фактического исполнения и отчёта об успешном прогоне пока нет.

Параметр не равен отправителю

В setUp проверяющий контракт выполняет new CourseCatalog(publisher). Отправителем создания является проверяющий контракт, однако значение поля должно совпасть с аргументом publisher. Это два разных адреса с разными назначениями. Конструктор нашей учебной модели не выбирает издателя автоматически через msg.sender.

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

Адрес address(0xA11CE) в лаборатории — удобная различимая величина. За ним не закреплён созданный нами реальный кошелёк. Он применяется вместе с инструментами локального моделирования отправителя. Для настоящего размещения потребуется доступный оператору адрес с подходящей процедурой управления ключами; этот урок такой процедуры не выполняет.

Нулевой издатель недопустим

Конструктор базового реестра проверяет publisher_ == address(0) и выбрасывает Unauthorized(). Возможно, в другом проекте выбрали бы отдельную ошибку конфигурации. Здесь мы проверяем уже установленный договор, а не переименовываем его во время написания сценария.

function testConstructorRejectsZeroPublisher() public {
    vm.expectRevert(CourseCatalog.Unauthorized.selector);
    new CourseCatalog(address(0));
}

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

Здесь не нужен prank. Требование относится к переданному значению, а не к запрету развёртывания читателем. Добавление подмены отправителя только усложнило бы объяснение, потому что не влияет на условие нашей модели. Мы выбираем минимальное окружение, в котором проверяемый договор остаётся видимым.

Создаём вторую конфигурацию

Проверим, что другой допустимый аргумент создаёт экземпляр с другим издателем и собственной пустой таблицей. Этот сценарий не меняет общий setUp: второй реестр появляется внутри конкретной функции.

function testTwoCatalogsHaveIndependentConfiguration() public {
    address secondPublisher = address(0xCAFE);
    CourseCatalog second = new CourseCatalog(secondPublisher);
    bytes32 digest = keccak256("edition-one");

    assertEq(catalog.publisher(), publisher);
    assertEq(second.publisher(), secondPublisher);

    vm.prank(publisher);
    catalog.publish(17, digest);

    (bytes32 secondDigest, bool secondActive) = second.courses(17);
    assertEq(secondDigest, bytes32(0));
    assertFalse(secondActive);
}

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

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

Что означает immutable

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

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

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

Разбираем изменённый пример

Предположим, будущий автор решит заменить конструктор на publisher = msg.sender, убрав аргумент. Тогда потребуется изменить интерфейс создания, проверку конфигурации и руководство по развёртыванию. Старые сценарии нельзя молча приспособить так, чтобы они продолжали выглядеть зелёными: изменился прикладной договор о выборе оператора.

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

Сценарий Требование Что он не устанавливает
Допустимый аргумент Адрес записан правильно Доступность настоящего ключа
Нулевой аргумент Создание отклонено нужной ошибкой Все возможные ошибки размещения
Два экземпляра Конфигурации и записи независимы Правильность настроек сайта

Конфигурация не подтверждает владение ключом

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

Если издателем выбрана другая программа, её способность вызвать publish зависит от собственного интерфейса и полномочий. Наш прямой prank моделирует адресный контекст, но не исполняет эту программу. Утверждать работоспособность multisig на основании такого сценария нельзя. Мы сохраняем границу наблюдения: созданный реестр правильно хранит заданную роль, а жизненный цикл её внешнего владельца относится к отдельной архитектуре.

Сохраняем исходное состояние других проверок

Наш общий setUp по-прежнему создаёт один пустой экземпляр. Дополнительный реестр нужен только в соответствующем сценарии. Остальные проверки не получают неожиданную предварительную публикацию и не зависят от порядка запуска функций.

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

В следующем уроке последовательно опубликуем, заменим и деактивируем выпуск. Объявления конструкторов и immutable описаны в документации Solidity 0.8.30. Оглавление курса.

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