foundry
foundry copied to clipboard
In Anvil fork after multiple trx, the gas used is equal to: gasUsed of all previous transactions + gas used of new transaction
Component
Anvil
Have you ensured that all of these are up to date?
- [X] Foundry
- [X] Foundryup
What version of Foundry are you on?
forge 0.2.0 (2b2a499 2024-01-31T17:41:20.967549332Z)
What command(s) is the bug in?
No response
Operating System
Linux
Describe the bug
Hello,
When I send multiple times transactions to an Anvil node. The gasUsed that I get from: eth_getTransactionReceipt is invalid.
I wrote a Rust code to illustrate the issue (please look only at the main and ignore the helper methods to build the transactions).
use std::{env, str::FromStr, sync::Arc};
use dotenv::dotenv;
use ethers::{
contract::abigen,
core::k256::ecdsa::SigningKey,
providers::{Http, Middleware, Provider},
signers::{coins_bip39::English, MnemonicBuilder, Signer, Wallet},
types::{transaction::eip2718::TypedTransaction, Bytes, H160, U256, U64},
utils::{parse_units, Anvil},
};
use eyre::Result;
use reqwest::Client;
use serde_json::json;
/// The type of the wallet that we use to sign transactions
pub type WalletType = Arc<Wallet<SigningKey>>;
// Generate simple ERC20 Abi
abigen!(
Erc20,
r"[
function approve(address _spender, uint256 _value) external
]"
);
/// Create a wallet at a specific derivation index from the mnemonic.
fn create_wallet(wallet: &MnemonicBuilder<English>, index: u32) -> WalletType {
Arc::new(
wallet
.clone()
.index(index)
.expect(&format!(
"Could not build wallets from mnemonic at index {index}"
))
.build()
.expect(&format!(
"Could not build wallets from mnemonic at index {index}"
)),
)
}
/// Build the anvil test wallets
fn build_anvil_wallets(amount_of_wallets: u32) -> Vec<WalletType> {
// Anvil basic mnemonic
let mnemonic = "test test test test test test test test test test test junk";
let wallet = MnemonicBuilder::<English>::default().phrase(mnemonic);
let mut wallets = Vec::new();
for i in 0..amount_of_wallets {
wallets.push(create_wallet(&wallet, i));
}
wallets
}
/// Helper function to build multiple raw approve transactions
async fn build_transactions(
provider: Arc<Provider<Http>>,
token_address: H160,
to: H160,
amount_of_wallets: u32,
) -> Result<Vec<Bytes>> {
let wallets = build_anvil_wallets(amount_of_wallets);
let token = Erc20::new(token_address, provider.clone());
let mut transactions = Vec::new();
let chain_id = U64::from(provider.get_chainid().await?.as_u64());
for wallet in wallets {
let mut approve_call = token.approve(to, U256::max_value() - U256::from(1));
let transaction = approve_call
.tx
.as_eip1559_mut()
.expect("The trx should be in EIP1559");
let nonce = provider
.get_transaction_count(wallet.address(), None)
.await?;
transaction.from = Some(wallet.address());
transaction.nonce = Some(nonce);
transaction.max_priority_fee_per_gas = Some(parse_units("0.5", "gwei")?.into());
transaction.max_fee_per_gas = Some(parse_units("100", "gwei")?.into());
transaction.chain_id = Some(chain_id);
transaction.gas = Some(U256::from(200000));
let typed_trx = TypedTransaction::Eip1559(transaction.clone());
let signature = wallet.sign_transaction_sync(&typed_trx)?;
transactions.push(typed_trx.rlp_signed(&signature));
}
Ok(transactions)
}
/// This code will send simple "approve" transactions of the WETH token on the UniswapV2 router
/// using an anvil forked node
///
/// Then it will display the gas used by every transactions that we get from the receipt of these transactions.
#[tokio::main]
async fn main() -> Result<()> {
// The amount of wallets that we should run this POC on:
const WALLET_AMOUNT: u32 = 5;
let token_to_approve = H160::from_str("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").unwrap();
let uniswapv2_router = H160::from_str("0x7a250d5630b4cf539739df2c5dacb4c659f2488d").unwrap();
dotenv().ok();
let http_rpc = env::var("HTTP_RPC").expect("Missing HTTP_RPC environment variable");
// Create an anvil instance with no mining
let anvil = Anvil::new().fork(&http_rpc).arg("--no-mining").spawn();
let provider = Arc::new(Provider::<Http>::try_from(anvil.endpoint())?);
// Building the transactions
let transactions = build_transactions(
provider.clone(),
token_to_approve,
uniswapv2_router,
WALLET_AMOUNT,
)
.await?;
// Sending transactions and keeping the hash
let mut transaction_hashes = Vec::new();
for transaction in transactions {
let pending_transaction = provider.send_raw_transaction(transaction).await?;
println!(
"Send transaction to local anvil node with hash: {:?}",
pending_transaction.tx_hash()
);
transaction_hashes.push(pending_transaction.tx_hash());
}
// minting a new block
let client = Client::new();
let mine_request = json!({
"jsonrpc": "2.0",
"method": "anvil_mine",
"params": [1, null], // Mine 1 block, without specifying a timestamp
"id": 1,
});
client
.post(anvil.endpoint())
.json(&mine_request)
.send()
.await?;
// Getting the gas used by every transaction
for hash in transaction_hashes {
let receipt = provider.get_transaction_receipt(hash).await?.unwrap();
println!(
"Transaction: {:?} was mined at block {} and took {} gas",
receipt.transaction_hash,
receipt.block_number.unwrap(),
receipt.gas_used.unwrap()
);
}
// Getting gas used but with block receipt
let latest_block = provider.get_block_number().await?;
let block_receipt = provider.get_block_receipts(latest_block).await?;
for receipt in block_receipt {
println!(
"In BLOCK RECEIPT for trx: {:?}, I got {} gas",
receipt.transaction_hash,
receipt.gas_used.unwrap()
);
}
Ok(())
}
Cargo.toml:
tokio = { version = "1.27.0", features = ["full"] }
dotenv = "0.15.0"
eyre = "0.6"
ethers = "2.0"
serde_json = "1.0"
reqwest = "0.11"
When I run this code, I get this output:
Send transaction to local anvil node with hash: 0x2295c17dbca9b62385674fab935121bc34d13fc27f38bd2dd7b3612d338bb7ec
Send transaction to local anvil node with hash: 0x34140ab1c8aa4b9c37ed223735c41d623d80d12c101f15d35cb510f66d3c1e50
Send transaction to local anvil node with hash: 0x79e82449bb33116d620fd170e3cd52326bc5ed1790082d10d3743a26f193e9cf
Send transaction to local anvil node with hash: 0xf8de22195b2f485966e79e4ebbe9a1caf859625be5c7c0bf7b7669440059ba2b
Send transaction to local anvil node with hash: 0x7ba96072ebe7c2e46cb857a2065b8a9a279fc44dacebbd7ff9d334d264896495
Transaction: 0x2295c17dbca9b62385674fab935121bc34d13fc27f38bd2dd7b3612d338bb7ec was mined at block 19182852 and took 46364 gas
Transaction: 0x34140ab1c8aa4b9c37ed223735c41d623d80d12c101f15d35cb510f66d3c1e50 was mined at block 19182852 and took 92728 gas
Transaction: 0x79e82449bb33116d620fd170e3cd52326bc5ed1790082d10d3743a26f193e9cf was mined at block 19182852 and took 139092 gas
Transaction: 0xf8de22195b2f485966e79e4ebbe9a1caf859625be5c7c0bf7b7669440059ba2b was mined at block 19182852 and took 185456 gas
Transaction: 0x7ba96072ebe7c2e46cb857a2065b8a9a279fc44dacebbd7ff9d334d264896495 was mined at block 19182852 and took 231820 gas
In BLOCK RECEIPT for trx: 0x2295c17dbca9b62385674fab935121bc34d13fc27f38bd2dd7b3612d338bb7ec, I got 46364 gas
In BLOCK RECEIPT for trx: 0x34140ab1c8aa4b9c37ed223735c41d623d80d12c101f15d35cb510f66d3c1e50, I got 92728 gas
In BLOCK RECEIPT for trx: 0x79e82449bb33116d620fd170e3cd52326bc5ed1790082d10d3743a26f193e9cf, I got 139092 gas
In BLOCK RECEIPT for trx: 0xf8de22195b2f485966e79e4ebbe9a1caf859625be5c7c0bf7b7669440059ba2b, I got 185456 gas
In BLOCK RECEIPT for trx: 0x7ba96072ebe7c2e46cb857a2065b8a9a279fc44dacebbd7ff9d334d264896495, I got 231820 gas
As you can see:
The first gas used: Transaction: 0x2295c17dbca9b62385674fab935121bc34d13fc27f38bd2dd7b3612d338bb7ec was mined at block 19182852 and took 46364 gas
Seems to be correct (when I send real approve transactions to goerli for WETH, I get around 46364 gas used)
But when I get the receipt of the next transaction: 92728 gas
It is equal to 46364 * 2
Then the next again: 139092 gas = 46364 * 2 + 46364 ...
The bug is also the same for the block receipt.