Перейти к основному содержанию
Это руководство создаёт пакет симуляции из 3 шагов, который отрабатывает классический процесс одобрения ERC-20: минтинг токенов держателю, одобрение spender’у, а затем вызов transferFrom этим spender’ом. Каждый шаг зависит от состояния, создаваемого предыдущим шагом, поэтому именно пакет — правильный инструмент.

Зачем нужен пакет?

Симуляция каждой транзакции по отдельности не работает:
  • Только Mint: вы можете проверить логику mint, но у адреса-держателя нет токенов в mainnet.
  • Только Approve: симуляции нужно, чтобы у держателя действительно был баланс для одобрения.
  • Только TransferFrom: требуется, чтобы был и баланс, и одобрение.
Sim Bundle выполняет все три последовательно, разделяя итоговое состояние. Шаг 2 видит сминтенный баланс из шага 1. Шаг 3 видит одобрение из шага 2.

Три транзакции

Все три транзакции направлены на контракт DAI в Ethereum mainnet (0x6b175474e89094c44da98b954eedeac495271d0f).

Шаг 1. Mint 2 DAI

From:  0xe2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2  (fake ward)
To:    0x6b175474e89094c44da98b954eedeac495271d0f  (DAI)
Input: 0x40c10f19
       000000000000000000000000e58b9ee93700a616b50509c8292977fa7a0f8ce1  (recipient)
       0000000000000000000000000000000000000000000000001bc16d674ec80000  (2 DAI)
Функция mint в DAI защищена маппингом wards: вызвать её могут только адреса, зарегистрированные как wards. Fake ward (0xe2e2...e2) не является ward в mainnet. Чтобы этот шаг прошёл успешно, добавьте переопределение состояния, которое устанавливает слот хранилища ward для этого адреса в 1:
ПолеЗначение
Contract0x6b175474e89094c44da98b954eedeac495271d0f
Storage key0xedd7d04419e9c48ceb6055956cbb4e2091ae310313a4d1fa7cbcfe7561616e03
Value0x0000000000000000000000000000000000000000000000000000000000000001
Ключ хранилища — это keccak256(abi.encode(wardAddress, 0)), стандартный слот маппинга Solidity для wards[fakeWardAddress] при позиции хранилища 0.

Шаг 2. Approve 1 DAI

From:  0xe58b9ee93700a616b50509c8292977fa7a0f8ce1  (token holder from step 1)
To:    0x6b175474e89094c44da98b954eedeac495271d0f  (DAI)
Input: 0x095ea7b3
       000000000000000000000000f7ddedc66b1d482e5c38e4730b3357d32411e5dd  (spender)
       0000000000000000000000000000000000000000000000000de0b6b3a7640000  (1 DAI)
Держатель одобряет spender’у списание до 1 DAI. Этот шаг работает, потому что шаг 1 уже зачислил 2 DAI на баланс держателя в общем потоке состояния.

Шаг 3. TransferFrom 0.03 DAI

From:  0xf7ddedc66b1d482e5c38e4730b3357d32411e5dd  (approved spender)
To:    0x6b175474e89094c44da98b954eedeac495271d0f  (DAI)
Input: 0x23b872dd
       000000000000000000000000e58b9ee93700a616b50509c8292977fa7a0f8ce1  (sender / token holder)
       000000000000000000000000bd8daa414fda8a8a129f7035e7496759c5af8570  (recipient)
       000000000000000000000000000000000000000000000000006a94d74f430000  (0.03 DAI)
Spender перемещает 0.03 DAI от держателя получателю. Это работает, потому что шаг 2 предоставил разрешение (allowance), которое видно только внутри потока состояния пакета, а не в сети.

Запуск через UI Simulator

1

Откройте Sim Bundle Builder

Перейдите в Simulator в левой навигации, затем нажмите New Simulation. Откроется конструктор пакета с одним пустым шагом.
2

Настройте панель сессии

Панель сессии расположена сверху. Установите Network в Mainnet и оставьте Block как Pending.
3

Заполните шаг 1: Mint

Переключите шаг в режим Raw. Установите:
  • From: 0xe2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2
  • To: 0x6b175474e89094c44da98b954eedeac495271d0f
  • Calldata: 0x40c10f19000000000000000000000000e58b9ee93700a616b50509c8292977fa7a0f8ce10000000000000000000000000000000000000000000000001bc16d674ec80000
Затем раскройте State overrides, добавьте контракт DAI и введите слот хранилища ward и значение из таблицы выше. Нажмите Add storage override, чтобы зафиксировать слот.
Step 1 mint transaction configured with state override
4

Добавьте шаг 2: Approve

Нажмите + Add function call на панели пакета. Переключите новый шаг в режим Raw. Установите:
  • From: 0xe58b9ee93700a616b50509c8292977fa7a0f8ce1
  • To: 0x6b175474e89094c44da98b954eedeac495271d0f
  • Calldata: 0x095ea7b3000000000000000000000000f7ddedc66b1d482e5c38e4730b3357d32411e5dd0000000000000000000000000000000000000000000000000de0b6b3a7640000
5

Добавьте шаг 3: TransferFrom

Добавьте ещё один шаг. Переключитесь в режим Raw. Установите:
  • From: 0xf7ddedc66b1d482e5c38e4730b3357d32411e5dd
  • To: 0x6b175474e89094c44da98b954eedeac495271d0f
  • Calldata: 0x23b872dd000000000000000000000000e58b9ee93700a616b50509c8292977fa7a0f8ce1000000000000000000000000bd8daa414fda8a8a129f7035e7496759c5af8570000000000000000000000000000000000000000000000000006a94d74f430000
6

Запустите пакет

Нажмите Simulate (или клавиши ⌘↵). Все три шага выполняются последовательно. Нажмите каждый шаг на панели, чтобы просмотреть его декодированный вывод, использованный газ, события и трансферы токенов.
Step 2 approve result showing the Approval event
Step 3 transferFrom result showing the DAI transfer

Запуск через Simulation API

Используйте эндпоинт simulate-bundle, чтобы запустить ту же последовательность программно. Замените {accountSlug} и {projectSlug} на ваши значения.
curl --request POST \
  --url https://api.tenderly.co/api/v1/account/{accountSlug}/project/{projectSlug}/simulate-bundle \
  --header 'Content-Type: application/json' \
  --header 'X-Access-Key: <api-key>' \
  --data '{
  "simulations": [
    {
      "network_id": "1",
      "save": true,
      "save_if_fails": true,
      "simulation_type": "full",
      "from": "0xe2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2",
      "to": "0x6b175474e89094c44da98b954eedeac495271d0f",
      "input": "0x40c10f19000000000000000000000000e58b9ee93700a616b50509c8292977fa7a0f8ce10000000000000000000000000000000000000000000000001bc16d674ec80000",
      "state_objects": {
        "0x6b175474e89094c44da98b954eedeac495271d0f": {
          "storage": {
            "0xedd7d04419e9c48ceb6055956cbb4e2091ae310313a4d1fa7cbcfe7561616e03": "0x0000000000000000000000000000000000000000000000000000000000000001"
          }
        }
      }
    },
    {
      "network_id": "1",
      "save": true,
      "save_if_fails": true,
      "simulation_type": "full",
      "from": "0xe58b9ee93700a616b50509c8292977fa7a0f8ce1",
      "to": "0x6b175474e89094c44da98b954eedeac495271d0f",
      "input": "0x095ea7b3000000000000000000000000f7ddedc66b1d482e5c38e4730b3357d32411e5dd0000000000000000000000000000000000000000000000000de0b6b3a7640000"
    },
    {
      "network_id": "1",
      "save": true,
      "save_if_fails": true,
      "simulation_type": "full",
      "from": "0xf7ddedc66b1d482e5c38e4730b3357d32411e5dd",
      "to": "0x6b175474e89094c44da98b954eedeac495271d0f",
      "input": "0x23b872dd000000000000000000000000e58b9ee93700a616b50509c8292977fa7a0f8ce1000000000000000000000000bd8daa414fda8a8a129f7035e7496759c5af8570000000000000000000000000000000000000000000000000006a94d74f430000"
    }
  ]
}'
Ответ — массив из трёх результатов симуляции, по одному на шаг. Каждая запись содержит status, gasUsed, декодированные logs, trace, assetChanges и balanceChanges. Успешное выполнение показывает "status": true для всех трёх шагов. Если шаг откатывается (например, потому что в шаге 1 отсутствует переопределение состояния), API возвращает все результаты вплоть до сбойного шага включительно, а последующие шаги не выполняются.

На что обращать внимание в результатах

ШагКлючевые сигналы
MintЗапись assetChanges с "type": "Mint" и зачислением 2 DAI держателю. Событие Transfer с нулевого адреса.
ApproveСобытие Approval в logs. Изменений активов нет (approve не перемещает токены).
TransferFromЗапись assetChanges с "type": "Transfer" и перемещением 0.03 DAI от держателя к получателю. Событие Transfer в logs. balanceChanges показывает отрицательное изменение у держателя и положительное у получателя.

Следующие шаги