use std::collections::HashMap;
use crate::{
evm::TraceEvm,
traits::{ResetDB, TraceOutput, TransactionTrace},
types::{SimulationBatch, SimulationTx, SlotAccess, StateOverride, StorageDiff},
};
use crate::errors::{EvmError, RuntimeError};
use crate::traits::TraceInspector;
use revm::{
context::{ContextTr, TxEnv},
context_interface::result::ExecutionResult,
database::{CacheDB, Database, DatabaseCommit, DatabaseRef},
handler::MainnetContext,
ExecuteEvm, InspectEvm,
};
impl<DB, INSP> TraceEvm<DB, INSP>
where
DB: Database + DatabaseCommit,
INSP: TraceInspector<MainnetContext<DB>>,
{
fn trace_internal(
&mut self,
input: SimulationTx,
is_stateful: bool,
) -> Result<(ExecutionResult, StorageDiff, INSP::Output), RuntimeError> {
self.reset_inspector();
let nonce = self
.db()
.basic(input.caller)
.map_err(|e| RuntimeError::ExecutionFailed(format!("Failed to get account info: {e}")))?
.map(|acc| acc.nonce)
.unwrap_or_default();
let chain_id = self.cfg.chain_id;
let tx = TxEnv::builder()
.caller(input.caller)
.value(input.value)
.data(input.data)
.kind(input.transact_to)
.nonce(nonce)
.chain_id(Some(chain_id))
.build_fill();
self.set_tx(tx);
let result = self.inspect_replay().map_err(|e| {
RuntimeError::ExecutionFailed(format!("Inspector execution failed: {e}"))
})?;
let state = result.state;
let result = result.result;
let mut diffs = HashMap::new();
for (address, account) in state.iter() {
for (slot, value) in account.storage.iter() {
if value.original_value != value.present_value {
diffs
.entry(*address)
.or_insert_with(Vec::new)
.push(SlotAccess {
address: *address,
slot: *slot,
old_value: value.original_value,
new_value: value.present_value,
is_write: true,
});
}
}
}
if is_stateful {
self.db().commit(state)
} else {
self.inspector.reset_slot_cache();
}
let output = self.get_inspector_output();
Ok((result, diffs, output))
}
}
impl<DB, INSP> TransactionTrace<MainnetContext<CacheDB<DB>>> for TraceEvm<CacheDB<DB>, INSP>
where
DB: DatabaseRef,
INSP: TraceInspector<MainnetContext<CacheDB<DB>>>,
{
type Inspector = INSP;
fn trace_transactions(
&mut self,
batch: SimulationBatch,
) -> Vec<
Result<
(
ExecutionResult,
StorageDiff,
<Self::Inspector as TraceOutput>::Output,
),
EvmError,
>,
> {
let SimulationBatch {
transactions,
is_stateful,
overrides,
} = batch;
let len = transactions.len();
self.reset_db();
self.inspector.reset_slot_cache();
let mut override_error: Option<EvmError> = None;
if let Some(overrides) = overrides {
let StateOverride { storages, balances } = overrides;
for (address, slots) in storages {
for (slot, value) in slots {
if let Err(e) = self.db().insert_account_storage(address, slot, value) {
override_error = Some(EvmError::OverrideError(format!(
"Failed to set storage override for {address}:{slot} = {value}: {e}"
)));
break;
}
}
}
if override_error.is_none() {
for (address, balance) in balances {
let account = self.db().load_account(address);
if let Err(e) = account {
override_error = Some(EvmError::OverrideError(format!(
"Failed to load account {address} for balance override: {e}"
)));
break;
} else {
let account = account.unwrap();
account.info.balance = balance;
}
}
}
}
if let Some(e) = override_error {
return std::iter::repeat_with(|| Err(e.clone()))
.take(len)
.collect();
}
let mut results = Vec::with_capacity(len);
for input in transactions.into_iter() {
let result = self
.trace_internal(input, is_stateful)
.map_err(EvmError::Runtime);
results.push(result);
}
self.reset_inspector();
self.set_tx(Default::default());
results
}
}
impl<DB, INSP> TraceEvm<CacheDB<DB>, INSP>
where
DB: DatabaseRef,
INSP: TraceInspector<MainnetContext<CacheDB<DB>>>,
{
pub fn execute_batch(
&mut self,
batch: SimulationBatch,
) -> Vec<Result<ExecutionResult, EvmError>> {
self.trace_transactions(batch)
.into_iter()
.map(|result| result.map(|(exec_result, _, _)| exec_result))
.collect()
}
}