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

Foundry: trace и оценка расхода газа

В предыдущем уроке мы определили известное начальное состояние. Теперь разберём, как читать путь исполнения и расход вычислительного ресурса. Trace помогает увидеть, какие вызовы произошли; газ помогает сравнить определённые операции при согласованных условиях.

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

Дерево вызовов показывает структуру

В unit-сценарии проверяющий контракт обращается к CourseCatalog, а тот меняет mapping и испускает событие. В сценарии читателя появляется дополнительный уровень: проверяющий контракт вызывает CourseReader, который обращается к getter реестра. В handler-кампании между генератором и реестром находится программа действий.

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

Сначала находим исследуемый прикладной вызов, затем читаем его вложенные обращения. Вспомогательные вызовы vm относятся к подготовке и наблюдению. Они не являются дополнительными методами реестра, которые пользователь смог бы вызвать в обычной сети.

Выбираем небольшой сценарий для будущего вывода

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

forge test --match-test testEditionLifecycle -vvvv

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

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

Газ не равен денежной цене

Газ выражает ресурс исполнения по правилам EVM. Денежная стоимость сетевой транзакции зависит также от цены газа и модели конкретной сети. В L2 могут присутствовать дополнительные компоненты, связанные с публикацией данных. Локальный unit-отчёт не предоставляет эти внешние величины автоматически.

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

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

Начальное состояние влияет на запись

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

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

Вариант Начальное состояние Исследуемый переход
Новый выпуск Пустая запись Два поля получают публикацию
Замена Активный прежний выпуск Digest обновляется
Повторение Активный тот же выпуск Значение может численно сохраниться
Отключение Активная запись Меняется флаг
Чужая попытка Известная запись Проверка роли и откат

Задаём профиль сравнения

В нашей лаборатории зафиксированы Solidity 0.8.30, EVM cancun, Foundry 1.3.1 и выключенный optimizer. Для сравнения редакций эти параметры должны совпадать. Изменение optimizer может поменять структуру байткода и расход, поэтому такой результат нельзя приписать только правке исходного метода.

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

До измерения формулируем ожидаемую причину различия, затем проверяем её по trace и отчёту. Если новый вариант дешевле, но перестал сохранять требуемый digest, это нарушение договора, а не удачная оптимизация. Корректность перехода остаётся основным условием сравнения.

Отчёт по методам требует чтения выборки

В Foundry предусмотрен будущий вывод forge test --gas-report. Для нашего набора можно ограничить его CourseCatalogTest, но команда здесь не выполняется. Отчёт может агрегировать несколько вызовов одного метода из разных сценариев, поэтому среднее число требует анализа состава выборки.

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

Не вставляем в статью красивые ожидаемые числа без исполнения. Полезный отчёт должен содержать источник данных, профиль, выбранные функции и фактический вывод. Значение «не измерено» точнее выдуманного расхода, даже если читателю хочется увидеть готовую таблицу стоимости.

Изменённый пример с потерей поведения

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

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

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

В следующем уроке свяжем исходники с артефактом и экземпляром. Понятие газа описано в документации Ethereum, возможности вывода — в руководстве Foundry. Оглавление курса.

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