foundry icon indicating copy to clipboard operation
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

Open NicolasWent opened this issue 1 year ago • 0 comments

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.

NicolasWent avatar Feb 08 '24 10:02 NicolasWent