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));
})()
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,
}
]
});
eth_call
state override json structure is not valid, should be:const stateOverride = {
[usdtContractAddress]: {
stateDiff: {
[mappingSlot]: fakeUsdtBalance,
}
}
}
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)
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