Anvil debug_traceTransaction works differently for transactions mined by anvil vs ones mined before
Closed this issue · 7 comments
Component
Anvil
Have you ensured that all of these are up to date?
- Foundry
- Foundryup
What version of Foundry are you on?
forge 0.2.0 (75fc63b 2024-12-05T00:23:28.354134759Z)
What command(s) is the bug in?
anvil --steps-tracing --fork-url --port
Operating System
Linux
Describe the bug
Preface
It seems debug_traceTransaction does not work as intended if the transaction being mined was mined with anvil vs mined before anvil started:
Dependencies and versions
I wrote a thorough test for it using ethers.js (6.13.1) node 20.18.0 npm 10.8.2 anvil 0.2.0 (75fc63b 2024-12-05T00:23:28.348102608Z)
Prerequisits
To run the code below you need to do:
npm install ethers
- Fill privateKey and providerUrl in the code provided below
- ensure providerUrl u set supports debug_traceTransaction
- the private key you have should have some funds cause it actually send a tx to sepolia chain
- look at other fields notable defaultGas which is default 50gwei and default amount in is 0.001 eth
- run
npx ts-node ./test.ts
What will the code do:
- Create a buy fixed input on sepolia v2 router
- sign the tx with your private key
- send the tx to normal sepolia provider
- wait for the normal tx to be mined
- print fields for debug_traceTransaction on the normal provider on the mined normal tx
- spawn anvil
- print fields for debug_traceTransaction using anvil provider on the mined normal tx
- Create another buy fixed input on sepolia v2 router
- sign the tx with your private key
- send the tx to anvil provider
- wait for anvil tx to be mined
- print fields for debug_traceTransaction on the anvil provider for the mined anvil tx
Output:
------------------------------------------------
---------------NORMAL_PROVIDER------------------
------------------NORMAL_TX---------------------
TX Sent. TX Hash xxxxxx
TX Mined
TX Succeeded
debug_traceTransaction fields using normal provider for normal TX "xxxxxx":
Result keys [ 'post', 'pre' ]
------------------------------------------------
Stay Tuned there will be around 20 seconds wait
------------------------------------------------
---------------ANVIL_PROVIDER------------------
------------------NORMAL_TX---------------------
debug_traceTransaction fields using anvil provider for normal TX "xxxxxx":
Result keys [ 'post', 'pre' ]
------------------ANVIL_TX---------------------
TX Sent. TX Hash yyyyyy
TX Mined
TX Succeeded
debug_traceTransaction fields using anvil provider for anvil TX "yyyyyy":
Result keys []
------------------------------------------------
NOTE:
Please ping me if you have problems running the code
THE CODE:
// test.ts
import { spawn } from 'child_process';
import { Contract, type JsonRpcApiProvider, Transaction, Wallet, WebSocketProvider } from 'ethers';
const providerUrl = 'ws://FILL_THIS_DONT_RUN_WITHOUT_FILLING_THIS_THIS_IS_VERY_IMPORTANT_TO_NOTE_DO_NOT_FORGET_FILLING_THIS_FIELD_PLEEEEEEEEEEEEEEEEEEEEEASE'; // Should set it to a websocket rpc node that supports debug_traceTransaction. I couldnt find one on chainlist https://chainlist.org/?chain=97&search=11155111&testnets=true so I left this empty
const chainId = 11155111; // using sepolia for tests
const privateKey =
'0xFILL_THIS_DONT_RUN_WITHOUT_FILLING_THIS_THIS_IS_VERY_IMPORTANT_TO_NOTE_DO_NOT_FORGET_FILLING_THIS_FIELD_PLEEEEEEEEEEEEEEEEEEEEEASE';
const amountIn = 1_000_000_000_000_000n;
const v2RouterAddress = '0xC532a74256D3Db42D0Bf7a0400fEFDbad7694008';
const wethAddress = '0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9';
const usdtAddress = '0xaA8E23Fb1079EA71e0a56F48a2aA51851D8433D0';
const defaultGas = 50_000_000_000n; // 50 gwei
const maxFeePerGas = defaultGas;
const maxPriorityFeePerGas = defaultGas;
const gasLimit = 250_000n; // should be enough
const signerWallet = new Wallet(privateKey);
const signingKey = signerWallet.signingKey;
const wallet = signerWallet.address;
const wait = (ms: number) => new Promise((res) => setTimeout(res, ms));
export const getBalanceChangeDueToTransaction = async (
provider: JsonRpcApiProvider,
transactionHash: string
): Promise<null | { [address: string]: { preBalance: bigint; postBalance: bigint } }> =>
provider
.send('debug_traceTransaction', [
transactionHash,
{
tracer: 'prestateTracer',
tracerConfig: {
onlyTopCall: false,
diffMode: true,
},
},
])
.then((result) => {
// console.log('Result', result);
console.log('Result keys', Object.keys(result ?? {}));
return result;
})
.catch((error) => {
console.error('ERROR:', error);
throw error;
});
(async () => {
const normalProvider = new WebSocketProvider(providerUrl, {
name: 'eth',
chainId,
});
const v2Router = new Contract(v2RouterAddress, [
{
inputs: [
{ internalType: 'uint256', name: 'amountOutMin', type: 'uint256' },
{ internalType: 'address[]', name: 'path', type: 'address[]' },
{ internalType: 'address', name: 'to', type: 'address' },
{ internalType: 'uint256', name: 'deadline', type: 'uint256' },
],
name: 'swapExactETHForTokensSupportingFeeOnTransferTokens',
outputs: [],
stateMutability: 'payable',
type: 'function',
},
]);
const contractTx =
await v2Router.swapExactETHForTokensSupportingFeeOnTransferTokens.populateTransaction(
0n, // min amount out = 0 => 100% slippage
[wethAddress, usdtAddress], // buyPath
wallet, // deposit to = wallet
Date.now() + 600 * 1000, // deadline
{
from: wallet,
value: amountIn,
}
);
contractTx.maxFeePerGas = maxFeePerGas;
contractTx.maxPriorityFeePerGas = maxPriorityFeePerGas;
contractTx.type = 2;
contractTx.gasLimit = gasLimit;
contractTx.nonce = await normalProvider.getTransactionCount(wallet);
delete contractTx.from;
contractTx.to = v2RouterAddress;
const normalTx = Transaction.from(contractTx);
normalTx.chainId = chainId;
normalTx.signature = signingKey.sign(normalTx.unsignedHash);
const normalRawTx = normalTx.serialized;
const normalTxHash = normalTx.hash!;
console.log('------------------------------------------------');
console.log('---------------NORMAL_PROVIDER------------------');
console.log('------------------NORMAL_TX---------------------');
await normalProvider.broadcastTransaction(normalRawTx);
console.log('TX Sent. TX Hash', normalTxHash);
await normalProvider.waitForTransaction(normalTxHash, 1, 30_000);
console.log('TX Mined');
if ((await normalProvider.getTransactionReceipt(normalTxHash))?.status !== 1) {
console.log('TX Failed');
throw new Error('Transaction failed on chain');
}
console.log('TX Succeeded');
console.log(
`debug_traceTransaction fields using normal provider for normal TX "${normalTxHash}":`
);
await getBalanceChangeDueToTransaction(normalProvider, normalTxHash);
console.log('------------------------------------------------');
console.log('\nStay Tuned there will be around 20 seconds wait\n');
await wait(15000);
const anvilProcess = spawn('anvil', ['--steps-tracing', '--fork-url', providerUrl]);
await wait(1000);
const anvilProvider = new WebSocketProvider('ws://127.0.0.1:8545', {
name: 'eth',
chainId,
});
await wait(1000);
await anvilProvider.send('evm_setAutomine', [true]);
console.log('------------------------------------------------');
console.log('---------------ANVIL_PROVIDER------------------');
console.log('------------------NORMAL_TX---------------------');
console.log(
`debug_traceTransaction fields using anvil provider for normal TX "${normalTxHash}":`
);
await getBalanceChangeDueToTransaction(anvilProvider, normalTxHash);
console.log('------------------ANVIL_TX---------------------');
contractTx.nonce = await anvilProvider.getTransactionCount(wallet);
const anvilTx = Transaction.from(contractTx);
anvilTx.chainId = chainId;
anvilTx.signature = signingKey.sign(anvilTx.unsignedHash);
const anvilTxRawTx = anvilTx.serialized;
const anvilTxHash = anvilTx.hash!;
await anvilProvider.broadcastTransaction(anvilTxRawTx);
console.log('TX Sent. TX Hash', anvilTxHash);
await anvilProvider
.waitForTransaction(anvilTxHash, 1, 1_000)
.catch(() => anvilProvider.getTransactionReceipt(anvilTxHash));
console.log('TX Mined');
if ((await anvilProvider.getTransactionReceipt(anvilTxHash))?.status !== 1) {
console.log('TX Failed');
throw new Error('Transaction failed on anvil');
}
console.log('TX Succeeded');
console.log(`debug_traceTransaction fields using anvil provider for anvil TX "${anvilTxHash}":`);
await getBalanceChangeDueToTransaction(anvilProvider, anvilTxHash);
console.log('------------------------------------------------');
await anvilProvider.destroy();
anvilProcess.kill('SIGTERM');
await normalProvider.destroy();
})();
Related Issue:
@maa105 thank you, will give it a try, would it be possible to create a gh repo with the test driver, could save time if we have a repo to clone, npm install and then run (as https://github.com/mshakeg/anvil-backtester within #7039 and https://github.com/vlad-blana/foundry-anvil-lock-repro in #7275)
@maa105 thank you, will give it a try, would it be possible to create a gh repo with the test driver, could save time if we have a repo to clone, npm install and then run (as https://github.com/mshakeg/anvil-backtester within #7039 and https://github.com/vlad-blana/foundry-anvil-lock-repro in #7275)
Sure will do
Done run:
git clone git@github.com:maa105/AnvilDebugTraceTransactionIssue.git
cd AnvilDebugTraceTransactionIssue
npm start
or one liner:
git clone git@github.com:maa105/AnvilDebugTraceTransactionIssue.git&&cd AnvilDebugTraceTransactionIssue&&npm start
thank you, that's awesome. will check!
@maa105 which provider you use, I don't have debug_traceTransaction
for Sepolia available with infura nor alchemy providers
ah, looking at the code, I see what's the issue here is that you're using the prestateTracer
https://github.com/maa105/AnvilDebugTraceTransactionIssue/blob/master/index.ts#L35 which is not supported in Anvil (e.g. if you change this line to tracer: "callTracer"
you will get the result keys). this is a dupe of #8443 which we're going to add support in post v1. Thank you!
@maa105 which provider you use, I don't have
debug_traceTransaction
for Sepolia available with infura nor alchemy providers
I have my own private node running sorry cant help there :|
ah, looking at the code, I see what's the issue here is that you're using the
prestateTracer
https://github.com/maa105/AnvilDebugTraceTransactionIssue/blob/master/index.ts#L35 which is not supported in Anvil (e.g. if you change this line totracer: "callTracer"
you will get the result keys). this is a dupe of #8443 which we're going to add support in post v1. Thank you!
Alright I'll give it a shot when I have time and report back. Thanks for your time man