> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tenderly.co/llms.txt
> Use this file to discover all available pages before exploring further.

# 模拟代币 Mint、Approve 和 TransferFrom 捆绑包

> 模拟一个包含 3 个步骤的捆绑包，用于铸造 DAI、授权 spender 并转移代币，各步骤之间共享状态，用于测试多交易流程。

本指南构建了一个 3 步的模拟捆绑包，用于演练经典的 ERC-20 授权流程：向持有人铸造代币、授权 spender，然后让 spender 调用 `transferFrom`。每一步都依赖前一步创建的状态，这也是为什么捆绑（bundle）是在此正确工具的原因。

## 为什么使用捆绑？

单独模拟每笔交易会失败：

* **仅铸造**：您可以验证铸造逻辑，但持有人地址在主网上没有代币。
* **仅授权**：模拟需要持有人实际持有一定余额才能授权。
* **仅 TransferFrom**：既需要余额也需要授权都已就绪。

Sim 捆绑包会依次运行这三步，共享生成的状态。第 2 步能看到第 1 步铸造的余额。第 3 步能看到第 2 步的授权。

## 三笔交易

这三笔交易都以 Ethereum 主网上的 DAI 合约（`0x6b175474e89094c44da98b954eedeac495271d0f`）为目标。

### 第 1 步：铸造 2 DAI

```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
From:  0xe2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2  (fake ward)
To:    0x6b175474e89094c44da98b954eedeac495271d0f  (DAI)
Input: 0x40c10f19
       000000000000000000000000e58b9ee93700a616b50509c8292977fa7a0f8ce1  (recipient)
       0000000000000000000000000000000000000000000000001bc16d674ec80000  (2 DAI)
```

DAI 的 `mint` 函数由 `wards` 映射守卫：只有注册为 ward 的地址才能调用它。fake ward（`0xe2e2...e2`）在主网上并不是 ward。要让这一步成功，请添加一个**状态覆盖**，将该地址的 ward 存储槽设置为 `1`：

| 字段          | 值                                                                    |
| ----------- | -------------------------------------------------------------------- |
| 合约          | `0x6b175474e89094c44da98b954eedeac495271d0f`                         |
| Storage key | `0xedd7d04419e9c48ceb6055956cbb4e2091ae310313a4d1fa7cbcfe7561616e03` |
| Value       | `0x0000000000000000000000000000000000000000000000000000000000000001` |

storage key 为 `keccak256(abi.encode(wardAddress, 0))`，即 `wards[fakeWardAddress]` 在存储位置 `0` 处的标准 Solidity mapping 槽位。

### 第 2 步：授权 1 DAI

```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
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

```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
From:  0xf7ddedc66b1d482e5c38e4730b3357d32411e5dd  (approved spender)
To:    0x6b175474e89094c44da98b954eedeac495271d0f  (DAI)
Input: 0x23b872dd
       000000000000000000000000e58b9ee93700a616b50509c8292977fa7a0f8ce1  (sender / token holder)
       000000000000000000000000bd8daa414fda8a8a129f7035e7496759c5af8570  (recipient)
       000000000000000000000000000000000000000000000000006a94d74f430000  (0.03 DAI)
```

spender 从持有人处向接收方转移 0.03 DAI。之所以能够成功，是因为第 2 步已授予额度，而该额度仅在捆绑包的状态流中可见，链上并不存在。

## 通过 Simulator UI 运行

<Steps>
  <Step title="打开 Sim Bundle Builder">
    在左侧导航中前往 **Simulator**，然后点击 **New Simulation**。捆绑包构建器将随一个空步骤打开。
  </Step>

  <Step title="配置会话条">
    会话条横跨顶部。将 **Network** 设置为 **Mainnet**，并将 **Block** 保持为 **Pending**。
  </Step>

  <Step title="填写第 1 步：Mint">
    将该步骤切换到 **Raw** 模式。设置：

    * **From**：`0xe2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2`
    * **To**：`0x6b175474e89094c44da98b954eedeac495271d0f`
    * **Calldata**：`0x40c10f19000000000000000000000000e58b9ee93700a616b50509c8292977fa7a0f8ce10000000000000000000000000000000000000000000000001bc16d674ec80000`

    然后展开 **State overrides**，添加 DAI 合约，并输入上表中的 ward 存储槽和值。点击 **Add storage override** 提交该槽位。

    <Frame caption="填写好 mint calldata 并配置 ward 状态覆盖的第 1 步。">
      <img src="https://mintcdn.com/tenderly/XsEZlaGXYskrtN68/images/simulator-ui/sim-bundle-step1-mint-result.webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=7bc42b094c26b08690b3d36992cc2f58" alt="Step 1 mint transaction configured with state override" width="1600" height="1000" data-path="images/simulator-ui/sim-bundle-step1-mint-result.webp" />
    </Frame>
  </Step>

  <Step title="添加第 2 步：Approve">
    在捆绑包侧栏中点击 **+ Add function call**。将新步骤切换到 **Raw** 模式。设置：

    * **From**：`0xe58b9ee93700a616b50509c8292977fa7a0f8ce1`
    * **To**：`0x6b175474e89094c44da98b954eedeac495271d0f`
    * **Calldata**：`0x095ea7b3000000000000000000000000f7ddedc66b1d482e5c38e4730b3357d32411e5dd0000000000000000000000000000000000000000000000000de0b6b3a7640000`
  </Step>

  <Step title="添加第 3 步：TransferFrom">
    再添加一个步骤。切换到 **Raw** 模式。设置：

    * **From**：`0xf7ddedc66b1d482e5c38e4730b3357d32411e5dd`
    * **To**：`0x6b175474e89094c44da98b954eedeac495271d0f`
    * **Calldata**：`0x23b872dd000000000000000000000000e58b9ee93700a616b50509c8292977fa7a0f8ce1000000000000000000000000bd8daa414fda8a8a129f7035e7496759c5af8570000000000000000000000000000000000000000000000000006a94d74f430000`
  </Step>

  <Step title="运行捆绑包">
    点击 **Simulate**（或按 `⌘↵`）。三个步骤会依次执行。点击侧栏中的每个步骤即可查看其解码后的输出、消耗的 gas、事件和代币转账。

    <Frame caption="运行后的捆绑包：第 2 步（approve）已被聚焦，显示了 Approval 事件和解码后的输出。">
      <img src="https://mintcdn.com/tenderly/XsEZlaGXYskrtN68/images/simulator-ui/sim-bundle-step2-approve-result.webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=f5e7d7ea168a4e57c85d8f900b632e53" alt="Step 2 approve result showing the Approval event" width="1600" height="1000" data-path="images/simulator-ui/sim-bundle-step2-approve-result.webp" />
    </Frame>

    <Frame caption="第 3 步（transferFrom）成功。0.03 DAI 的转账在 ERC-20 transfers 标签页中可见。">
      <img src="https://mintcdn.com/tenderly/XsEZlaGXYskrtN68/images/simulator-ui/sim-bundle-step3-transfer-result.webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=3880b61afb94b616875f6c3be3d71467" alt="Step 3 transferFrom result showing the DAI transfer" width="1600" height="1000" data-path="images/simulator-ui/sim-bundle-step3-transfer-result.webp" />
    </Frame>
  </Step>
</Steps>

## 通过 Simulation API 运行

使用 `simulate-bundle` 端点以编程方式运行相同的序列。将 `{accountSlug}` 和 `{projectSlug}` 替换为您自己的值。

```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
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      | `logs` 中出现 `Approval` 事件。没有资产变化（授权不会转移代币）。                                                                                 |
| TransferFrom | `assetChanges` 条目中出现 `"type": "Transfer"`，0.03 DAI 从持有人转移到接收方。`logs` 中出现 `Transfer` 事件。`balanceChanges` 显示持有人有负增量，接收方有正增量。 |

## 后续步骤

* [Bundled Simulations（API 参考）](/simulations/bundled-simulations)：完整的端点文档和响应结构。
* [状态覆盖](/simulations/state-overrides)：如何在模拟中覆盖存储、余额和字节码。
* [Simulator UI](/simulator-ui/overview)：捆绑包构建器的布局参考和键盘快捷键。
