Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

In Anvil fork after multiple trx, the gas used is equal to: gasUsed of all previous transactions + gas used of new transaction #7046

Closed
2 tasks done
Tracked by #8269
NicolasWent opened this issue Feb 8, 2024 · 1 comment
Labels
C-anvil Command: anvil T-bug Type: bug T-to-investigate Type: to investigate

Comments

@NicolasWent
Copy link

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 (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 NicolasWent added the T-bug Type: bug label Feb 8, 2024
@gakonst gakonst added this to Foundry Feb 8, 2024
@github-project-automation github-project-automation bot moved this to Todo in Foundry Feb 8, 2024
@zerosnacks zerosnacks added the T-to-investigate Type: to investigate label Jun 27, 2024
@zerosnacks zerosnacks added the C-anvil Command: anvil label Jul 11, 2024
@klkvr
Copy link
Member

klkvr commented Jul 13, 2024

Confirming this was an issue on earlier commit.

Can't reproduce on latest nightly, so closing:

Send transaction to local anvil node with hash: 0xc5a8b01236e7b86682549ff7a2c3d88f94bcf65814308dd8d334361bc574ba81
Send transaction to local anvil node with hash: 0x84d36487633eaea7d0a8d31602590a65cd9f6e69429bf9a6aa30a8bb50f2c019
Send transaction to local anvil node with hash: 0xa72ac1e9fd9e938320464fde3c04e2ae9b714f3e18330e4234832714acc24cfd
Send transaction to local anvil node with hash: 0x3bb53772c79a3b1219b1ce0ddf1299df606a1d1ad8ba8be52ef50a9c83348da4
Send transaction to local anvil node with hash: 0x5f14c3ccf7c8e63a6b49c7148da014bce56318019d011de8660287bd8059afca
Transaction: 0xc5a8b01236e7b86682549ff7a2c3d88f94bcf65814308dd8d334361bc574ba81 was mined at block 20292623 and took 46364 gas
Transaction: 0x84d36487633eaea7d0a8d31602590a65cd9f6e69429bf9a6aa30a8bb50f2c019 was mined at block 20292623 and took 46364 gas
Transaction: 0xa72ac1e9fd9e938320464fde3c04e2ae9b714f3e18330e4234832714acc24cfd was mined at block 20292623 and took 46364 gas
Transaction: 0x3bb53772c79a3b1219b1ce0ddf1299df606a1d1ad8ba8be52ef50a9c83348da4 was mined at block 20292623 and took 46364 gas
Transaction: 0x5f14c3ccf7c8e63a6b49c7148da014bce56318019d011de8660287bd8059afca was mined at block 20292623 and took 46364 gas
In BLOCK RECEIPT for trx: 0xc5a8b01236e7b86682549ff7a2c3d88f94bcf65814308dd8d334361bc574ba81, I got 46364 gas
In BLOCK RECEIPT for trx: 0x84d36487633eaea7d0a8d31602590a65cd9f6e69429bf9a6aa30a8bb50f2c019, I got 46364 gas
In BLOCK RECEIPT for trx: 0xa72ac1e9fd9e938320464fde3c04e2ae9b714f3e18330e4234832714acc24cfd, I got 46364 gas
In BLOCK RECEIPT for trx: 0x3bb53772c79a3b1219b1ce0ddf1299df606a1d1ad8ba8be52ef50a9c83348da4, I got 46364 gas
In BLOCK RECEIPT for trx: 0x5f14c3ccf7c8e63a6b49c7148da014bce56318019d011de8660287bd8059afca, I got 46364 gas

@klkvr klkvr closed this as completed Jul 13, 2024
@jenpaff jenpaff moved this from Todo to Completed in Foundry Sep 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-anvil Command: anvil T-bug Type: bug T-to-investigate Type: to investigate
Projects
Archived in project
Development

No branches or pull requests

3 participants