Search code examples
ethereum

How to simulate modification of ERC20 token balance using Ethereum State Override?


I am attempting to simulate the modification of an ERC20 token balance using Ethereum’s State Override feature. I have injected the token contract code into the EVM and used State Override to modify the balance of a target account. However, the result of querying the balance of the target account always returns 0, no matter how I adjust my code.

I have tried various methods, including using different fake code, modifying different state variables, etc. However, the result is always incorrect.

I would like to know where I am going wrong, or if there is another way to use State Override to simulate the modification of an ERC20 token balance?

I have tried with following code with change USDT balance, but not work:



(async () => {
    const Web3 = require('web3');
    const web3 = new Web3('http://192.168.50.159:7546'); 

    const targetAddress = '0xb7BF3e961645b8ebB75A622A07983335B61cf5c0'; // replace with the target account address
    const usdtContractAddress = '0xdac17f958d2ee523a2206206994597c13d831ec7'; //  USDT contract address
    const fakeUsdtCode = '0x606060405260008054600160a060020a03191673ffffffffffffffffffffffffffffffffffffffff9091169150506103c3806100516000396000f3';

    const fakeUsdtBalance = '0x00000000000000000000000000000000000000000000000000000000000000ff'; // set the fake USDT balance to 255

    const result = await web3.eth.call({
        to: usdtContractAddress,
        data: '0x70a08231000000000000000000000000' + targetAddress.substring(2), // USDT balanceOf() function
        stateOverride: {
            account: targetAddress,
            code: fakeUsdtCode,
            storage: {
                [web3.utils.sha3('balances(' + targetAddress + ')')]: fakeUsdtBalance
            }
        }
    });

    console.log('USDT balance of ' + targetAddress + ': ' + web3.utils.hexToNumberString(result));
})()



Solution

    1. web3.eth.call doesn't support state override parameters.

    The argument formatter of eth_call ignores the stateOverride field. You can extend web3.eth and call the RPC method manually:

    web3.eth.extend({
        methods: [
            {
                name: 'callWithState',
                call: 'eth_call',
                params: 3,
            }
        ]
    });
    
    1. eth_call state override json structure is not valid, should be:

    const stateOverride = {
        [usdtContractAddress]: {
            stateDiff: {
                [mappingSlot]: fakeUsdtBalance,
            }
        }
    }
    
    1. Storage key is wrong.

    You should take the slot number into consideration. You calculate it from the source code: https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7#code The balances state variable has the slot number - 2. (I've written an article about slots and storage simple-deep-dive-into-evm-storage)

    The correct slot calculation would be:

    let mValue = web3.eth.abi.encodeParameters (['address', 'uint256'], [targetAddress, 2]);
    let mSlot = web3.utils.keccak256(mValue)
    
    1. Something wrong with your overridden bytecode.

    Geth throws an exception: stack underflow (2 <=> 3)


    To summarize the code:

    web3.eth.extend({
      methods: [
        {
          name: 'callWithState',
          call: 'eth_call',
          params: 3,
        }
      ]
    });
    
    const targetAddress = '0xb7BF3e961645b8ebB75A622A07983335B61cf5c0';
    const usdtContractAddress = '0xdac17f958d2ee523a2206206994597c13d831ec7';
    const fakeUsdtCode = '0x606060405260008054600160a060020a03191673ffffffffffffffffffffffffffffffffffffffff9091169150506103c3806100516000396000f3';
    
    const fakeUsdtBalance = '0x00000000000000000000000000000000000000000000000000000000000000ff';
    
    const mValue = web3.eth.abi.encodeParameters(['address', 'uint256'], [targetAddress, 2]);
    const mSlot = web3.utils.keccak256(mValue)
    
    const callParams = {
      to: usdtContractAddress,
      data: '0x70a08231000000000000000000000000' + targetAddress.substring(2),
    };
    const stateDiff = {
      [usdtContractAddress]: {
        //code: fakeUsdtCode,
        stateDiff: {
          [mSlot]: fakeUsdtBalance,
        },
      }
    };
    
    const result = await web3.eth.callWithState(callParams, 'latest', stateDiff);
    console.log(`USDT balance: ${result}`);
    //>  USDT balance: 0x00000000000000000000000000000000000000000000000000000000000000ff