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

Декодирование calldata в кошельке

Ориентированное на проверку руководство по calldata транзакций, словам и смещениям ABI, коллизиям селекторов, прокси, пакетам, разрешениям, permit на основе типизированных данных, симуляции и сверке после транзакции.

Обновлено

Только в образовательных целях; не является инвестиционным советом или инвестиционной рекомендацией. Инвестиции могут привести к убыткам.

Краткий ответ

Calldata — неизменяемая строка байтов, передаваемая на вход транзакции Ethereum верхнего уровня или внутреннего вызова сообщения. Обычный вызов функции Solidity начинается с селектора 4-byte, за которым следуют аргументы в кодировке ABI. Однако calldata не является самоописываемой: одни и те же байты могут означать разное для различного исполняемого кода, реализаций прокси или схем. Функции fallback, низкоуровневый ассемблер и протоколы не на Solidity вообще не обязаны следовать стандартному ABI функций.

Поэтому кошелёк должен показывать больше, чем предполагаемое имя функции. Безопасная проверка связывает байты с chainId, блоком, from, to, нативным value, codeHash исполняемого кода, активной реализацией и доверенным ABI; строго декодирует каждый вложенный вызов; отличает calldata в сети от подписей EIP-712; симулирует в явно заданном состоянии; а после включения сверяет фактическую квитанцию и изменения состояния.

Декодирование calldata
0 / 5
0 проверено; 5 осталось

Завершение этой проверки не доказывает безопасность актива, транзакции или системы.

Как это работает

  1. Зафиксируйте конверт подписи и точку наблюдения: chainId, номер и хеш блока, from, to, нативное value, входные байты, nonce и поля комиссии. Сохраните источник данных кошелька или RPC: декодированная нагрузка из другой сети или другого блока подтверждает уже не то же самое.
  2. Перед декодированием определите тип объекта. Транзакция, запрос типизированных данных EIP-712, permit ERC-2612, UserOperation ERC-4337 и обычное personal-sign сообщение используют разные домены и схемы; нельзя принудительно пропускать их все через ABI транзакций.
  3. Определите цель на зафиксированном блоке. Прочитайте исполняемый байткод и codeHash; при необходимости выявите прокси, beacon или реализацию; запишите слоты реализации и администратора; получите ABI именно для этой версии кода. Реестр селекторов предлагает варианты, но не является доказательством.
  4. Декодируйте строго. Селектор — первые 4 bytes Keccak-256 от канонической сигнатуры функции без типов возвращаемых значений. Статические значения занимают слова по 32-byte, а заголовки динамических значений содержат смещения от блока аргументов после селектора. Отклоняйте усечённые данные, смещения за границами, невозможные длины, некорректное дополнение и необъяснимые конечные байты.
  5. Рекурсивно раскройте multicall, вложенную calldata и делегированное выполнение. Для каждого дочернего вызова укажите цель, нативную стоимость, селектор, аргументы, тип вызова и флаг allowFailure, если он есть. При delegatecall код реализации выполняется в контексте адреса, баланса и хранилища вызывающего контракта, а msg.sender и msg.value сохраняются.
  6. Создайте отдельные реестры полномочий и стоимости, затем выполните симуляцию. Запишите получателей, операторов расходов, операторов NFT, сырые единицы токенов, десятичность, сроки, пределы проскальзывания и нативную стоимость. Симулируйте с точным блоком, отправителем и стоимостью, но считайте результат условным снимком: состояние, цены, время, код и порядок транзакций могут измениться.
  7. До подписи подтвердите каждое существенное поле. После включения проверьте статус квитанции, события, трассы при наличии, а также изменения балансов, разрешений и статуса операторов; отличайте перехваченные ошибки дочерних вызовов от успеха верхнего уровня; учитывайте gas даже при откате; дождитесь требуемой финальности; при необъяснимой ошибке остановитесь, а не подписывайте вслепую повторно.

Разобранные примеры

  • Статический перевод ERC-20. transfer(address,uint256) обычно использует селектор 0xa9059cbb. Один селектор и два слова ABI занимают 4 + 2 * 32 = 68 bytes. Сырое значение 1,500,000 для токена, у которого независимо подтверждено 6 decimals, отображается как 1.5 tokens. Число десятичных знаков — внешние метаданные контракта, в аргументах оно не закодировано; один селектор не определяет контракт или функцию однозначно.
  • Смещение динамических байтов. Для f(address,bytes) с нагрузкой 3-byte заголовок из двух слов занимает 64 bytes. Динамическое смещение равно 0x40 и отсчитывается от начала блока аргументов без селектора. Хвост содержит слово длины 32-byte и дополненное слово данных 32-byte, поэтому общий размер calldata равен 4 + 64 + 32 + 32 = 132 bytes. Если ошибочно считать смещение абсолютным от нулевого байта, адрес окажется на четыре байта дальше.
  • Стоимость пакета зависит от реализации. Внешний вызов передаёт 1.00 ETH; три декодированных дочерних вызова явно требуют 0.20 ETH, 0.30 ETH и 0.10 ETH, всего 0.60 ETH. Оставшиеся 0.40 ETH код пакета может вернуть, удержать, переслать или из-за них откатиться. Если третий вызов завершается ошибкой при allowFailure=true, предыдущие могут остаться исполненными; атомарная реализация может откатить всё.
  • Permit во время подписи — не calldata ретранслятора. Владелец с 1,000 USDC подписывает permit ERC-2612 на 300 USDC при nonce 41. Одна подпись не меняет ни баланс, ни разрешение. После успешной отправки ретранслятором nonce становится 42, а разрешение — 300; после расходования 180 баланс равен 820, остаток разрешения — 120. Отключение сайта не отзывает полномочие.

Риски

  • Декодирование для неверной сети, развилки, метки блока или конверта транзакции.
  • Подпись для поддельного домена, целевого адреса или получателя.
  • Ошибочное предположение об уникальности селектора 4-byte при возможных коллизиях.
  • Использование предполагаемого, устаревшего или неверно проверенного ABI.
  • Доверие метке проверенного исходного кода без сверки с текущим codeHash.
  • Пропуск смены реализации, beacon или администратора между проверкой и выполнением.
  • Игнорирование функции прокси, чей селектор конфликтует с реализацией.
  • Непонимание того, что delegatecall записывает в хранилище вызывающего контракта.
  • Принятие некорректных динамических смещений, длин, дополнения или конечных байтов.
  • Нераскрытый вложенный пакет, скрывающий цели, стоимость или полномочия.
  • Предположение об атомарности, когда реализация перехватывает или допускает ошибки дочерних вызовов.
  • Игнорирование нативного value верхнего уровня из-за безобидного вида аргументов токена.
  • Неверная десятичность или предположение, что токены с комиссией за перевод и ребейзингом стандартны.
  • Неограниченное разрешение ERC-20 или неверная обработка гонки при его изменении.
  • Игнорирование того, что NFT setApprovalForAll распространяется на всю коллекцию.
  • Принятие типизированных данных EIP-712 или permit ERC-2612 за calldata транзакции.
  • Пропуск nonce, срока, проверяющего контракта, домена сети или границ защиты от повтора.
  • Доверие симуляции вопреки изменениям oracle, времени, ожидающего состояния, MEV или кода.
  • Принятие статуса квитанции, событий или трасс провайдера за полное доказательство экономического состояния.
  • Слепая повторная подпись через скомпрометированный интерфейс или игнорирование включения, реорганизации и финальности.

Распространённые заблуждения

  • Селектор функции однозначно определяет, что исполнит контракт.
  • Сводка проверенного интерфейса совпадает с подписываемыми байтами и текущей реализацией.
  • Транзакция с value=0 не может перемещать токены, NFT или делегированные активы.
  • Успех симуляции или квитанции доказывает безопасность и ожидаемый экономический результат.
  • Отключение dapp отзывает разрешения, permit и полномочия операторов NFT.

Связанные темы

Источники

Навигация

Поиск по вики...