stratus/eth/executor/
evm.rs

1use std::cmp::min;
2use std::collections::BTreeMap;
3use std::sync::Arc;
4
5use alloy_consensus::transaction::TransactionInfo;
6use alloy_rpc_types_trace::geth::CallFrame;
7use alloy_rpc_types_trace::geth::FourByteFrame;
8use alloy_rpc_types_trace::geth::GethDebugBuiltInTracerType;
9use alloy_rpc_types_trace::geth::GethDebugTracerType;
10use alloy_rpc_types_trace::geth::GethTrace;
11use alloy_rpc_types_trace::geth::NoopFrame;
12use alloy_rpc_types_trace::geth::call::FlatCallFrame;
13use alloy_rpc_types_trace::geth::mux::MuxFrame;
14use anyhow::anyhow;
15use itertools::Itertools;
16use log::log_enabled;
17use revm::Context;
18use revm::Database;
19use revm::DatabaseRef;
20use revm::ExecuteCommitEvm;
21use revm::ExecuteEvm;
22use revm::InspectEvm;
23use revm::Journal;
24use revm::context::BlockEnv;
25use revm::context::CfgEnv;
26use revm::context::Evm as RevmEvm;
27use revm::context::TransactTo;
28use revm::context::TxEnv;
29use revm::context::result::EVMError;
30use revm::context::result::ExecutionResult as RevmExecutionResult;
31use revm::context::result::InvalidTransaction;
32use revm::context::result::ResultAndState;
33use revm::database::CacheDB;
34use revm::handler::EthFrame;
35use revm::handler::EthPrecompiles;
36use revm::handler::instructions::EthInstructions;
37use revm::interpreter::interpreter::EthInterpreter;
38use revm::primitives::B256;
39use revm::primitives::U256;
40use revm::primitives::hardfork::SpecId;
41use revm::state::AccountInfo;
42use revm::state::EvmState;
43use revm_inspectors::tracing::FourByteInspector;
44use revm_inspectors::tracing::MuxInspector;
45use revm_inspectors::tracing::TracingInspector;
46use revm_inspectors::tracing::TracingInspectorConfig;
47use revm_inspectors::tracing::js::JsInspector;
48
49use super::evm_input::InspectorInput;
50use crate::alias::RevmAddress;
51use crate::alias::RevmBytecode;
52use crate::eth::codegen;
53use crate::eth::executor::EvmExecutionResult;
54use crate::eth::executor::EvmInput;
55use crate::eth::executor::ExecutorConfig;
56use crate::eth::primitives::Account;
57use crate::eth::primitives::Address;
58use crate::eth::primitives::BlockFilter;
59use crate::eth::primitives::Bytes;
60use crate::eth::primitives::EvmExecution;
61use crate::eth::primitives::EvmExecutionMetrics;
62use crate::eth::primitives::ExecutionAccountChanges;
63use crate::eth::primitives::ExecutionChanges;
64use crate::eth::primitives::ExecutionResult;
65use crate::eth::primitives::ExecutionValueChange;
66use crate::eth::primitives::Gas;
67use crate::eth::primitives::Log;
68use crate::eth::primitives::PointInTime;
69use crate::eth::primitives::Slot;
70use crate::eth::primitives::SlotIndex;
71use crate::eth::primitives::StorageError;
72use crate::eth::primitives::StratusError;
73use crate::eth::primitives::TransactionError;
74use crate::eth::primitives::TransactionStage;
75use crate::eth::primitives::UnexpectedError;
76use crate::eth::storage::StratusStorage;
77use crate::ext::OptionExt;
78use crate::ext::not;
79#[cfg(feature = "metrics")]
80use crate::infra::metrics;
81
82/// Maximum gas limit allowed for a transaction. Prevents a transaction from consuming too many resources.
83const GAS_MAX_LIMIT: u64 = 1_000_000_000;
84type ContextWithDB = Context<BlockEnv, TxEnv, CfgEnv, RevmSession, Journal<RevmSession>>;
85type GeneralRevm<DB> =
86    RevmEvm<Context<BlockEnv, TxEnv, CfgEnv, DB>, (), EthInstructions<EthInterpreter<()>, Context<BlockEnv, TxEnv, CfgEnv, DB>>, EthPrecompiles, EthFrame>;
87
88/// Implementation of EVM using [`revm`](https://crates.io/crates/revm).
89pub struct Evm {
90    evm: RevmEvm<ContextWithDB, (), EthInstructions<EthInterpreter, ContextWithDB>, EthPrecompiles, EthFrame>,
91    kind: EvmKind,
92}
93
94#[derive(Clone, Copy)]
95pub enum EvmKind {
96    Transaction,
97    Call,
98}
99
100impl Evm {
101    /// Creates a new instance of the Evm.
102    pub fn new(storage: Arc<StratusStorage>, config: ExecutorConfig, kind: EvmKind) -> Self {
103        tracing::info!(?config, "creating revm");
104
105        // configure revm
106        let chain_id = config.executor_chain_id;
107
108        Self {
109            evm: Self::create_evm(chain_id, config.executor_evm_spec, RevmSession::new(storage, config.clone()), kind),
110            kind,
111        }
112    }
113
114    /// Execute a transaction that deploys a contract or call a contract function.
115    pub fn execute(&mut self, input: EvmInput) -> Result<EvmExecutionResult, StratusError> {
116        #[cfg(feature = "metrics")]
117        let start = metrics::now();
118
119        // configure session
120        self.evm.journaled_state.database.reset(input.clone());
121
122        self.evm.fill_env(input);
123
124        if log_enabled!(log::Level::Debug) {
125            let block_env_log = self.evm.block.clone();
126            let tx_env_log = self.evm.tx.clone();
127            // execute transaction
128            tracing::debug!(block_env = ?block_env_log, tx_env = ?tx_env_log, "executing transaction in revm");
129        }
130
131        let tx = std::mem::take(&mut self.evm.tx);
132        let evm_result = self.evm.transact(tx);
133
134        // extract results
135        let session = &mut self.evm.journaled_state.database;
136        let session_input = std::mem::take(&mut session.input);
137        let session_storage_changes = std::mem::take(&mut session.storage_changes);
138        let session_metrics = std::mem::take(&mut session.metrics);
139        #[cfg(feature = "metrics")]
140        let session_point_in_time = session.input.point_in_time;
141
142        // parse result
143        let execution = match evm_result {
144            // executed
145            Ok(result) => Ok(parse_revm_execution(result, session_input, session_storage_changes)?),
146
147            // nonce errors
148            Err(EVMError::Transaction(InvalidTransaction::NonceTooHigh { tx, state })) => Err(TransactionError::Nonce {
149                transaction: tx.into(),
150                account: state.into(),
151            }
152            .into()),
153            Err(EVMError::Transaction(InvalidTransaction::NonceTooLow { tx, state })) => Err(TransactionError::Nonce {
154                transaction: tx.into(),
155                account: state.into(),
156            }
157            .into()),
158
159            // storage error
160            Err(EVMError::Database(e)) => {
161                tracing::warn!(reason = ?e, "evm storage error");
162                Err(e)
163            }
164
165            // unexpected errors
166            Err(e) => {
167                tracing::warn!(reason = ?e, "evm transaction error");
168                Err(TransactionError::EvmFailed(e.to_string()).into())
169            }
170        };
171
172        // track metrics
173        #[cfg(feature = "metrics")]
174        {
175            metrics::inc_evm_execution(start.elapsed(), session_point_in_time, execution.is_ok());
176            metrics::inc_evm_execution_account_reads(session_metrics.account_reads);
177        }
178
179        execution.map(|execution| EvmExecutionResult {
180            execution,
181            metrics: session_metrics,
182        })
183    }
184
185    fn create_evm<DB: Database>(chain_id: u64, spec: SpecId, db: DB, kind: EvmKind) -> GeneralRevm<DB> {
186        let ctx = Context::new(db, spec)
187            .modify_cfg_chained(|cfg_env| {
188                cfg_env.chain_id = chain_id;
189                cfg_env.disable_nonce_check = matches!(kind, EvmKind::Call);
190                cfg_env.disable_eip3607 = matches!(kind, EvmKind::Call);
191                cfg_env.limit_contract_code_size = Some(usize::MAX);
192            })
193            .modify_block_chained(|block_env: &mut BlockEnv| {
194                block_env.beneficiary = Address::COINBASE.into();
195            })
196            .modify_tx_chained(|tx_env: &mut TxEnv| {
197                tx_env.gas_priority_fee = None;
198            });
199
200        RevmEvm::new(ctx, EthInstructions::new_mainnet(), EthPrecompiles::default())
201    }
202
203    /// Execute a transaction using a tracer.
204    pub fn inspect(&mut self, input: InspectorInput) -> Result<GethTrace, StratusError> {
205        let InspectorInput {
206            tx_hash,
207            opts,
208            trace_unsuccessful_only,
209        } = input;
210        let tracer_type = opts.tracer.ok_or_else(|| anyhow!("no tracer type provided"))?;
211
212        if matches!(tracer_type, GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::NoopTracer)) {
213            return Ok(NoopFrame::default().into());
214        }
215
216        let tx = self
217            .evm
218            .journaled_state
219            .database
220            .storage
221            .read_transaction(tx_hash)?
222            .ok_or_else(|| anyhow!("transaction not found: {}", tx_hash))?;
223
224        // CREATE transactions need to be traced for blockscout to work correctly
225        if tx.deployed_contract_address().is_none() && trace_unsuccessful_only && matches!(tx.result(), ExecutionResult::Success) {
226            return Ok(default_trace(tracer_type, tx));
227        }
228
229        let block = self
230            .evm
231            .journaled_state
232            .database
233            .storage
234            .read_block(BlockFilter::Number(tx.block_number()))?
235            .ok_or_else(|| {
236                StratusError::Storage(StorageError::BlockNotFound {
237                    filter: BlockFilter::Number(tx.block_number()),
238                })
239            })?;
240
241        let tx_info = TransactionInfo {
242            block_hash: Some(block.hash().0.0.into()),
243            hash: Some(tx_hash.0.0.into()),
244            index: tx.index().map_into(),
245            block_number: Some(block.number().as_u64()),
246            base_fee: None,
247        };
248        let inspect_input: EvmInput = tx.try_into()?;
249        self.evm.journaled_state.database.reset(EvmInput {
250            point_in_time: PointInTime::MinedPast(inspect_input.block_number.prev().unwrap_or_default()),
251            ..Default::default()
252        });
253
254        let spec = self.evm.cfg.spec;
255
256        let mut cache_db = CacheDB::new(&self.evm.journaled_state.database);
257        let mut evm = Self::create_evm(inspect_input.chain_id.unwrap_or_default().into(), spec, &mut cache_db, self.kind);
258
259        // Execute all transactions before target tx_hash
260        for tx in block.transactions.into_iter().sorted_by_key(|item| item.transaction_index) {
261            if tx.input.hash == tx_hash {
262                break;
263            }
264            let tx_input: EvmInput = tx.into();
265
266            // Configure EVM state
267            evm.fill_env(tx_input);
268            let tx = std::mem::take(&mut evm.tx);
269            evm.transact_commit(tx)?;
270        }
271
272        let trace_result: GethTrace = match tracer_type {
273            GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::FourByteTracer) => {
274                let mut inspector = FourByteInspector::default();
275                let mut evm_with_inspector = evm.with_inspector(&mut inspector);
276                evm_with_inspector.fill_env(inspect_input);
277                let tx = std::mem::take(&mut evm_with_inspector.tx);
278                evm_with_inspector.inspect_tx(tx)?;
279                FourByteFrame::from(&inspector).into()
280            }
281            GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::CallTracer) => {
282                let call_config = opts.tracer_config.into_call_config()?;
283                let mut inspector = TracingInspector::new(TracingInspectorConfig::from_geth_call_config(&call_config));
284                let mut evm_with_inspector = evm.with_inspector(&mut inspector);
285                evm_with_inspector.fill_env(inspect_input);
286                let tx = std::mem::take(&mut evm_with_inspector.tx);
287                let res = evm_with_inspector.inspect_tx(tx)?;
288                let mut trace = inspector.geth_builder().geth_call_traces(call_config, res.result.gas_used()).into();
289                enhance_trace_with_decoded_errors(&mut trace);
290                trace
291            }
292            GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::PreStateTracer) => {
293                let prestate_config = opts.tracer_config.into_pre_state_config()?;
294                let mut inspector = TracingInspector::new(TracingInspectorConfig::from_geth_prestate_config(&prestate_config));
295                let mut evm_with_inspector = evm.with_inspector(&mut inspector);
296                evm_with_inspector.fill_env(inspect_input);
297                let tx = std::mem::take(&mut evm_with_inspector.tx);
298                let res = evm_with_inspector.inspect_tx(tx)?;
299
300                inspector.geth_builder().geth_prestate_traces(&res, &prestate_config, &cache_db)?.into()
301            }
302            GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::NoopTracer) => NoopFrame::default().into(),
303            GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::MuxTracer) => {
304                let mux_config = opts.tracer_config.into_mux_config()?;
305                let mut inspector = MuxInspector::try_from_config(mux_config).map_err(|e| anyhow!(e))?;
306                let mut evm_with_inspector = evm.with_inspector(&mut inspector);
307                evm_with_inspector.fill_env(inspect_input);
308                let tx = std::mem::take(&mut evm_with_inspector.tx);
309                let res = evm_with_inspector.inspect_tx(tx)?;
310                inspector.try_into_mux_frame(&res, &cache_db, tx_info)?.into()
311            }
312            GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::FlatCallTracer) => {
313                let flat_call_config = opts.tracer_config.into_flat_call_config()?;
314                let mut inspector = TracingInspector::new(TracingInspectorConfig::from_flat_call_config(&flat_call_config));
315                let mut evm_with_inspector = evm.with_inspector(&mut inspector);
316                evm_with_inspector.fill_env(inspect_input);
317                let tx = std::mem::take(&mut evm_with_inspector.tx);
318                let res = evm_with_inspector.inspect_tx(tx)?;
319                inspector
320                    .with_transaction_gas_limit(res.result.gas_used())
321                    .into_parity_builder()
322                    .into_localized_transaction_traces(tx_info)
323                    .into()
324            }
325            GethDebugTracerType::JsTracer(code) => {
326                let mut inspector = JsInspector::new(code, opts.tracer_config.into_json()).map_err(|e| anyhow!(e.to_string()))?;
327                let mut evm_with_inspector = evm.with_inspector(&mut inspector);
328                evm_with_inspector.fill_env(inspect_input);
329                let tx = std::mem::take(&mut evm_with_inspector.tx);
330                let block = std::mem::take(&mut evm_with_inspector.block);
331                let res = evm_with_inspector.inspect_tx(tx.clone())?;
332                GethTrace::JS(inspector.json_result(res, &tx, &block, &cache_db).map_err(|e| anyhow!(e.to_string()))?)
333            }
334        };
335
336        Ok(trace_result)
337    }
338}
339
340trait TxEnvExt {
341    fn fill_env(&mut self, input: EvmInput);
342}
343
344trait EvmExt<DB: Database> {
345    fn fill_env(&mut self, input: EvmInput);
346}
347
348impl<DB: Database, INSP, I, P, F> EvmExt<DB> for RevmEvm<Context<BlockEnv, TxEnv, CfgEnv, DB>, INSP, I, P, F> {
349    fn fill_env(&mut self, input: EvmInput) {
350        self.block.fill_env(&input);
351        self.tx.fill_env(input);
352    }
353}
354
355impl TxEnvExt for TxEnv {
356    fn fill_env(&mut self, input: EvmInput) {
357        self.caller = input.from.into();
358        self.kind = match input.to {
359            Some(contract) => TransactTo::Call(contract.into()),
360            None => TransactTo::Create,
361        };
362        self.gas_limit = min(input.gas_limit.into(), GAS_MAX_LIMIT);
363        self.gas_price = input.gas_price;
364        self.chain_id = input.chain_id.map_into();
365        self.nonce = input.nonce.map_into().unwrap_or_default();
366        self.data = input.data.into();
367        self.value = input.value.into();
368        self.gas_priority_fee = None;
369    }
370}
371
372trait BlockEnvExt {
373    fn fill_env(&mut self, input: &EvmInput);
374}
375
376impl BlockEnvExt for BlockEnv {
377    fn fill_env(&mut self, input: &EvmInput) {
378        self.timestamp = U256::from(*input.block_timestamp);
379        self.number = U256::from(input.block_number.as_u64());
380        self.basefee = 0;
381    }
382}
383
384// -----------------------------------------------------------------------------
385// Database
386// -----------------------------------------------------------------------------
387
388/// Contextual data that is read or set durint the execution of a transaction in the EVM.
389struct RevmSession {
390    /// Executor configuration.
391    config: ExecutorConfig,
392
393    /// Service to communicate with the storage.
394    storage: Arc<StratusStorage>,
395
396    /// Input passed to EVM to execute the transaction.
397    input: EvmInput,
398
399    /// Changes made to the storage during the execution of the transaction.
400    storage_changes: ExecutionChanges,
401
402    /// Metrics collected during EVM execution.
403    metrics: EvmExecutionMetrics,
404}
405
406impl RevmSession {
407    /// Creates the base session to be used with REVM.
408    pub fn new(storage: Arc<StratusStorage>, config: ExecutorConfig) -> Self {
409        Self {
410            config,
411            storage,
412            input: EvmInput::default(),
413            storage_changes: BTreeMap::default(),
414            metrics: EvmExecutionMetrics::default(),
415        }
416    }
417
418    /// Resets the session to be used with a new transaction.
419    pub fn reset(&mut self, input: EvmInput) {
420        self.input = input;
421        self.storage_changes = BTreeMap::default();
422        self.metrics = EvmExecutionMetrics::default();
423    }
424}
425
426impl Database for RevmSession {
427    type Error = StratusError;
428
429    fn basic(&mut self, revm_address: RevmAddress) -> Result<Option<AccountInfo>, StratusError> {
430        self.metrics.account_reads += 1;
431
432        // retrieve account
433        let address: Address = revm_address.into();
434        let account = self.storage.read_account(address, self.input.point_in_time, self.input.kind)?;
435
436        // warn if the loaded account is the `to` account and it does not have a bytecode
437        if let Some(to_address) = self.input.to
438            && account.bytecode.is_none()
439            && address == to_address
440            && self.input.is_contract_call()
441        {
442            if self.config.executor_reject_not_contract {
443                return Err(TransactionError::AccountNotContract { address: to_address }.into());
444            } else {
445                tracing::warn!(%address, "evm to_account is not a contract because does not have bytecode");
446            }
447        }
448
449        // early convert response because account will be moved
450        let revm_account: AccountInfo = (&account).into();
451        // track original value, except if ignored address
452        if not(account.address.is_ignored()) {
453            self.storage_changes
454                .insert(account.address, ExecutionAccountChanges::from_original_values(account));
455        }
456
457        Ok(Some(revm_account))
458    }
459
460    fn code_by_hash(&mut self, _: B256) -> Result<RevmBytecode, StratusError> {
461        todo!()
462    }
463
464    fn storage(&mut self, revm_address: RevmAddress, revm_index: U256) -> Result<U256, StratusError> {
465        self.metrics.slot_reads += 1;
466
467        // convert slot
468        let address: Address = revm_address.into();
469        let index: SlotIndex = revm_index.into();
470
471        // load slot from storage
472        let slot = self.storage.read_slot(address, index, self.input.point_in_time, self.input.kind)?;
473
474        // track original value, except if ignored address
475        if not(address.is_ignored()) {
476            match self.storage_changes.get_mut(&address) {
477                Some(account) => {
478                    account.slots.insert(index, ExecutionValueChange::from_original(slot));
479                }
480                None => {
481                    tracing::error!(reason = "reading slot without account loaded", %address, %index);
482                    return Err(UnexpectedError::Unexpected(anyhow!("Account '{}' was expected to be loaded by EVM, but it was not", address)).into());
483                }
484            };
485        }
486
487        Ok(slot.value.into())
488    }
489
490    fn block_hash(&mut self, _: u64) -> Result<B256, StratusError> {
491        todo!()
492    }
493}
494
495impl DatabaseRef for RevmSession {
496    type Error = StratusError;
497
498    fn basic_ref(&self, address: revm::primitives::Address) -> Result<Option<AccountInfo>, Self::Error> {
499        // retrieve account
500        let address: Address = address.into();
501        let account = self.storage.read_account(address, self.input.point_in_time, self.input.kind)?;
502        let revm_account: AccountInfo = (&account).into();
503
504        Ok(Some(revm_account))
505    }
506
507    fn storage_ref(&self, address: revm::primitives::Address, index: U256) -> Result<U256, Self::Error> {
508        // convert slot
509        let address: Address = address.into();
510        let index: SlotIndex = index.into();
511
512        // load slot from storage
513        let slot = self.storage.read_slot(address, index, self.input.point_in_time, self.input.kind)?;
514
515        Ok(slot.value.into())
516    }
517
518    fn block_hash_ref(&self, _: u64) -> Result<B256, Self::Error> {
519        todo!()
520    }
521
522    fn code_by_hash_ref(&self, _code_hash: B256) -> Result<revm::state::Bytecode, Self::Error> {
523        unimplemented!()
524    }
525}
526
527// -----------------------------------------------------------------------------
528// Conversion
529// -----------------------------------------------------------------------------
530
531fn parse_revm_execution(revm_result: ResultAndState, input: EvmInput, execution_changes: ExecutionChanges) -> Result<EvmExecution, StratusError> {
532    let (result, tx_output, logs, gas) = parse_revm_result(revm_result.result);
533    let changes = parse_revm_state(revm_result.state, execution_changes)?;
534
535    tracing::debug!(?result, %gas, tx_output_len = %tx_output.len(), %tx_output, "evm executed");
536    let mut deployed_contract_address = None;
537    for (address, changes) in changes.iter() {
538        if changes.bytecode.is_modified() {
539            deployed_contract_address = Some(*address);
540        }
541    }
542
543    Ok(EvmExecution {
544        block_timestamp: input.block_timestamp,
545        result,
546        output: tx_output,
547        logs,
548        gas,
549        changes,
550        deployed_contract_address,
551    })
552}
553
554fn parse_revm_result(result: RevmExecutionResult) -> (ExecutionResult, Bytes, Vec<Log>, Gas) {
555    match result {
556        RevmExecutionResult::Success { output, gas_used, logs, .. } => {
557            let result = ExecutionResult::Success;
558            let output = Bytes::from(output);
559            let logs = logs.into_iter().map_into().collect();
560            let gas = Gas::from(gas_used);
561            (result, output, logs, gas)
562        }
563        RevmExecutionResult::Revert { output, gas_used } => {
564            let output = Bytes::from(output);
565            let result = ExecutionResult::Reverted { reason: (&output).into() };
566            let gas = Gas::from(gas_used);
567            (result, output, Vec::new(), gas)
568        }
569        RevmExecutionResult::Halt { reason, gas_used } => {
570            let result = ExecutionResult::new_halted(format!("{reason:?}"));
571            let output = Bytes::default();
572            let gas = Gas::from(gas_used);
573            (result, output, Vec::new(), gas)
574        }
575    }
576}
577
578fn parse_revm_state(revm_state: EvmState, mut execution_changes: ExecutionChanges) -> Result<ExecutionChanges, StratusError> {
579    for (revm_address, revm_account) in revm_state {
580        let address: Address = revm_address.into();
581        if address.is_ignored() {
582            continue;
583        }
584
585        // apply changes according to account status
586        tracing::debug!(
587            %address,
588            status = ?revm_account.status,
589            balance = %revm_account.info.balance,
590            nonce = %revm_account.info.nonce,
591            slots = %revm_account.storage.len(),
592            "evm account"
593        );
594        let (account_created, account_touched) = (revm_account.is_created(), revm_account.is_touched());
595
596        // parse revm types to stratus primitives
597        let account: Account = (revm_address, revm_account.info).into();
598        let account_modified_slots: Vec<Slot> = revm_account
599            .storage
600            .into_iter()
601            .filter_map(|(index, value)| match value.is_changed() {
602                true => Some(Slot::new(index.into(), value.present_value.into())),
603                false => None,
604            })
605            .collect();
606
607        // handle account created (contracts) or touched (everything else)
608        if account_created {
609            let addr = account.address;
610            let account_changes = ExecutionAccountChanges::from_modified_values(account, account_modified_slots);
611            execution_changes.insert(addr, account_changes);
612        } else if account_touched {
613            let Some(account_changes) = execution_changes.get_mut(&address) else {
614                tracing::error!(keys = ?execution_changes.keys(), %address, "account touched, but not loaded by evm");
615                return Err(UnexpectedError::Unexpected(anyhow!("Account '{}' was expected to be loaded by EVM, but it was not", address)).into());
616            };
617            account_changes.apply_modifications(account, account_modified_slots);
618        }
619    }
620    Ok(execution_changes)
621}
622
623pub fn default_trace(tracer_type: GethDebugTracerType, tx: TransactionStage) -> GethTrace {
624    match tracer_type {
625        GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::FourByteTracer) => FourByteFrame::default().into(),
626        // HACK: Spoof empty call frame to prevent Blockscout from retrying unnecessary trace calls
627        GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::CallTracer) => {
628            let (typ, to) = match tx.to() {
629                Some(_) => ("CALL".to_string(), tx.to().map_into()),
630                None => ("CREATE".to_string(), tx.deployed_contract_address().map_into()),
631            };
632
633            CallFrame {
634                from: tx.from().into(),
635                to,
636                typ,
637                ..Default::default()
638            }
639            .into()
640        }
641        GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::MuxTracer) => MuxFrame::default().into(),
642        GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::FlatCallTracer) => FlatCallFrame::default().into(),
643        _ => NoopFrame::default().into(),
644    }
645}
646
647/// Enhances a GethTrace with decoded error information.
648fn enhance_trace_with_decoded_errors(trace: &mut GethTrace) {
649    match trace {
650        GethTrace::CallTracer(call_frame) => {
651            enhance_call_frame_errors(call_frame);
652        }
653        _ => {
654            // Other trace types don't have call frames with errors to decode
655        }
656    }
657}
658
659/// Enhances a single CallFrame and recursively enhances all nested calls.
660fn enhance_call_frame_errors(frame: &mut CallFrame) {
661    if let Some(error) = frame.error.as_ref()
662        && let Some(decoded_error) = frame.output.as_ref().and_then(|output| codegen::error_sig_opt(output))
663    {
664        frame.revert_reason = Some(format!("{error}: {decoded_error}"));
665    }
666
667    for nested_call in frame.calls.iter_mut() {
668        enhance_call_frame_errors(nested_call);
669    }
670}