🤔 Got questions? Schedule an office hours session.
Simulations
State Overrides

Simulation API With State Overrides

State overrides allow you to customize the network state for the purpose of your simulation. This feature is useful when you need to test scenarios under conditions that aren’t currently present on the live network.

To illustrate how state overrides work, let’s use the DAI stablecoin contract. In the DAI system, certain actions, like minting new DAI tokens, can only be performed by addresses designated as wards. If your address isn’t a ward, you can’t test minting operations.

Setting up as a ward through state overrides

To simulate a transaction where we mint DAI, we need to override the state to make our address appear as a ward. We need to adjust the wards map in the DAI contract to include our address with the necessary permissions.

To be able to mint, the sender of the transaction must be a ward. In other words, the following condition must hold: wards['0xe2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2'] == 1.

In the API request, assign a map of overrides to the state_objects object. This map points to specific storage locations in the smart contract and assigns new values for the simulation.

  • DAI contract address: 0x6b175474e89094c44da98b954eedeac495271d0f
  • Storage slot location: 0xedd7d04419e9c48ceb6055956cbb4e2091ae310313a4d1fa7cbcfe7561616e03
  • Override value: 0x0000000000000000000000000000000000000000000000000000000000000001
example.json
{
  "state_objects": {
    "0x6b175474e89094c44da98b954eedeac495271d0f": {
      "storage": {
        "0xedd7d04419e9c48ceb6055956cbb4e2091ae310313a4d1fa7cbcfe7561616e03": "0x0000000000000000000000000000000000000000000000000000000000000001"
      }
    }
  }
}

The script below shows how to set the overrides and send a request to Simulation API.

Make sure to have TENDERLY_ACCOUNT_SLUG and TENDERLY_ACCESS_KEY set as environment variables. Learn how to obtain the slug and access key.

example.ts
import axios from 'axios';
import * as dotenv from 'dotenv';
import { ethers } from 'ethers';
 
dotenv.config();
 
// assuming environment variables TENDERLY_ACCOUNT_SLUG, TENDERLY_PROJECT_SLUG and TENDERLY_ACCESS_KEY are set
// https://docs.tenderly.co/account/projects/account-project-slug
// https://docs.tenderly.co/account/projects/how-to-generate-api-access-token
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();

Storage Slot Calculation

The key part of setting an override is calculating the correct storage location. This depends on the structure of the contract’s storage and the specific variable you want to override.

For the DAI contract, the storage location for the wards mapping for our address is calculated based on the Solidity storage layout rules, and is given by this expression:

example.js
const fakeWardAddress = '0xe2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2';
const overrideStorageLocation = keccak256(
  concatenate(
    hexZeroPad(fakeWardAddress), hexZeroPad(0x0)
  )
)

In our example, the storage location is: 0xedd7d04419e9c48ceb6055956cbb4e2091ae310313a4d1fa7cbcfe7561616e03.