diff --git a/crates/anvil/core/src/eth/mod.rs b/crates/anvil/core/src/eth/mod.rs index d380c3cc47e22..03e841f279781 100644 --- a/crates/anvil/core/src/eth/mod.rs +++ b/crates/anvil/core/src/eth/mod.rs @@ -10,7 +10,7 @@ use alloy_rpc_types::{ filter::TraceFilter, geth::{GethDebugTracingCallOptions, GethDebugTracingOptions}, }, - BlockId, BlockNumberOrTag as BlockNumber, Filter, Index, + BlockId, BlockNumberOrTag as BlockNumber, BlockOverrides, Filter, Index, }; use alloy_serde::WithOtherFields; use foundry_common::serde_helpers::{ @@ -151,6 +151,7 @@ pub enum EthRequest { WithOtherFields, #[serde(default)] Option, #[serde(default)] Option, + #[serde(default)] Option>, ), #[serde(rename = "eth_simulateV1")] @@ -164,6 +165,7 @@ pub enum EthRequest { WithOtherFields, #[serde(default)] Option, #[serde(default)] Option, + #[serde(default)] Option>, ), #[serde(rename = "eth_getTransactionByHash", with = "sequence")] diff --git a/crates/anvil/src/eth/api.rs b/crates/anvil/src/eth/api.rs index 8a7e17e3e630c..984434540dab6 100644 --- a/crates/anvil/src/eth/api.rs +++ b/crates/anvil/src/eth/api.rs @@ -1,5 +1,8 @@ use super::{ - backend::mem::{state, BlockRequest, State}, + backend::{ + db::MaybeFullDatabase, + mem::{state, BlockRequest, State}, + }, sign::build_typed_transaction, }; use crate::{ @@ -54,7 +57,7 @@ use alloy_rpc_types::{ }, request::TransactionRequest, simulate::{SimulatePayload, SimulatedBlock}, - state::StateOverride, + state::EvmOverrides, trace::{ filter::TraceFilter, geth::{GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace}, @@ -84,7 +87,7 @@ use foundry_evm::{ backend::DatabaseError, decode::RevertDecoder, revm::{ - db::DatabaseRef, + db::{CacheDB, DatabaseRef}, interpreter::{return_ok, return_revert, InstructionResult}, primitives::BlockEnv, }, @@ -247,18 +250,20 @@ impl EthApi { EthRequest::EthSendRawTransaction(tx) => { self.send_raw_transaction(tx).await.to_rpc_result() } - EthRequest::EthCall(call, block, overrides) => { - self.call(call, block, overrides).await.to_rpc_result() - } + EthRequest::EthCall(call, block, state_override, block_overrides) => self + .call(call, block, EvmOverrides::new(state_override, block_overrides)) + .await + .to_rpc_result(), EthRequest::EthSimulateV1(simulation, block) => { self.simulate_v1(simulation, block).await.to_rpc_result() } EthRequest::EthCreateAccessList(call, block) => { self.create_access_list(call, block).await.to_rpc_result() } - EthRequest::EthEstimateGas(call, block, overrides) => { - self.estimate_gas(call, block, overrides).await.to_rpc_result() - } + EthRequest::EthEstimateGas(call, block, state_override, block_overrides) => self + .estimate_gas(call, block, EvmOverrides::new(state_override, block_overrides)) + .await + .to_rpc_result(), EthRequest::EthGetRawTransactionByHash(hash) => { self.raw_transaction(hash).await.to_rpc_result() } @@ -969,7 +974,8 @@ impl EthApi { if request.gas.is_none() { // estimate if not provided - if let Ok(gas) = self.estimate_gas(request.clone(), None, None).await { + if let Ok(gas) = self.estimate_gas(request.clone(), None, EvmOverrides::default()).await + { request.gas = Some(gas.to()); } } @@ -996,7 +1002,8 @@ impl EthApi { if request.gas.is_none() { // estimate if not provided - if let Ok(gas) = self.estimate_gas(request.clone(), None, None).await { + if let Ok(gas) = self.estimate_gas(request.clone(), None, EvmOverrides::default()).await + { request.gas = Some(gas.to()); } } @@ -1070,7 +1077,7 @@ impl EthApi { &self, request: WithOtherFields, block_number: Option, - overrides: Option, + overrides: EvmOverrides, ) -> Result { node_info!("eth_call"); let block_request = self.block_request(block_number).await?; @@ -1078,8 +1085,8 @@ impl EthApi { if let BlockRequest::Number(number) = block_request { if let Some(fork) = self.get_fork() { if fork.predates_fork(number) { - if overrides.is_some() { - return Err(BlockchainError::StateOverrideError( + if overrides.has_state() || overrides.has_block() { + return Err(BlockchainError::EvmOverrideError( "not available on past forked blocks".to_string(), )); } @@ -1201,7 +1208,7 @@ impl EthApi { &self, request: WithOtherFields, block_number: Option, - overrides: Option, + overrides: EvmOverrides, ) -> Result { node_info!("eth_estimateGas"); self.do_estimate_gas( @@ -2082,7 +2089,9 @@ impl EthApi { // Estimate gas if tx_req.gas.is_none() { - if let Ok(gas) = self.estimate_gas(tx_req.clone(), None, None).await { + if let Ok(gas) = + self.estimate_gas(tx_req.clone(), None, EvmOverrides::default()).await + { tx_req.gas = Some(gas.to()); } } @@ -2550,7 +2559,8 @@ impl EthApi { request.from = Some(from); - let gas_limit_fut = self.estimate_gas(request.clone(), Some(BlockId::latest()), None); + let gas_limit_fut = + self.estimate_gas(request.clone(), Some(BlockId::latest()), EvmOverrides::default()); let fees_fut = self.fee_history( U256::from(EIP1559_FEE_ESTIMATION_PAST_BLOCKS), @@ -2651,15 +2661,15 @@ impl EthApi { &self, request: WithOtherFields, block_number: Option, - overrides: Option, + overrides: EvmOverrides, ) -> Result { let block_request = self.block_request(block_number).await?; // check if the number predates the fork, if in fork mode if let BlockRequest::Number(number) = block_request { if let Some(fork) = self.get_fork() { if fork.predates_fork(number) { - if overrides.is_some() { - return Err(BlockchainError::StateOverrideError( + if overrides.has_state() || overrides.has_block() { + return Err(BlockchainError::EvmOverrideError( "not available on past forked blocks".to_string(), )); } @@ -2672,14 +2682,18 @@ impl EthApi { // self.on_blocking_task(|this| async move { this.backend - .with_database_at(Some(block_request), |mut state, block| { - if let Some(overrides) = overrides { - state = Box::new(state::apply_state_override( - overrides.into_iter().collect(), - state, - )?); + .with_database_at(Some(block_request), |state, mut block| { + let mut cache_db = CacheDB::new(state); + if let Some(state_overrides) = overrides.state { + state::apply_state_overrides( + state_overrides.into_iter().collect(), + &mut cache_db, + )?; + } + if let Some(block_overrides) = overrides.block { + state::apply_block_overrides(*block_overrides, &mut cache_db, &mut block); } - this.do_estimate_gas_with_state(request, &state, block) + this.do_estimate_gas_with_state(request, cache_db.as_dyn(), block) }) .await? }) diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index 2f5aa321080bc..086592e4e1ae6 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -52,7 +52,7 @@ use alloy_rpc_types::{ request::TransactionRequest, serde_helpers::JsonStorageKey, simulate::{SimBlock, SimCallResult, SimulatePayload, SimulatedBlock}, - state::StateOverride, + state::EvmOverrides, trace::{ filter::TraceFilter, geth::{ @@ -1359,16 +1359,19 @@ impl Backend { request: WithOtherFields, fee_details: FeeDetails, block_request: Option, - overrides: Option, + overrides: EvmOverrides, ) -> Result<(InstructionResult, Option, u128, State), BlockchainError> { - self.with_database_at(block_request, |state, block| { + self.with_database_at(block_request, |state, mut block| { let block_number = block.number.to::(); - let (exit, out, gas, state) = match overrides { - None => self.call_with_state(state.as_dyn(), request, fee_details, block), - Some(overrides) => { - let state = state::apply_state_override(overrides.into_iter().collect(), state)?; - self.call_with_state(state.as_dyn(), request, fee_details, block) - }, + let (exit, out, gas, state) = { + let mut cache_db = CacheDB::new(state); + if let Some(state_overrides) = overrides.state { + state::apply_state_overrides(state_overrides.into_iter().collect(), &mut cache_db)?; + } + if let Some(block_overrides) = overrides.block { + state::apply_block_overrides(*block_overrides, &mut cache_db, &mut block); + } + self.call_with_state(cache_db.as_dyn(), request, fee_details, block) }?; trace!(target: "backend", "call return {:?} out: {:?} gas {} on block {}", exit, out, gas, block_number); Ok((exit, out, gas, state)) @@ -1512,34 +1515,12 @@ impl Backend { let mut log_index = 0; let mut gas_used = 0; let mut transactions = Vec::with_capacity(calls.len()); - // apply state overrides before executing the transactions + // apply state and block overrides before executing the transactions if let Some(state_overrides) = state_overrides { - state::apply_cached_db_state_override(state_overrides, &mut cache_db)?; + state::apply_state_overrides(state_overrides, &mut cache_db)?; } - - // apply block overrides - if let Some(overrides) = block_overrides { - if let Some(number) = overrides.number { - block_env.number = number.saturating_to(); - } - if let Some(difficulty) = overrides.difficulty { - block_env.difficulty = difficulty; - } - if let Some(time) = overrides.time { - block_env.timestamp = U256::from(time); - } - if let Some(gas_limit) = overrides.gas_limit { - block_env.gas_limit = U256::from(gas_limit); - } - if let Some(coinbase) = overrides.coinbase { - block_env.coinbase = coinbase; - } - if let Some(random) = overrides.random { - block_env.prevrandao = Some(random); - } - if let Some(base_fee) = overrides.base_fee { - block_env.basefee = base_fee.saturating_to(); - } + if let Some(block_overrides) = block_overrides { + state::apply_block_overrides(block_overrides, &mut cache_db, &mut block_env); } // execute all calls in that block @@ -1740,19 +1721,20 @@ impl Backend { block_request: Option, opts: GethDebugTracingCallOptions, ) -> Result { - let GethDebugTracingCallOptions { tracing_options, block_overrides: _, state_overrides } = + let GethDebugTracingCallOptions { tracing_options, block_overrides, state_overrides } = opts; let GethDebugTracingOptions { config, tracer, tracer_config, .. } = tracing_options; - self.with_database_at(block_request, |state, block| { + self.with_database_at(block_request, |state, mut block| { let block_number = block.number; - let state = if let Some(overrides) = state_overrides { - Box::new(state::apply_state_override(overrides, state)?) - as Box - } else { - state - }; + let mut cache_db = CacheDB::new(state); + if let Some(state_overrides) = state_overrides { + state::apply_state_overrides(state_overrides, &mut cache_db)?; + } + if let Some(block_overrides) = block_overrides { + state::apply_block_overrides(block_overrides, &mut cache_db, &mut block); + } if let Some(tracer) = tracer { return match tracer { @@ -1768,7 +1750,7 @@ impl Backend { let env = self.build_call_env(request, fee_details, block); let mut evm = self.new_evm_with_inspector_ref( - state.as_dyn(), + cache_db.as_dyn(), env, &mut inspector, ); @@ -1803,7 +1785,7 @@ impl Backend { .with_tracing_config(TracingInspectorConfig::from_geth_config(&config)); let env = self.build_call_env(request, fee_details, block); - let mut evm = self.new_evm_with_inspector_ref(state.as_dyn(), env, &mut inspector); + let mut evm = self.new_evm_with_inspector_ref(cache_db.as_dyn(), env, &mut inspector); let ResultAndState { result, state: _ } = evm.transact()?; let (exit_reason, gas_used, out) = match result { diff --git a/crates/anvil/src/eth/backend/mem/state.rs b/crates/anvil/src/eth/backend/mem/state.rs index aea154a7bd573..d5bb17cb24715 100644 --- a/crates/anvil/src/eth/backend/mem/state.rs +++ b/crates/anvil/src/eth/backend/mem/state.rs @@ -3,13 +3,13 @@ use crate::eth::error::BlockchainError; use alloy_primitives::{keccak256, Address, B256, U256}; use alloy_rlp::Encodable; -use alloy_rpc_types::state::StateOverride; +use alloy_rpc_types::{state::StateOverride, BlockOverrides}; use alloy_trie::{HashBuilder, Nibbles}; use foundry_evm::{ backend::DatabaseError, revm::{ db::{CacheDB, DatabaseRef, DbAccount}, - primitives::{AccountInfo, Bytecode, HashMap}, + primitives::{AccountInfo, BlockEnv, Bytecode, HashMap}, }, }; @@ -70,21 +70,8 @@ pub fn trie_account_rlp(info: &AccountInfo, storage: &HashMap) -> Ve out } -/// Applies the given state overrides to the state, returning a new CacheDB state -pub fn apply_state_override( - overrides: StateOverride, - state: D, -) -> Result, BlockchainError> -where - D: DatabaseRef, -{ - let mut cache_db = CacheDB::new(state); - apply_cached_db_state_override(overrides, &mut cache_db)?; - Ok(cache_db) -} - /// Applies the given state overrides to the given CacheDB -pub fn apply_cached_db_state_override( +pub fn apply_state_overrides( overrides: StateOverride, cache_db: &mut CacheDB, ) -> Result<(), BlockchainError> @@ -134,3 +121,50 @@ where } Ok(()) } + +/// Applies the given block overrides to the env and updates overridden block hashes in the db. +pub fn apply_block_overrides( + overrides: BlockOverrides, + cache_db: &mut CacheDB, + env: &mut BlockEnv, +) { + let BlockOverrides { + number, + difficulty, + time, + gas_limit, + coinbase, + random, + base_fee, + block_hash, + } = overrides; + + if let Some(block_hashes) = block_hash { + // override block hashes + cache_db + .block_hashes + .extend(block_hashes.into_iter().map(|(num, hash)| (U256::from(num), hash))) + } + + if let Some(number) = number { + env.number = number; + } + if let Some(difficulty) = difficulty { + env.difficulty = difficulty; + } + if let Some(time) = time { + env.timestamp = U256::from(time); + } + if let Some(gas_limit) = gas_limit { + env.gas_limit = U256::from(gas_limit); + } + if let Some(coinbase) = coinbase { + env.coinbase = coinbase; + } + if let Some(random) = random { + env.prevrandao = Some(random); + } + if let Some(base_fee) = base_fee { + env.basefee = base_fee; + } +} diff --git a/crates/anvil/src/eth/error.rs b/crates/anvil/src/eth/error.rs index 6666f8021dcfe..023be10378d59 100644 --- a/crates/anvil/src/eth/error.rs +++ b/crates/anvil/src/eth/error.rs @@ -60,6 +60,8 @@ pub enum BlockchainError { AlloyForkProvider(#[from] TransportError), #[error("EVM error {0:?}")] EvmError(InstructionResult), + #[error("Evm override error: {0}")] + EvmOverrideError(String), #[error("Invalid url {0:?}")] InvalidUrl(String), #[error("Internal error: {0:?}")] @@ -428,6 +430,9 @@ impl ToRpcResponseResult for Result { err @ BlockchainError::EvmError(_) => { RpcError::internal_error_with(err.to_string()) } + err @ BlockchainError::EvmOverrideError(_) => { + RpcError::invalid_params(err.to_string()) + } err @ BlockchainError::InvalidUrl(_) => RpcError::invalid_params(err.to_string()), BlockchainError::Internal(err) => RpcError::internal_error_with(err), err @ BlockchainError::BlockOutOfRange(_, _) => { diff --git a/crates/anvil/tests/it/fork.rs b/crates/anvil/tests/it/fork.rs index cda5fc805cf8c..fa756a9a56e13 100644 --- a/crates/anvil/tests/it/fork.rs +++ b/crates/anvil/tests/it/fork.rs @@ -11,6 +11,7 @@ use alloy_provider::Provider; use alloy_rpc_types::{ anvil::Forking, request::{TransactionInput, TransactionRequest}, + state::EvmOverrides, BlockId, BlockNumberOrTag, }; use alloy_serde::WithOtherFields; @@ -870,7 +871,7 @@ async fn test_fork_call() { ..Default::default() }), None, - None, + EvmOverrides::default(), ) .await .unwrap(); @@ -1313,7 +1314,7 @@ async fn test_fork_execution_reverted() { ..Default::default() }), Some(target.into()), - None, + EvmOverrides::default(), ) .await; diff --git a/crates/anvil/tests/it/transaction.rs b/crates/anvil/tests/it/transaction.rs index fc049614e3bdd..98e7e609e76d8 100644 --- a/crates/anvil/tests/it/transaction.rs +++ b/crates/anvil/tests/it/transaction.rs @@ -6,7 +6,7 @@ use alloy_network::{EthereumWallet, TransactionBuilder, TransactionResponse}; use alloy_primitives::{address, hex, map::B256HashSet, Address, Bytes, FixedBytes, U256}; use alloy_provider::Provider; use alloy_rpc_types::{ - state::{AccountOverride, StateOverride}, + state::{AccountOverride, EvmOverrides, StateOverride}, AccessList, AccessListItem, BlockId, BlockNumberOrTag, BlockTransactions, TransactionRequest, }; use alloy_serde::WithOtherFields; @@ -1090,7 +1090,7 @@ async fn estimates_gas_on_pending_by_default() { .to(sender) .value(U256::from(1e10)) .input(Bytes::from(vec![0x42]).into()); - api.estimate_gas(WithOtherFields::new(tx), None, None).await.unwrap(); + api.estimate_gas(WithOtherFields::new(tx), None, EvmOverrides::default()).await.unwrap(); } #[tokio::test(flavor = "multi_thread")] @@ -1107,7 +1107,8 @@ async fn test_estimate_gas() { .value(U256::from(1e10)) .input(Bytes::from(vec![0x42]).into()); // Expect the gas estimation to fail due to insufficient funds. - let error_result = api.estimate_gas(WithOtherFields::new(tx.clone()), None, None).await; + let error_result = + api.estimate_gas(WithOtherFields::new(tx.clone()), None, EvmOverrides::default()).await; assert!(error_result.is_err(), "Expected an error due to insufficient funds"); let error_message = error_result.unwrap_err().to_string(); @@ -1125,7 +1126,7 @@ async fn test_estimate_gas() { // Estimate gas with state override implying sufficient funds. let gas_estimate = api - .estimate_gas(WithOtherFields::new(tx), None, Some(state_override)) + .estimate_gas(WithOtherFields::new(tx), None, EvmOverrides::new(Some(state_override), None)) .await .expect("Failed to estimate gas with state override"); @@ -1242,5 +1243,5 @@ async fn estimates_gas_prague() { .with_input(hex!("0xcafebabe")) .with_from(address!("0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266")) .with_to(address!("0x70997970c51812dc3a010c7d01b50e0d17dc79c8")); - api.estimate_gas(WithOtherFields::new(req), None, None).await.unwrap(); + api.estimate_gas(WithOtherFields::new(req), None, EvmOverrides::default()).await.unwrap(); }