Обучение

Как расшифровать calldata транзакции перед подписью

Практическая инструкция по calldata: как определить selector, подобрать ABI, проверить параметры, proxy и вложенные вызовы до подписи транзакции.

Как расшифровать calldata транзакции перед подписью

Calldata – байты, которые транзакция передаёт смарт-контракту. Они описывают вызываемую функцию и её параметры: адрес получателя, сумму, токен, срок, минимальный результат обмена или другой набор данных. Кошелёк может показать понятную расшифровку, но перед важной подписью полезно проверить, совпадает ли она с сырыми данными.

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

Из чего состоит calldata

Поле input транзакции обычно начинается с четырёх байтов selector. Он получается из Keccak-256 хеша канонической сигнатуры функции, например имени и типов аргументов. Возвращаемые типы в selector не входят. Остальная часть содержит аргументы в формате Ethereum ABI.

Статические значения занимают слова по 32 байта. Адрес дополняется нулями, число кодируется как целое соответствующего типа, а bool представлен нулём или единицей. Для динамических значений вроде bytes, строки или массива в основной области хранится offset, который указывает на отдельную область с длиной и содержимым.

ABI-кодирование не является самодокументируемым. Одни и те же байты можно интерпретировать по-разному, если выбрать другие типы. Поэтому найденное по selector название функции – подсказка, а не доказательство. Надёжная расшифровка требует ABI именно той реализации, которая обработает вызов.

Сначала проверьте оболочку транзакции

До разбора байтов зафиксируйте сеть, поле to, нативное value и отправителя. to показывает контракт первого уровня, а value – сколько ETH или другой нативной монеты уйдёт вместе с вызовом. Эти данные не спрятаны внутри calldata, но могут быть важнее названия функции.

Сравните полный адрес to с официально подтверждённым адресом протокола или ранее проверенной записью. Несовпадение одного символа означает другой контракт. Для незнакомого получателя применим тот же порядок, что и в инструкции по проверке адреса и тестовому переводу.

Убедитесь, что chain ID соответствует ожидаемой сети. Один адрес может иметь код в нескольких сетях, причём логика и владелец не обязаны совпадать. Копирование ABI из Ethereum для контракта с тем же адресом в другой L2 способно дать убедительную, но неверную расшифровку.

Как подобрать ABI и проверить selector

  1. Скопируйте полный input и отделите первые четыре байта после 0x.
  2. Откройте в обозревателе адрес to и проверьте, верифицирован ли код.
  3. Возьмите ABI из верифицированного контракта или проверенного репозитория проекта.
  4. Сопоставьте selector с функцией из ABI и декодируйте оставшиеся аргументы по её типам.
  5. Сверьте каждый экономически важный параметр с тем, что вы намеревались сделать.

Публичная база четырёхбайтовых сигнатур полезна, когда ABI отсутствует, но один selector может иметь несколько кандидатов из-за коллизии первых четырёх байтов. Такое совпадение не подтверждает контракт, порядок аргументов или их смысл. Неизвестный вызов лучше не подписывать, пока не найден проверяемый ABI.

Особенно внимательно смотрите на address, amount, tokenId, spender, recipient, deadline и минимально допустимый результат. Число нужно интерпретировать с учётом decimals токена. Сырая величина без decimals легко выглядит в миллион раз больше или меньше человеческого значения.

Approve, permit и опасные параметры

Вызов approve обычно выдаёт адресу spender право расходовать токены владельца в пределах allowance. Получатель разрешения может отличаться от сайта, router и конечного пула. Проверьте именно адрес spender и величину. Очень большое число часто означает неограниченное разрешение, которое сохраняется после завершения текущей операции.

permit и Permit2 могут оформляться через типизированную подпись, а не обычную onchain-транзакцию. У EIP-712 запроса есть domain, primaryType, message, chain ID и verifying contract. В таком запросе нельзя искать обычное поле calldata: нужно проверять домен, spender, amount, nonce и deadline.

Перевод токена может быть вложен в router или multicall, поэтому верхний selector иногда сообщает только о выполнении пакета. Без раскрытия внутренних элементов пользователь видит оболочку, но не конечные адреса и суммы.

Proxy и вложенные вызовы

Если to является proxy, его fallback передаёт calldata реализации через delegatecall. Пользовательская функция находится в ABI implementation, хотя состояние и баланс принадлежат адресу proxy. Нужно найти текущий implementation slot или механизм beacon и убедиться, что реализация не сменилась.

Проверка proxy, admin и обновляемости описана отдельно в материале о proxy-контрактах. Важен текущий блок: расшифровка по старой реализации может уже не соответствовать выполняемому коду.

В multicall, execute и универсальных router calldata может содержать массив других байтовых вызовов. Каждый элемент нужно декодировать отдельно, затем восстановить последовательность: approve, transfer, swap, unwrap, отправка результата. Проверяйте не только финальный получатель, но и промежуточные контракты.

Симуляция и проверка результата

После декодирования полезно выполнить симуляцию на актуальном состоянии сети. JSON-RPC метод eth_call локально исполняет сообщение без публикации транзакции. Кошельки и сервисы симуляции могут дополнительно показать изменения балансов, approvals и вызванные контракты.

Успешная симуляция не даёт гарантии. До включения транзакции изменятся цена, состояние пула, nonce, block timestamp или реализация обновляемого контракта. Злоумышленник также может менять поведение по отправителю и состоянию. Ограничения подробно рассмотрены в статье о рисках симуляции.

После выполнения события помогают понять, что сообщил контракт, но лог не равен состоянию. Его emitter, topics и data следует сопоставлять с ABI, а критический итог проверять по балансам и storage. Для этого пригодится инструкция по чтению событий контракта.

Короткий чек-лист перед подписью

  • сеть и chain ID совпадают с ожидаемыми;
  • to и value проверены отдельно от calldata;
  • ABI относится к текущему implementation, а не к похожему адресу;
  • selector однозначно сопоставлен с функцией;
  • spender, recipient, токен, сумма, decimals и deadline понятны;
  • multicall и вложенные байты раскрыты до конечных действий;
  • симуляция не показывает неожиданного transfer или approval.

Если хотя бы один важный параметр остаётся неизвестным, отказ от подписи – нормальный результат проверки. Calldata выглядит громоздко, но её структура регулярна: оболочка транзакции, четырёхбайтовый selector, слова аргументов и динамические хвосты. Последовательная расшифровка убирает магию и позволяет сравнить запрос сайта с фактической командой контракту.