From 25f24e677a6a32a62512ad4f561995589ac2c7dc Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 27 Sep 2024 20:48:48 +0200 Subject: [PATCH] fix: 4844 fee fixes (#8963) * fix: use zero blob fee for estimate * add basic test * fix gas_price * support EIP-4844 with with_max_fee_per_blob_gas None * this should run succesfully once Alloy counterpart has been merged * undo max_fee_per_blob_gas != 0 check, not necessary anymore * clean up * fix setup bug from Matt * add test with signer, currently failing on Result::unwrap()` on an `Err` value: ErrorResp(ErrorPayload { code: -32003, message: "Block `blob_versioned_hashes` is not supported before the Cancun hardfork", data: None }) * able to reproduce * apply hotfix by Matt * remove debugs * use or_else, only need to do this if the blob_versioned hashes are non zero * move blob_hashes out --------- Co-authored-by: zerosnacks Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> --- crates/anvil/core/src/eth/transaction/mod.rs | 2 +- crates/anvil/src/config.rs | 3 +- crates/anvil/src/eth/backend/mem/mod.rs | 50 ++++++----- crates/anvil/src/eth/fees.rs | 2 +- crates/anvil/tests/it/eip4844.rs | 93 +++++++++++++++++++- 5 files changed, 124 insertions(+), 26 deletions(-) diff --git a/crates/anvil/core/src/eth/transaction/mod.rs b/crates/anvil/core/src/eth/transaction/mod.rs index 25e42e001b21..e03a862b86ab 100644 --- a/crates/anvil/core/src/eth/transaction/mod.rs +++ b/crates/anvil/core/src/eth/transaction/mod.rs @@ -128,7 +128,7 @@ pub fn transaction_request_to_typed( })) } // EIP4844 - (Some(3), None, _, _, _, Some(_), Some(_), Some(sidecar), to) => { + (Some(3), None, _, _, _, _, Some(_), Some(sidecar), to) => { let tx = TxEip4844 { nonce: nonce.unwrap_or_default(), max_fee_per_gas: max_fee_per_gas.unwrap_or_default(), diff --git a/crates/anvil/src/config.rs b/crates/anvil/src/config.rs index 96f4c53d5ee2..d907fc200f53 100644 --- a/crates/anvil/src/config.rs +++ b/crates/anvil/src/config.rs @@ -493,7 +493,8 @@ impl NodeConfig { { BlobExcessGasAndPrice::new(excess_blob_gas as u64) } else { - BlobExcessGasAndPrice { blob_gasprice: 0, excess_blob_gas: 0 } + // If no excess blob gas is configured, default to 0 + BlobExcessGasAndPrice::new(0) } } diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index cd23fa6d3a94..eed634bfacd5 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -1227,26 +1227,36 @@ impl Backend { }); let caller = from.unwrap_or_default(); let to = to.as_ref().and_then(TxKind::to); - env.tx = TxEnv { - caller, - gas_limit: gas_limit as u64, - gas_price: U256::from(gas_price), - gas_priority_fee: max_priority_fee_per_gas.map(U256::from), - max_fee_per_blob_gas: max_fee_per_blob_gas.map(U256::from), - transact_to: match to { - Some(addr) => TxKind::Call(*addr), - None => TxKind::Create, - }, - value: value.unwrap_or_default(), - data: input.into_input().unwrap_or_default(), - chain_id: None, - // set nonce to None so that the correct nonce is chosen by the EVM - nonce: None, - access_list: access_list.unwrap_or_default().into(), - blob_hashes: blob_versioned_hashes.unwrap_or_default(), - optimism: OptimismFields { enveloped_tx: Some(Bytes::new()), ..Default::default() }, - authorization_list: authorization_list.map(Into::into), - }; + let blob_hashes = blob_versioned_hashes.unwrap_or_default(); + env.tx = + TxEnv { + caller, + gas_limit: gas_limit as u64, + gas_price: U256::from(gas_price), + gas_priority_fee: max_priority_fee_per_gas.map(U256::from), + max_fee_per_blob_gas: max_fee_per_blob_gas + .or_else(|| { + if !blob_hashes.is_empty() { + env.block.get_blob_gasprice() + } else { + None + } + }) + .map(U256::from), + transact_to: match to { + Some(addr) => TxKind::Call(*addr), + None => TxKind::Create, + }, + value: value.unwrap_or_default(), + data: input.into_input().unwrap_or_default(), + chain_id: None, + // set nonce to None so that the correct nonce is chosen by the EVM + nonce: None, + access_list: access_list.unwrap_or_default().into(), + blob_hashes, + optimism: OptimismFields { enveloped_tx: Some(Bytes::new()), ..Default::default() }, + authorization_list: authorization_list.map(Into::into), + }; if env.block.basefee.is_zero() { // this is an edge case because the evm fails if `tx.effective_gas_price < base_fee` diff --git a/crates/anvil/src/eth/fees.rs b/crates/anvil/src/eth/fees.rs index 45b33ad0fcdf..cb1b8508fd24 100644 --- a/crates/anvil/src/eth/fees.rs +++ b/crates/anvil/src/eth/fees.rs @@ -464,7 +464,7 @@ impl FeeDetails { impl fmt::Debug for FeeDetails { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { write!(fmt, "Fees {{ ")?; - write!(fmt, "gaPrice: {:?}, ", self.gas_price)?; + write!(fmt, "gas_price: {:?}, ", self.gas_price)?; write!(fmt, "max_fee_per_gas: {:?}, ", self.max_fee_per_gas)?; write!(fmt, "max_priority_fee_per_gas: {:?}, ", self.max_priority_fee_per_gas)?; write!(fmt, "}}")?; diff --git a/crates/anvil/tests/it/eip4844.rs b/crates/anvil/tests/it/eip4844.rs index 353083409458..71a8bfd84c06 100644 --- a/crates/anvil/tests/it/eip4844.rs +++ b/crates/anvil/tests/it/eip4844.rs @@ -1,7 +1,7 @@ -use crate::utils::http_provider; +use crate::utils::{http_provider, http_provider_with_signer}; use alloy_consensus::{SidecarBuilder, SimpleCoder}; -use alloy_eips::eip4844::{DATA_GAS_PER_BLOB, MAX_DATA_GAS_PER_BLOCK}; -use alloy_network::{TransactionBuilder, TransactionBuilder4844}; +use alloy_eips::eip4844::{BLOB_TX_MIN_BLOB_GASPRICE, DATA_GAS_PER_BLOB, MAX_DATA_GAS_PER_BLOCK}; +use alloy_network::{EthereumWallet, TransactionBuilder, TransactionBuilder4844}; use alloy_primitives::U256; use alloy_provider::Provider; use alloy_rpc_types::{BlockId, TransactionRequest}; @@ -204,3 +204,90 @@ async fn can_check_blob_fields_on_genesis() { assert_eq!(block.header.blob_gas_used, Some(0)); assert_eq!(block.header.excess_blob_gas, Some(0)); } + +#[tokio::test(flavor = "multi_thread")] +async fn can_correctly_estimate_blob_gas_with_recommended_fillers() { + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Cancun.into())); + let (_api, handle) = spawn(node_config).await; + + let provider = http_provider(&handle.http_endpoint()); + + let accounts = provider.get_accounts().await.unwrap(); + let alice = accounts[0]; + let bob = accounts[1]; + + let sidecar: SidecarBuilder = SidecarBuilder::from_slice(b"Blobs are fun!"); + let sidecar = sidecar.build().unwrap(); + + let tx = TransactionRequest::default().with_to(bob).with_blob_sidecar(sidecar); + let tx = WithOtherFields::new(tx); + + // Send the transaction and wait for the broadcast. + let pending_tx = provider.send_transaction(tx).await.unwrap(); + + println!("Pending transaction... {}", pending_tx.tx_hash()); + + // Wait for the transaction to be included and get the receipt. + let receipt = pending_tx.get_receipt().await.unwrap(); + + // Grab the processed transaction. + let tx = provider.get_transaction_by_hash(receipt.transaction_hash).await.unwrap().unwrap(); + + println!( + "Transaction included in block {}", + receipt.block_number.expect("Failed to get block number") + ); + + assert!(tx.max_fee_per_blob_gas.unwrap() >= BLOB_TX_MIN_BLOB_GASPRICE); + assert_eq!(receipt.from, alice); + assert_eq!(receipt.to, Some(bob)); + assert_eq!( + receipt.blob_gas_used.expect("Expected to be EIP-4844 transaction"), + DATA_GAS_PER_BLOB as u128 + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn can_correctly_estimate_blob_gas_with_recommended_fillers_with_signer() { + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Cancun.into())); + let (_api, handle) = spawn(node_config).await; + + let signer = handle.dev_wallets().next().unwrap(); + let wallet: EthereumWallet = signer.clone().into(); + + let provider = http_provider_with_signer(&handle.http_endpoint(), wallet); + + let accounts = provider.get_accounts().await.unwrap(); + let alice = accounts[0]; + let bob = accounts[1]; + + let sidecar: SidecarBuilder = SidecarBuilder::from_slice(b"Blobs are fun!"); + let sidecar = sidecar.build().unwrap(); + + let tx = TransactionRequest::default().with_to(bob).with_blob_sidecar(sidecar); + let tx = WithOtherFields::new(tx); + + // Send the transaction and wait for the broadcast. + let pending_tx = provider.send_transaction(tx).await.unwrap(); + + println!("Pending transaction... {}", pending_tx.tx_hash()); + + // Wait for the transaction to be included and get the receipt. + let receipt = pending_tx.get_receipt().await.unwrap(); + + // Grab the processed transaction. + let tx = provider.get_transaction_by_hash(receipt.transaction_hash).await.unwrap().unwrap(); + + println!( + "Transaction included in block {}", + receipt.block_number.expect("Failed to get block number") + ); + + assert!(tx.max_fee_per_blob_gas.unwrap() >= BLOB_TX_MIN_BLOB_GASPRICE); + assert_eq!(receipt.from, alice); + assert_eq!(receipt.to, Some(bob)); + assert_eq!( + receipt.blob_gas_used.expect("Expected to be EIP-4844 transaction"), + DATA_GAS_PER_BLOB as u128 + ); +}