Skip to content

feat(anvil): add block context overrides for eth_call and eth_estimateGas #10487

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion crates/anvil/core/src/eth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -151,6 +151,7 @@ pub enum EthRequest {
WithOtherFields<TransactionRequest>,
#[serde(default)] Option<BlockId>,
#[serde(default)] Option<StateOverride>,
#[serde(default)] Option<Box<BlockOverrides>>,
),

#[serde(rename = "eth_simulateV1")]
Expand All @@ -164,6 +165,7 @@ pub enum EthRequest {
WithOtherFields<TransactionRequest>,
#[serde(default)] Option<BlockId>,
#[serde(default)] Option<StateOverride>,
#[serde(default)] Option<Box<BlockOverrides>>,
),

#[serde(rename = "eth_getTransactionByHash", with = "sequence")]
Expand Down
68 changes: 41 additions & 27 deletions crates/anvil/src/eth/api.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use super::{
backend::mem::{state, BlockRequest, State},
backend::{
db::MaybeFullDatabase,
mem::{state, BlockRequest, State},
},
sign::build_typed_transaction,
};
use crate::{
Expand Down Expand Up @@ -54,7 +57,7 @@ use alloy_rpc_types::{
},
request::TransactionRequest,
simulate::{SimulatePayload, SimulatedBlock},
state::StateOverride,
state::EvmOverrides,
trace::{
filter::TraceFilter,
geth::{GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace},
Expand Down Expand Up @@ -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,
},
Expand Down Expand Up @@ -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()
}
Expand Down Expand Up @@ -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());
}
}
Expand All @@ -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());
}
}
Expand Down Expand Up @@ -1070,16 +1077,16 @@ impl EthApi {
&self,
request: WithOtherFields<TransactionRequest>,
block_number: Option<BlockId>,
overrides: Option<StateOverride>,
overrides: EvmOverrides,
) -> Result<Bytes> {
node_info!("eth_call");
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(),
));
}
Expand Down Expand Up @@ -1201,7 +1208,7 @@ impl EthApi {
&self,
request: WithOtherFields<TransactionRequest>,
block_number: Option<BlockId>,
overrides: Option<StateOverride>,
overrides: EvmOverrides,
) -> Result<U256> {
node_info!("eth_estimateGas");
self.do_estimate_gas(
Expand Down Expand Up @@ -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());
}
}
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -2651,15 +2661,15 @@ impl EthApi {
&self,
request: WithOtherFields<TransactionRequest>,
block_number: Option<BlockId>,
overrides: Option<StateOverride>,
overrides: EvmOverrides,
) -> Result<u128> {
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(),
));
}
Expand All @@ -2672,14 +2682,18 @@ impl EthApi {
// <https://github.com/foundry-rs/foundry/issues/6036>
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?
})
Expand Down
72 changes: 27 additions & 45 deletions crates/anvil/src/eth/backend/mem/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -1359,16 +1359,19 @@ impl Backend {
request: WithOtherFields<TransactionRequest>,
fee_details: FeeDetails,
block_request: Option<BlockRequest>,
overrides: Option<StateOverride>,
overrides: EvmOverrides,
) -> Result<(InstructionResult, Option<Output>, 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::<u64>();
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))
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1740,19 +1721,20 @@ impl Backend {
block_request: Option<BlockRequest>,
opts: GethDebugTracingCallOptions,
) -> Result<GethTrace, BlockchainError> {
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<dyn MaybeFullDatabase>
} 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 {
Expand All @@ -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,
);
Expand Down Expand Up @@ -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 {
Expand Down
Loading