Обучение

Почему симуляция транзакции не гарантирует безопасный результат

Что на самом деле проверяет симуляция транзакции, почему результат меняется до включения в блок и какие approvals, proxy, MEV и подписи она может не показать.

Почему симуляция транзакции не гарантирует безопасный результат

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

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

Как работает симуляция

В EVM-сетях базовый механизм похож на eth_call: узел исполняет сообщение против выбранного блока, но не записывает изменения состояния. Передаются sender, destination, calldata, value и параметры gas. Результатом становятся return data или ошибка исполнения.

Расширенные методы могут выполнить последовательность транзакций, собрать traces, logs и state diffs. Сервис поверх RPC декодирует вызовы и превращает низкоуровневые изменения в понятное описание. Но качество предупреждения зависит от правильного sender, актуального блока, полноты trace и базы известных контрактов.

Некоторые симуляторы разрешают state overrides: временно меняют баланс, nonce, storage или code для проверки сценария. Это полезно разработчику, но такой результат описывает искусственное состояние. Он не доказывает, что те же условия существуют в публичной сети.

Почему точное предсказание невозможно

Blockchain – общая изменяемая машина состояний. Пока подписанная транзакция ждёт включения, другие пользователи совершают swaps, меняют allowances, ликвидируют позиции и обновляют контракты. EVM-программа может ветвиться в зависимости от любого доступного ей значения.

На поведение влияют block.timestamp, block.number, block.basefee, адрес получателя priority fee, случайность предыдущего блока и storage других контрактов. Симулятор подставляет конкретный контекст, а producer следующего блока создаёт другой.

Даже правильная симуляция не знает окончательный порядок mempool. Перед вашей операцией может пройти крупный swap, арбитраж или liquidation. После неё может быть размещена другая транзакция. Результат зависит не только от вашего calldata, но и от соседей в блоке.

Состояние меняется между проверкой и исполнением

Самый простой пример – обмен в AMM. Симулятор видит текущие резервы и рассчитывает выход. До включения другой пользователь меняет цену. Если задан разумный minimum amount out, транзакция откатится или исполнится в допустимых пределах. Если защита слишком широкая, результат может быть значительно хуже прогноза.

Похожая ситуация возникает при mint NFT с ограниченным supply, claim, регистрации имени и покупке по order. Объект может исчезнуть, цена – измениться, а nonce – стать недействительным. Неудача после безопасной симуляции не обязательно означает обман: состояние просто перестало соответствовать снимку.

Для чувствительной операции важны deadline, slippage, price limit и минимальный выход. Их нужно читать в calldata, а не полагаться на число, которое показал интерфейс перед подписью.

MEV и порядок транзакций

Публичная транзакция может быть замечена searcher до включения. Он способен разместить операцию до или после неё, если economics и правила блока позволяют. Sandwich меняет цену вокруг swap, backrun забирает возникший арбитраж, а liquidation competing определяет, кто первым исполнит действие.

Одиночная симуляция обычно не моделирует действия неизвестного searcher. Она покажет результат в текущем state, но не худший допустимый результат после перестановки. Private relay уменьшает публичность маршрута, однако добавляет зависимость от его политики и не заменяет ограничения в calldata.

Approve может выглядеть безобидно

Транзакция approve часто не переводит токены сразу. Симуляция показывает нулевую потерю баланса, хотя spender получает право вызвать transferFrom позже. Безлимитное разрешение сохраняется после завершения текущего swap и остаётся опасным при взломе или подмене spender.

Permit и Permit2 создают похожее право через подпись. Иногда подпись вообще не является onchain-транзакцией и не проходит через обычный симулятор. Проверяйте token, spender, amount, nonce, deadline и chain. Подробный разбор есть в материале о Permit и Permit2.

После работы с незнакомым приложением полезно проверить активные allowance и отозвать лишние. Порядок описан в инструкции по отзыву разрешений токенов.

Подпись сообщения может остаться вне симуляции

EIP-712-подпись не меняет состояние сама по себе. Она выдаёт доказательство согласия, которое другой участник может отправить позднее. Если кошелёк симулирует только onchain-вызовы, будущий эффект order, permit или meta-transaction не появится в balance diff.

Важно читать domain, verifying contract, chainId, action, asset, amount, recipient, nonce и deadline. Как проверить эти поля, объясняет статья о безопасной проверке EIP-712.

Blind signing ещё опаснее: устройство подтверждает байты, которые пользователь не может независимо прочитать. Внешний симулятор помогает, но если он получил другой calldata или доверяет скомпрометированному приложению, экран «успех» не связывает намерение с тем, что подписывает устройство.

Proxy и изменяемый код

Адрес, который симулировался вчера, сегодня может исполнять другую implementation. Upgradeable proxy делегирует вызов отдельному контракту, а admin способен заменить его по правилам системы. Если upgrade проходит между симуляцией и включением, одинаковый calldata получает новое поведение.

Проверьте implementation, proxy admin, multisig и timelock. Наличие verified source только у proxy недостаточно. Практический порядок приведён в статье о проверке обновляемого контракта.

Даже без proxy основной контракт может вызывать registry, router, oracle или plugin, адрес которых меняется через storage. Симулятор видит текущую ссылку, но governance или администратор способен обновить её до исполнения.

Что ещё может пропустить интерфейс

  • Internal calls. Сервис декодирует верхний вызов, но не объясняет опасный delegatecall или callback.
  • Нестандартный токен. Fee-on-transfer, rebase, blacklist и callback меняют фактический результат.
  • Будущие полномочия. Operator approval для NFT или модуль smart account не уменьшает баланс немедленно.
  • Другой sender. Контракт ветвится по msg.sender, а симуляция выполнена не от вашего адреса.
  • Другая сеть. Одинаковый адрес в другой chain может содержать иной code.
  • Account abstraction. Bundler, paymaster и EntryPoint добавляют проверки, отсутствующие в простом eth_call.
  • Bundle. Отдельная транзакция выглядит безопасно, но результат зависит от предыдущей операции в пакете.
  • Reorg. Симуляция против последнего блока может опираться на состояние, которое перестанет быть canonical.

Может ли контракт распознать симуляцию

У EVM нет универсального флага «сейчас симуляция», однако окружение проверки отличается от будущего блока. Контракт может ветвиться по sender, gas, balance, timestamp, block values, доступности внешнего сервиса или состоянию связанного адреса. Злоумышленник способен подобрать условия, при которых известный симулятор видит безопасную ветку, а реальное исполнение – другую.

Защита строится не на попытке обнаружить каждый трюк, а на минимизации полномочий. Точная сумма approve лучше неограниченной, minimum output лучше обещания интерфейса, а отдельный кошелёк с небольшим балансом ограничивает ущерб неизвестного dApp.

Как правильно читать результат

Сначала проверьте, какой block использован и совпадают ли chain, sender, destination, value и calldata. Затем изучите не только token transfers, но и approvals, operator permissions, ownership changes, module installation и native value. Ошибка декодирования должна понижать доверие, а не восприниматься как отсутствие эффекта.

Посмотрите полный trace: какие контракты вызываются, где применяется delegatecall, какой адрес получает актив и какое условие ограничивает результат. Сравните implementation и admin с теми, которые вы проверяли ранее. Для swap найдите minimum received и deadline; для lending – collateral, debt asset и on-behalf-of address.

Если сервис показывает «безопасно», но не объясняет неизвестный метод, большой allowance или изменяемый proxy, остановитесь. Рейтинг симулятора не заменяет понимание полномочия, которое вы выдаёте.

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

  1. Сверьте сайт приложения, сеть и адрес контракта по независимому каналу.
  2. Проверьте sender, recipient, native value и decoded method.
  3. Изучите approvals, Permit, NFT operators и устанавливаемые модули.
  4. Проверьте slippage, minimum output, deadline и адрес получателя.
  5. Посмотрите implementation, admin и задержку обновления.
  6. Убедитесь, что симуляция выполнена на свежем состоянии и от вашего адреса.
  7. Для нового протокола используйте отдельный кошелёк и небольшую тестовую сумму.
  8. После операции проверьте receipt, реальные transfers и оставшиеся permissions.

Когда симуляция особенно полезна

Она хорошо выявляет очевидный revert, неожиданный recipient, большой native transfer, mint вместо swap и немедленное списание нескольких активов. Для сложной DeFi-операции trace помогает увидеть router, pool, oracle и фактическую последовательность вызовов.

Разработчику симуляция даёт воспроизводимый тест против fork выбранного блока. Пользователю – дополнительный шанс заметить расхождение между обещанием интерфейса и calldata. Чем точнее входные данные и свежее state, тем полезнее прогноз, но его временная область всё равно ограничена.

Итог

Симуляция – это снимок исполнения, а не гарантия. Она отвечает на вопрос, что произошло бы при заданном state, sender, calldata и block context. Реальный блок может иметь другое состояние и порядок, а подписи и долговременные разрешения вообще не обязаны сразу менять balances.

Используйте симуляцию вместе с декодированием calldata, проверкой approvals, proxy, лимитов и адресов. Безопасность определяется тем, какие полномочия получает контракт и какие ограничения записаны в транзакции, а не цветом предупреждения в одном интерфейсе.