跳转到主要内容
状态覆盖(State overrides)允许您为模拟的目的自定义网络状态。当您需要在实时网络上当前不存在的条件下测试场景时,此功能非常有用。要通过 dashboard 而不是 API 应用相同的覆盖,请参阅 Simulator UI 中的状态覆盖 为了说明状态覆盖如何工作,我们以 DAI 稳定币合约为例。在 DAI 系统中,某些操作(例如铸造新的 DAI 代币)只能由被指定为 wards 的地址执行。如果您的地址不是 ward,就无法测试铸币操作。

通过状态覆盖将自己设置为 ward

为了模拟铸造 DAI 的交易,我们需要覆盖状态,使我们的地址显示为 ward。我们需要调整 DAI 合约中的 wards 映射,以包含我们的地址及必要的权限。 要能够铸币,交易的发送方必须是 ward。换句话说,必须满足以下条件:wards['0xe2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2'] == 1 在 API 请求中,将覆盖映射分配给 state_objects 对象。此映射指向智能合约中特定的存储位置,并为模拟分配新值。
  • DAI 合约地址:0x6b175474e89094c44da98b954eedeac495271d0f
  • 存储槽位置:0xedd7d04419e9c48ceb6055956cbb4e2091ae310313a4d1fa7cbcfe7561616e03
  • 覆盖值:0x0000000000000000000000000000000000000000000000000000000000000001
example.json
{
  "state_objects": {
    "0x6b175474e89094c44da98b954eedeac495271d0f": {
      "storage": {
        "0xedd7d04419e9c48ceb6055956cbb4e2091ae310313a4d1fa7cbcfe7561616e03": "0x0000000000000000000000000000000000000000000000000000000000000001"
      }
    }
  }
}
下方脚本展示了如何设置覆盖并向 Simulation API 发送请求。 请确保已将 TENDERLY_ACCOUNT_SLUGTENDERLY_ACCESS_KEY 设置为环境变量。了解如何获取 slug 和访问密钥。
example.ts

dotenv.config();

// assuming environment variables TENDERLY_ACCOUNT_SLUG, TENDERLY_PROJECT_SLUG and TENDERLY_ACCESS_KEY are set
// https://docs.tenderly.co/platform/account/projects/slug
// https://docs.tenderly.co/platform/account/projects/api-tokens
const { TENDERLY_ACCOUNT_SLUG, TENDERLY_PROJECT_SLUG, TENDERLY_ACCESS_KEY } = process.env;

const mintDai = async () => {
  console.time('Simulation');
  const fakeWardAddress = '0xe2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2';
  // calculate the storage location of `wards[fakeWardAddress]`
  // yields 0xedd7d04419e9c48ceb6055956cbb4e2091ae310313a4d1fa7cbcfe7561616e03
  const overrideStorageLocation = ethers.utils.keccak256(
    ethers.utils.concat([
      ethers.utils.hexZeroPad(fakeWardAddress, 32), // the ward address (address 0x000..0) - mapping key
      ethers.utils.hexZeroPad('0x0', 32), // the wards slot is 0th in the DAI contract - the mapping variable
    ]),
  );
  console.log('Storage Override Location', overrideStorageLocation);
  const mint = (
    await axios.post(
      `https://api.tenderly.co/api/v1/account/${TENDERLY_ACCOUNT_SLUG}/project/${TENDERLY_PROJECT_SLUG}/simulate`,
      // the transaction
      {
        save: true,
        save_if_fails: true,
        simulation_type: 'full',
        network_id: '1',
        from: '0xe2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2',
        to: '0x6b175474e89094c44da98b954eedeac495271d0f',
        input:
          '0x40c10f19000000000000000000000000e58b9ee93700a616b50509c8292977fa7a0f8ce10000000000000000000000000000000000000000000000001bc16d674ec80000',
        gas: 8000000,
        state_objects: {
          '0x6b175474e89094c44da98b954eedeac495271d0f': {
            storage: {
              '0xedd7d04419e9c48ceb6055956cbb4e2091ae310313a4d1fa7cbcfe7561616e03':
                '0x0000000000000000000000000000000000000000000000000000000000000001',
            },
          },
        },
      },
      {
        headers: {
          'X-Access-Key': TENDERLY_ACCESS_KEY as string,
        },
      },
    )
  ).data;

  console.timeEnd('Simulation');
  console.log(JSON.stringify(mint, null, 2));
  // TODO: extract token transferred
};

mintDai();

存储槽计算

设置覆盖的关键部分是计算正确的存储位置。这取决于合约存储的结构以及您要覆盖的具体变量。 对于 DAI 合约,我们地址对应的 wards 映射的存储位置根据 Solidity 存储布局规则计算,由以下表达式给出:
example.js
const fakeWardAddress = '0xe2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2';
const overrideStorageLocation = keccak256(
  concatenate(
    hexZeroPad(fakeWardAddress), hexZeroPad(0x0)
  )
)
在我们的示例中,存储位置为:0xedd7d04419e9c48ceb6055956cbb4e2091ae310313a4d1fa7cbcfe7561616e03

探索

如何覆盖 Gnosis Safe 合约