Foundry: глубина, число запусков и seed
В предыдущем уроке мы добавили строгую политику отказов и категории действий. Теперь зададим воспроизводимый будущий запуск. Для оценки результата недостаточно слова «случайный»: нужны число историй, их глубина, seed и точная версия лаборатории.
Сохраним выбранный бюджет и форму команды в README проекта. Она приведена для самостоятельной работы читателя и сейчас не исполняется. У нас нет фактического отчёта кампании, найденного контрпримера или измеренной длительности; результат главы — понятные условия будущего исследования.
Число историй и длина истории
Параметр runs задаёт количество запусков последовательностей для invariant-кампании. Параметр depth ограничивает количество выбранных действий внутри одной истории. Увеличение числа историй исследует больше начальных вариантов генерации; увеличение глубины позволяет рассматривать более длинное накопление состояния.
В нашем foundry.toml сохранены runs = 128 и depth = 64. Базовый верхний расчёт выбранных действий одной такой кампании составляет 128 × 64 = 8192. Это плановый бюджет, а не количество состоявшихся публикаций, отключений или реально выполненных транзакций.
Часть действий может быть пропущена handler из-за неактивного ожидаемого курса. Часть относится к чужим попыткам. Кроме того, раннее нарушение останавливает обычную историю, а воспроизведение и уменьшение контрпримера могут добавить работу вне простого произведения. Поэтому итоговое число операций нельзя объявлять заранее.
Длинная история не всегда полезнее
Для нашего малого набора номеров глубина позволяет повторно публиковать и отключать один курс. Но если генератор чаще выбирает пропуски, увеличение глубины лишь создаёт больше бесполезных действий. Сначала оцениваем категории из предыдущего урока и смысл достигнутых переходов.
Также нельзя считать одну очень длинную историю эквивалентом множества коротких. Начальное состояние и ранние действия влияют на последующие возможности. Разные истории помогают исследовать разные начала, а глубина — последствия накопления. Выбор бюджета связан с предполагаемыми ошибками, а не с одним универсальным числом.
При будущей редактуре можно увеличить runs или depth, но изменение сохраняется рядом с отчётом. Сравнение результатов имеет смысл только при понимании, какой параметр изменён. Если одновременно поменять handler, область ключей и бюджет, нельзя приписать различие одной дополнительной сотне запусков.
Фиксируем источник случайности
Seed задаёт начальную величину для генератора случайности. В выбранной версии Foundry 1.3.1 параметр --fuzz-seed объявлен в CLI forge test. Мы сверили его наличие по официальному исходнику, не выполняя программу. Для учебного примера выберем 0x10; это удобная величина, а не специальный рекомендуемый seed.
forge test --match-contract CourseCatalogInvariantTest --fuzz-seed 0x10
Команда выбирает invariant-контракт нашей лаборатории. Её будущий запуск должен использовать согласованные исходники, foundry.toml и зависимости. Обычные unit- и fuzz-сценарии не удаляются; выбор одного проверяющего контракта нужен для отдельного чтения этой кампании.
Фиксированный seed помогает воспроизводить генерацию в том же окружении. Он не обещает одинаковые истории между разными версиями инструментов, изменённым ABI, другим handler или иной подготовкой. Поэтому записывать только seed недостаточно для полезного отчёта о воспроизведении.
Сохраняем паспорт кампании
В README можно подготовить таблицу, которую читатель заполнит после собственного запуска. До исполнения поля результата остаются неизвестными. Нельзя вписывать ожидаемый успех в колонку фактически полученного статуса.
| Поле | Текущее подготовленное значение |
|---|---|
| Профиль Solidity | 0.8.30, EVM cancun |
| Foundry и библиотека | 1.3.1, forge-std 1.9.7 |
| Начальное состояние | Новый пустой реестр и handler |
| Область номеров | 1..20 в handler |
| Целевые действия | Два разрешённых и два чужих |
| Бюджет | 128 историй, глубина 64 |
| Пример seed | 0x10 |
| Фактическое исполнение | Не выполнялось |
После запуска добавляются дата, точная редакция исходников, команда, вывод, категории действий и сведения о нарушении. Если нарушений не найдено, пишем это как результат конкретной кампании, а не как универсальное доказательство правильности контракта.
Если зависимости получены из иной версии, её нужно записать фактически. Само упоминание forge-std 1.9.7 в статье не создаёт установленную библиотеку или lockfile. Подготовка окружения является отдельным шагом читателя, который сейчас не выполнен нами.
Меняем seed и сохраняем важный сценарий
Другой seed, например 0x11, может выбрать иной порядок действий. Это полезно для дополнительного поиска, но смена числа не гарантирует принципиально новую область состояний. Особенно при чрезмерно узких преобразованиях handler разные случайные входы могут приводить к похожим прикладным историям.
Если обнаружен конкретный важный маршрут, не оставляем его исключительно на случайную генерацию. После минимизации сохраняем детерминированную регрессионную функцию. Тогда договор воспроизводится понятными шагами, даже если будущая кампания выбирает другую историю.
Seed при этом остаётся частью происхождения находки, а не единственным способом её изучить. В отчёте полезно иметь исходную длинную последовательность, уменьшенный контрпример и короткое объяснение нарушенного требования. Следующий урок покажет такую структуру на намеренно ошибочном варианте отключения.
Ограничиваем обещание воспроизводимости
Повторный запуск тем же seed тоже имеет смысл: он помогает отличить случайную смену выборки от изменившегося окружения. Если результат разошёлся, сравниваем профиль, исходники, зависимости и внешние входы, прежде чем объявлять ошибку нестабильной. Для сохранённой регрессии дополнительно читаем конкретные действия без генерации. Эти два способа воспроизведения дают рецензенту больше информации, чем последовательная смена случайных чисел до удобного результата.
Для локального unit-окружения нет необходимости обращаться к внешнему RPC. Если позже используется fork, добавляются адрес узла, выбранный блок, доступность состояния и конфигурация экземпляра. Изменившаяся внешняя сеть способна нарушить повторение даже при том же seed.
Не следует автоматически публиковать в отчёте секретные RPC-ключи или реальные приватные ключи. Для нашей кампании их вообще нет: роли моделируются локально. Паспорт сохраняет необходимые параметры поведения, а доступ к внешним сервисам при другой архитектуре описывается безопасными именами конфигурации.
На этом шаге подготовлены конечный бюджет, пример seed и форма отчёта. Всё остаётся неисполненным. В следующем уроке превратим возможное нарушение в минимальный сценарий. Параметр CLI можно увидеть в официальном исходнике Foundry 1.3.1, правила кампании — в руководстве Foundry. Оглавление курса.