> ## 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.

# Foundry 作弊码教程

> 在 Virtual Environment 上部署一个作弊码演练环境，并使用 cast send 从交易中操纵余额、nonce、存储和事件日志。

本教程在 Foundry 中构建一个模块化的作弊码演练环境。您将向 Virtual Environment 部署一个合约，然后使用 `cast send` 调用其函数，从交易内部演练每一个 [Solidity 预编译作弊码](/virtual-environments/precompiles)。

## 您将构建的内容

三个文件：

* `src/IVnetPrecompile.sol`：预编译接口。
* `src/MyTestSetup.sol`：一个演练合约。每个函数封装一个预编译调用，因此您只需部署一次，然后调用各个函数来一次操纵一部分状态。
* `script/Deploy.s.sol`：一个 `forge script` 部署脚本。

### 函数映射

| 类别      | 函数                                      | 封装                                       |
| ------- | --------------------------------------- | ---------------------------------------- |
| Balance | `forceBalance(account, newBalance)`     | `setBalance`                             |
|         | `topUp(account, amount)`                | `addBalance`                             |
| Nonce   | `forceNonce(account, newNonce)`         | `setNonce`                               |
| Storage | `writeStorage(target, slot, value)`     | `setStorageAt`                           |
|         | `readStorage(target, slot) → bytes32`   | `getStorageAt`                           |
| Events  | `fireTransfer(token, from, to, amount)` | `emitEvent`（ERC-20 `Transfer`）           |
|         | `fireEvent(target, topics[], data)`     | `emitEvent`（通用，0–4 个 topics）             |
| Demo    | `demoScenario(account)`                 | 组合 balance、nonce、storage、USDC `Transfer` |

## 前提条件

* 一个包含项目的 Tenderly 账户。
* 已安装 Foundry（`curl -L https://foundry.paradigm.xyz | bash && foundryup`）。

<Steps>
  ### 创建 Virtual Environment

  在 Tenderly Dashboard 中，前往 **Virtual Environments** 并创建一个 Virtual Environment。复制 **Admin RPC** URL，公共 RPC 在充值和验证调用上会返回 401，因此本教程始终使用 Admin RPC。

  ### 初始化 Foundry 项目

  ```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  forge init tenderly-cheatcodes
  cd tenderly-cheatcodes
  ```

  ### 添加预编译接口

  保存为 `src/IVnetPrecompile.sol`：

  ```solidity title="src/IVnetPrecompile.sol" theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  // SPDX-License-Identifier: MIT
  pragma solidity ^0.8.0;

  interface IVnetPrecompile {
      function setStorageAt(address target, bytes32 slot, bytes32 value) external returns (bool);
      function getStorageAt(address target, bytes32 slot) external view returns (bytes32);
      function setBalance(address target, uint256 balance) external returns (bool);
      function addBalance(address target, uint256 amount) external returns (bool);
      function setNonce(address target, uint256 value) external returns (bool);

      function emitEvent(address target, bytes calldata data) external returns (bool);
      function emitEvent(address target, bytes32 topic1) external returns (bool);
      function emitEvent(address target, bytes32 topic1, bytes calldata data) external returns (bool);
      function emitEvent(address target, bytes32 topic1, bytes32 topic2) external returns (bool);
      function emitEvent(address target, bytes32 topic1, bytes32 topic2, bytes calldata data) external returns (bool);
      function emitEvent(address target, bytes32 topic1, bytes32 topic2, bytes32 topic3) external returns (bool);
      function emitEvent(address target, bytes32 topic1, bytes32 topic2, bytes32 topic3, bytes calldata data) external returns (bool);
      function emitEvent(address target, bytes32 topic1, bytes32 topic2, bytes32 topic3, bytes32 topic4) external returns (bool);
      function emitEvent(address target, bytes32 topic1, bytes32 topic2, bytes32 topic3, bytes32 topic4, bytes calldata data) external returns (bool);
  }
  ```

  ### 添加演练合约

  保存为 `src/MyTestSetup.sol`：

  ```solidity title="src/MyTestSetup.sol" theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  // SPDX-License-Identifier: MIT
  pragma solidity ^0.8.0;

  import {IVnetPrecompile} from "./IVnetPrecompile.sol";

  contract MyTestSetup {
      IVnetPrecompile constant VNET = IVnetPrecompile(0xc100000000000000000000000000000000000000);

      bytes32 constant TRANSFER_TOPIC = keccak256("Transfer(address,address,uint256)");

      function forceBalance(address account, uint256 amount) external {
          VNET.setBalance(account, amount);
      }

      function topUp(address account, uint256 amount) external {
          VNET.addBalance(account, amount);
      }

      function forceNonce(address account, uint256 n) external {
          VNET.setNonce(account, n);
      }

      function writeStorage(address target, bytes32 slot, bytes32 value) external {
          VNET.setStorageAt(target, slot, value);
      }

      function readStorage(address target, bytes32 slot) external view returns (bytes32) {
          return VNET.getStorageAt(target, slot);
      }

      function fireTransfer(address token, address from, address to, uint256 amount) external {
          VNET.emitEvent(
              token,
              TRANSFER_TOPIC,
              bytes32(uint256(uint160(from))),
              bytes32(uint256(uint160(to))),
              abi.encode(amount)
          );
      }

      function fireEvent(address target, bytes32[] calldata topics, bytes calldata data) external {
          if (topics.length == 0)      VNET.emitEvent(target, data);
          else if (topics.length == 1) VNET.emitEvent(target, topics[0], data);
          else if (topics.length == 2) VNET.emitEvent(target, topics[0], topics[1], data);
          else if (topics.length == 3) VNET.emitEvent(target, topics[0], topics[1], topics[2], data);
          else                         VNET.emitEvent(target, topics[0], topics[1], topics[2], topics[3], data);
      }

      function demoScenario(address account) external {
          VNET.setBalance(account, 10 ether);
          VNET.setNonce(account, 5);
          VNET.setStorageAt(account, bytes32(uint256(0)), bytes32(uint256(0x1234)));
          VNET.addBalance(account, 2 ether);
          VNET.emitEvent(
              0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48,
              TRANSFER_TOPIC,
              bytes32(uint256(0)),
              bytes32(uint256(uint160(account))),
              abi.encode(uint256(1000e6))
          );
      }
  }
  ```

  ### 添加部署脚本

  保存为 `script/Deploy.s.sol`：

  ```solidity title="script/Deploy.s.sol" theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  // SPDX-License-Identifier: MIT
  pragma solidity ^0.8.0;

  import {Script} from "forge-std/Script.sol";
  import {MyTestSetup} from "../src/MyTestSetup.sol";

  contract Deploy is Script {
      function run() external {
          vm.startBroadcast();
          new MyTestSetup();
          vm.stopBroadcast();
      }
  }
  ```

  ### 配置环境变量

  保存为 `.env`：

  ```bash title=".env" theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  VNET_RPC=https://virtual.mainnet.eu.rpc.tenderly.co/<your-virtual-env-uuid>
  PRIVATE_KEY=0x...
  DEPLOYER=0x...
  ```

  加载它：`source .env`。

  ### 为部署者注资

  ```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  cast rpc tenderly_setBalance "[\"$DEPLOYER\"]" "0x56BC75E2D63100000" --rpc-url "$VNET_RPC"
  ```

  ### 部署和验证

  ```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  forge script script/Deploy.s.sol:Deploy \
      --rpc-url "$VNET_RPC" \
      --private-key "$PRIVATE_KEY" \
      --broadcast --slow \
      --verify \
      --verifier custom \
      --verifier-url "$VNET_RPC/verify"
  ```

  从脚本输出中导出已部署的地址：

  ```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  export TEST_CONTRACT=0x...
  ```

  ### 演练作弊码

  每次调用都是一笔单独的交易。您可以随时用不同参数重新运行，无需重新部署。

  ```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  # Set 999 ETH on an arbitrary account
  cast send "$TEST_CONTRACT" "forceBalance(address,uint256)" \
      0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 999000000000000000000 \
      --rpc-url "$VNET_RPC" --private-key "$PRIVATE_KEY"

  # Set vitalik.eth's nonce to 4242
  cast send "$TEST_CONTRACT" "forceNonce(address,uint256)" \
      0xd8da6bf26964af9d7eed9e03e53415d37aa96045 4242 \
      --rpc-url "$VNET_RPC" --private-key "$PRIVATE_KEY"

  # Write storage slot 0 of a contract
  cast send "$TEST_CONTRACT" "writeStorage(address,bytes32,bytes32)" \
      "$TARGET" \
      0x0000000000000000000000000000000000000000000000000000000000000000 \
      0x0000000000000000000000000000000000000000000000000000000000001234 \
      --rpc-url "$VNET_RPC" --private-key "$PRIVATE_KEY"

  # Spoof a 1 DAI Transfer from the real DAI contract
  cast send "$TEST_CONTRACT" "fireTransfer(address,address,address,uint256)" \
      0x6B175474E89094C44Da98b954EedeAC495271d0F \
      0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC \
      0xDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD \
      1000000000000000000 \
      --rpc-url "$VNET_RPC" --private-key "$PRIVATE_KEY"

  # Spoof a BAYC ERC-721 Transfer of token #1234 (all args indexed, data is empty)
  cast send "$TEST_CONTRACT" "fireEvent(address,bytes32[],bytes)" \
      0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D \
      '[0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef,0x000000000000000000000000CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC,0x000000000000000000000000DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD,0x00000000000000000000000000000000000000000000000000000000000004d2]' \
      0x \
      --rpc-url "$VNET_RPC" --private-key "$PRIVATE_KEY"
  ```

  ### 验证状态变化

  ```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  cast balance 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA --rpc-url "$VNET_RPC"
  cast nonce   0xd8da6bf26964af9d7eed9e03e53415d37aa96045 --rpc-url "$VNET_RPC"
  cast storage "$TARGET" 0                                --rpc-url "$VNET_RPC"
  ```

  ### 确认伪造的日志

  拉取收据并检查 `logs[]` 中 `address` 与您伪造的合约匹配的条目：

  ```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  cast receipt <TX_HASH> --rpc-url "$VNET_RPC" --json | jq '.logs'
  ```

  对于 BAYC 示例，收据应包含：

  ```json theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  {
    "address": "0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d",
    "topics": [
      "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
      "0x000000000000000000000000cccccccccccccccccccccccccccccccccccccccc",
      "0x000000000000000000000000dddddddddddddddddddddddddddddddddddddddd",
      "0x00000000000000000000000000000000000000000000000000000000000004d2"
    ],
    "data": "0x"
  }
  ```

  `address` 与 BAYC 合约匹配，`topics[0]` 是 `Transfer` 事件签名哈希，`topics[1–3]` 携带索引的 `from`、`to` 和 `tokenId`。监听 BAYC 转账的索引器将以与真实发出相同的方式处理此日志。

  <Note>
    预编译还会发出诊断日志，其 `address` = `0xc100000000000000000000000000000000000000`，记录每次状态修改。按目标地址过滤 `logs[]`，可将伪造的日志与预编译自身的条目区分开来。
  </Note>
</Steps>

## 另请参阅

* [Solidity Cheatcodes 参考](/virtual-environments/precompiles)，完整的接口定义和函数表。
* [Admin RPC](/virtual-environments/admin-rpc)，通过 JSON-RPC 从交易外部操纵状态。
