1use std::sync::Arc;
2
3use alloy_consensus::transaction::TransactionInfo;
4use alloy_rpc_types_trace::geth::CallFrame;
5use alloy_rpc_types_trace::geth::FourByteFrame;
6use alloy_rpc_types_trace::geth::GethDebugBuiltInTracerType;
7use alloy_rpc_types_trace::geth::GethDebugTracerType;
8use alloy_rpc_types_trace::geth::GethTrace;
9use alloy_rpc_types_trace::geth::NoopFrame;
10use alloy_rpc_types_trace::geth::call::FlatCallFrame;
11use alloy_rpc_types_trace::geth::mux::MuxFrame;
12use anyhow::anyhow;
13use itertools::Itertools;
14use log::log_enabled;
15use revm::Context;
16use revm::Database;
17use revm::DatabaseRef;
18use revm::ExecuteCommitEvm;
19use revm::ExecuteEvm;
20use revm::InspectEvm;
21use revm::Journal;
22use revm::context::BlockEnv;
23use revm::context::CfgEnv;
24use revm::context::Evm as RevmEvm;
25use revm::context::TransactTo;
26use revm::context::TxEnv;
27use revm::context::result::EVMError;
28use revm::context::result::ExecutionResult as RevmExecutionResult;
29use revm::context::result::InvalidTransaction;
30use revm::context::result::ResultAndState;
31use revm::database::CacheDB;
32use revm::handler::EthFrame;
33use revm::handler::EthPrecompiles;
34use revm::handler::instructions::EthInstructions;
35use revm::interpreter::interpreter::EthInterpreter;
36use revm::primitives::B256;
37use revm::primitives::U256;
38use revm::primitives::hardfork::SpecId;
39use revm::state::AccountInfo;
40use revm::state::EvmState;
41use revm_inspectors::tracing::FourByteInspector;
42use revm_inspectors::tracing::MuxInspector;
43use revm_inspectors::tracing::TracingInspector;
44use revm_inspectors::tracing::TracingInspectorConfig;
45use revm_inspectors::tracing::js::JsInspector;
46
47use super::evm_input::InspectorInput;
48use crate::alias::RevmAddress;
49use crate::alias::RevmBytecode;
50use crate::eth::codegen;
51use crate::eth::executor::EvmExecutionResult;
52use crate::eth::executor::EvmInput;
53use crate::eth::executor::ExecutorConfig;
54use crate::eth::primitives::Account;
55use crate::eth::primitives::Address;
56use crate::eth::primitives::BlockFilter;
57use crate::eth::primitives::Bytes;
58use crate::eth::primitives::EvmExecution;
59use crate::eth::primitives::EvmExecutionMetrics;
60use crate::eth::primitives::ExecutionAccountChanges;
61use crate::eth::primitives::ExecutionChanges;
62use crate::eth::primitives::ExecutionResult;
63use crate::eth::primitives::Gas;
64use crate::eth::primitives::Log;
65use crate::eth::primitives::MinedData;
66use crate::eth::primitives::PointInTime;
67use crate::eth::primitives::Slot;
68use crate::eth::primitives::SlotIndex;
69use crate::eth::primitives::StorageError;
70use crate::eth::primitives::StratusError;
71use crate::eth::primitives::TransactionError;
72use crate::eth::primitives::TransactionExecution;
73use crate::eth::storage::StratusStorage;
74use crate::ext::OptionExt;
75#[cfg(feature = "metrics")]
76use crate::infra::metrics;
77
78#[cfg(feature = "dev")]
80const GAS_MAX_LIMIT: u64 = 1_000_000_000;
81#[cfg(not(feature = "dev"))]
82const GAS_MAX_LIMIT: u64 = 100_000_000;
83
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
88pub 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 pub fn new(storage: Arc<StratusStorage>, config: ExecutorConfig, kind: EvmKind) -> Self {
103 tracing::info!(?config, "creating revm");
104
105 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 pub fn execute(&mut self, input: EvmInput) -> Result<EvmExecutionResult, StratusError> {
116 #[cfg(feature = "metrics")]
117 let start = metrics::now();
118
119 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 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 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 let execution = match evm_result {
144 Ok(result) => Ok(parse_revm_execution(result, session_input, session_storage_changes)?),
146
147 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 Err(EVMError::Database(e)) => {
161 tracing::warn!(reason = ?e, "evm storage error");
162 Err(e)
163 }
164
165 Err(e) => {
167 tracing::warn!(reason = ?e, "evm transaction error");
168 Err(TransactionError::EvmFailed(e.to_string()).into())
169 }
170 };
171
172 #[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.spec = spec;
190 cfg_env.tx_chain_id_check = matches!(kind, EvmKind::Transaction);
191 cfg_env.limit_contract_initcode_size = None;
192 cfg_env.disable_nonce_check = matches!(kind, EvmKind::Call);
193 cfg_env.max_blobs_per_tx = None;
194 cfg_env.tx_gas_limit_cap = None;
195 cfg_env.blob_base_fee_update_fraction = None;
196 cfg_env.disable_eip3607 = matches!(kind, EvmKind::Call);
197 cfg_env.limit_contract_code_size = Some(usize::MAX);
198 cfg_env.memory_limit = (1 << 32) - 1;
199 cfg_env.disable_balance_check = false;
200 cfg_env.disable_block_gas_limit = false;
201 cfg_env.disable_eip3541 = false;
202 cfg_env.disable_eip7623 = false;
203 cfg_env.disable_base_fee = false;
204 cfg_env.disable_fee_charge = false;
205 })
206 .modify_block_chained(|block_env: &mut BlockEnv| {
207 block_env.beneficiary = Address::COINBASE.into();
208 })
209 .modify_tx_chained(|tx_env: &mut TxEnv| {
210 tx_env.gas_priority_fee = None;
211 });
212
213 RevmEvm::new(ctx, EthInstructions::new_mainnet(), EthPrecompiles::default())
214 }
215
216 pub fn inspect(&mut self, input: InspectorInput) -> Result<GethTrace, StratusError> {
218 let InspectorInput {
219 tx_hash,
220 opts,
221 trace_unsuccessful_only,
222 } = input;
223 let tracer_type = opts.tracer.ok_or_else(|| anyhow!("no tracer type provided"))?;
224
225 if matches!(tracer_type, GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::NoopTracer)) {
226 return Ok(NoopFrame::default().into());
227 }
228
229 let (tx, mined_data): (TransactionExecution, Option<MinedData>) = self
230 .evm
231 .journaled_state
232 .database
233 .storage
234 .read_transaction(tx_hash)?
235 .ok_or_else(|| anyhow!("transaction not found: {tx_hash}"))?
236 .into();
237
238 if tx.result.execution.deployed_contract_address.is_none() && trace_unsuccessful_only && matches!(tx.result.execution.result, ExecutionResult::Success)
240 {
241 return Ok(default_trace(tracer_type, tx));
242 }
243
244 let block = self
245 .evm
246 .journaled_state
247 .database
248 .storage
249 .read_block(BlockFilter::Number(tx.evm_input.block_number))?
250 .ok_or_else(|| {
251 StratusError::Storage(StorageError::BlockNotFound {
252 filter: BlockFilter::Number(tx.evm_input.block_number),
253 })
254 })?;
255
256 let tx_info = TransactionInfo {
257 block_hash: Some(block.hash().0.0.into()),
258 hash: Some(tx_hash.0.0.into()),
259 index: mined_data.map(|data| data.index.into()),
260 block_number: Some(block.number().as_u64()),
261 base_fee: None,
262 };
263 let inspect_input: EvmInput = tx.evm_input;
264 self.evm.journaled_state.database.reset(EvmInput {
265 point_in_time: PointInTime::MinedPast(inspect_input.block_number.prev().unwrap_or_default()),
266 ..Default::default()
267 });
268
269 let spec = self.evm.cfg.spec;
270
271 let mut cache_db = CacheDB::new(&self.evm.journaled_state.database);
272 let mut evm = Self::create_evm(inspect_input.chain_id.unwrap_or_default().into(), spec, &mut cache_db, self.kind);
273
274 for tx in block.transactions.into_iter() {
276 if tx.info.hash == tx_hash {
277 break;
278 }
279 let tx_input: EvmInput = tx.execution.evm_input;
280
281 evm.fill_env(tx_input);
283 let tx = std::mem::take(&mut evm.tx);
284 evm.transact_commit(tx)?;
285 }
286
287 let trace_result: GethTrace = match tracer_type {
288 GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::FourByteTracer) => {
289 let mut inspector = FourByteInspector::default();
290 let mut evm_with_inspector = evm.with_inspector(&mut inspector);
291 evm_with_inspector.fill_env(inspect_input);
292 let tx = std::mem::take(&mut evm_with_inspector.tx);
293 evm_with_inspector.inspect_tx(tx)?;
294 FourByteFrame::from(&inspector).into()
295 }
296 GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::CallTracer) => {
297 let call_config = opts.tracer_config.into_call_config()?;
298 let mut inspector = TracingInspector::new(TracingInspectorConfig::from_geth_call_config(&call_config));
299 let mut evm_with_inspector = evm.with_inspector(&mut inspector);
300 evm_with_inspector.fill_env(inspect_input);
301 let tx = std::mem::take(&mut evm_with_inspector.tx);
302 let res = evm_with_inspector.inspect_tx(tx)?;
303 let mut trace = inspector.geth_builder().geth_call_traces(call_config, res.result.gas_used()).into();
304 enhance_trace_with_decoded_errors(&mut trace);
305 trace
306 }
307 GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::PreStateTracer) => {
308 let prestate_config = opts.tracer_config.into_pre_state_config()?;
309 let mut inspector = TracingInspector::new(TracingInspectorConfig::from_geth_prestate_config(&prestate_config));
310 let mut evm_with_inspector = evm.with_inspector(&mut inspector);
311 evm_with_inspector.fill_env(inspect_input);
312 let tx = std::mem::take(&mut evm_with_inspector.tx);
313 let res = evm_with_inspector.inspect_tx(tx)?;
314
315 inspector.geth_builder().geth_prestate_traces(&res, &prestate_config, &cache_db)?.into()
316 }
317 GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::NoopTracer) => NoopFrame::default().into(),
318 GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::MuxTracer) => {
319 let mux_config = opts.tracer_config.into_mux_config()?;
320 let mut inspector = MuxInspector::try_from_config(mux_config).map_err(|e| anyhow!(e))?;
321 let mut evm_with_inspector = evm.with_inspector(&mut inspector);
322 evm_with_inspector.fill_env(inspect_input);
323 let tx = std::mem::take(&mut evm_with_inspector.tx);
324 let res = evm_with_inspector.inspect_tx(tx)?;
325 inspector.try_into_mux_frame(&res, &cache_db, tx_info)?.into()
326 }
327 GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::FlatCallTracer) => {
328 let flat_call_config = opts.tracer_config.into_flat_call_config()?;
329 let mut inspector = TracingInspector::new(TracingInspectorConfig::from_flat_call_config(&flat_call_config));
330 let mut evm_with_inspector = evm.with_inspector(&mut inspector);
331 evm_with_inspector.fill_env(inspect_input);
332 let tx = std::mem::take(&mut evm_with_inspector.tx);
333 let res = evm_with_inspector.inspect_tx(tx)?;
334 inspector
335 .with_transaction_gas_limit(res.result.gas_used())
336 .into_parity_builder()
337 .into_localized_transaction_traces(tx_info)
338 .into()
339 }
340 GethDebugTracerType::JsTracer(code) => {
341 let mut inspector = JsInspector::new(code, opts.tracer_config.into_json()).map_err(|e| anyhow!(e.to_string()))?;
342 let mut evm_with_inspector = evm.with_inspector(&mut inspector);
343 evm_with_inspector.fill_env(inspect_input);
344 let tx = std::mem::take(&mut evm_with_inspector.tx);
345 let block = std::mem::take(&mut evm_with_inspector.block);
346 let res = evm_with_inspector.inspect_tx(tx.clone())?;
347 GethTrace::JS(inspector.json_result(res, &tx, &block, &cache_db).map_err(|e| anyhow!(e.to_string()))?)
348 }
349 };
350
351 Ok(trace_result)
352 }
353}
354
355trait TxEnvExt {
356 fn fill_env(&mut self, input: EvmInput);
357}
358
359trait EvmExt<DB: Database> {
360 fn fill_env(&mut self, input: EvmInput);
361}
362
363impl<DB: Database, INSP, I, P, F> EvmExt<DB> for RevmEvm<Context<BlockEnv, TxEnv, CfgEnv, DB>, INSP, I, P, F> {
364 fn fill_env(&mut self, input: EvmInput) {
365 self.block.fill_env(&input);
366 self.tx.fill_env(input);
367 }
368}
369
370impl TxEnvExt for TxEnv {
371 fn fill_env(&mut self, input: EvmInput) {
372 self.caller = input.from.into();
373 self.kind = match input.to {
374 Some(contract) => TransactTo::Call(contract.into()),
375 None => TransactTo::Create,
376 };
377 self.gas_limit = GAS_MAX_LIMIT;
378 self.gas_price = 0;
379 self.chain_id = input.chain_id.map_into();
380 self.nonce = input.nonce.map_into().unwrap_or_default();
381 self.data = input.data.into();
382 self.value = input.value.into();
383 self.gas_priority_fee = None;
384 }
385}
386
387trait BlockEnvExt {
388 fn fill_env(&mut self, input: &EvmInput);
389}
390
391impl BlockEnvExt for BlockEnv {
392 fn fill_env(&mut self, input: &EvmInput) {
393 self.timestamp = U256::from(*input.block_timestamp);
394 self.number = U256::from(input.block_number.as_u64());
395 self.basefee = 0;
396 }
397}
398
399struct RevmSession {
405 config: ExecutorConfig,
407
408 storage: Arc<StratusStorage>,
410
411 input: EvmInput,
413
414 storage_changes: ExecutionChanges,
416
417 metrics: EvmExecutionMetrics,
419}
420
421impl RevmSession {
422 pub fn new(storage: Arc<StratusStorage>, config: ExecutorConfig) -> Self {
424 Self {
425 config,
426 storage,
427 input: EvmInput::default(),
428 storage_changes: ExecutionChanges::default(),
429 metrics: EvmExecutionMetrics::default(),
430 }
431 }
432
433 pub fn reset(&mut self, input: EvmInput) {
435 self.input = input;
436 self.storage_changes = ExecutionChanges::default();
437 self.metrics = EvmExecutionMetrics::default();
438 }
439}
440
441impl Database for RevmSession {
442 type Error = StratusError;
443
444 fn basic(&mut self, revm_address: RevmAddress) -> Result<Option<AccountInfo>, StratusError> {
445 self.metrics.account_reads += 1;
446
447 let address: Address = revm_address.into();
449 let account = self.storage.read_account(address, self.input.point_in_time, self.input.kind)?;
450
451 if let Some(to_address) = self.input.to
453 && account.bytecode.is_none()
454 && address == to_address
455 && self.input.is_contract_call()
456 {
457 if self.config.executor_reject_not_contract {
458 return Err(TransactionError::AccountNotContract { address: to_address }.into());
459 } else {
460 tracing::warn!(%address, "evm to_account is not a contract because does not have bytecode");
461 }
462 }
463
464 if !address.is_ignored()
465 && let std::collections::hash_map::Entry::Vacant(entry) = self.storage_changes.accounts.entry(address)
466 {
467 entry.insert(ExecutionAccountChanges::from_unchanged(account.clone()));
468 }
469
470 Ok(Some(account.into()))
471 }
472
473 fn code_by_hash(&mut self, _: B256) -> Result<RevmBytecode, StratusError> {
474 todo!()
475 }
476
477 fn storage(&mut self, revm_address: RevmAddress, revm_index: U256) -> Result<U256, StratusError> {
478 self.metrics.slot_reads += 1;
479
480 let address: Address = revm_address.into();
482 let index: SlotIndex = revm_index.into();
483
484 let slot = self.storage.read_slot(address, index, self.input.point_in_time, self.input.kind)?;
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 let address: Address = address.into();
501 let account = self.storage.read_account(address, self.input.point_in_time, self.input.kind)?;
502 Ok(Some(account.into()))
503 }
504
505 fn storage_ref(&self, address: revm::primitives::Address, index: U256) -> Result<U256, Self::Error> {
506 let address: Address = address.into();
508 let index: SlotIndex = index.into();
509
510 let slot = self.storage.read_slot(address, index, self.input.point_in_time, self.input.kind)?;
512
513 Ok(slot.value.into())
514 }
515
516 fn block_hash_ref(&self, _: u64) -> Result<B256, Self::Error> {
517 todo!()
518 }
519
520 fn code_by_hash_ref(&self, _code_hash: B256) -> Result<revm::state::Bytecode, Self::Error> {
521 unimplemented!()
522 }
523}
524
525fn parse_revm_execution(revm_result: ResultAndState, input: EvmInput, execution_changes: ExecutionChanges) -> Result<EvmExecution, StratusError> {
530 let (result, tx_output, logs, gas) = parse_revm_result(revm_result.result);
531 let (changes, deployed_contract_address) = parse_revm_state(revm_result.state, execution_changes)?;
532 tracing::debug!(?result, %gas, tx_output_len = %tx_output.len(), %tx_output, "evm executed");
533
534 Ok(EvmExecution {
535 block_timestamp: input.block_timestamp,
536 result,
537 output: tx_output,
538 logs,
539 gas_used: gas,
540 changes,
541 deployed_contract_address,
542 })
543}
544
545fn parse_revm_result(result: RevmExecutionResult) -> (ExecutionResult, Bytes, Vec<Log>, Gas) {
546 match result {
547 RevmExecutionResult::Success { output, gas_used, logs, .. } => {
548 let result = ExecutionResult::Success;
549 let output = Bytes::from(output);
550 let logs = logs.into_iter().map_into().collect();
551 let gas = Gas::from(gas_used);
552 (result, output, logs, gas)
553 }
554 RevmExecutionResult::Revert { output, gas_used } => {
555 let output = Bytes::from(output);
556 let result = ExecutionResult::Reverted { reason: (&output).into() };
557 let gas = Gas::from(gas_used);
558 (result, output, Vec::new(), gas)
559 }
560 RevmExecutionResult::Halt { reason, gas_used } => {
561 let result = ExecutionResult::new_halted(format!("{reason:?}"));
562 let output = Bytes::default();
563 let gas = Gas::from(gas_used);
564 (result, output, Vec::new(), gas)
565 }
566 }
567}
568
569fn parse_revm_state(revm_state: EvmState, mut execution_changes: ExecutionChanges) -> Result<(ExecutionChanges, Option<Address>), StratusError> {
570 let mut deployed_contract_address = None;
571
572 for (revm_address, revm_account) in revm_state {
573 let address: Address = revm_address.into();
574 if address.is_ignored() {
575 continue;
576 }
577
578 tracing::debug!(
580 %address,
581 status = ?revm_account.status,
582 balance = %revm_account.info.balance,
583 nonce = %revm_account.info.nonce,
584 slots = %revm_account.storage.len(),
585 "evm account"
586 );
587
588 let (account_created, account_touched) = (revm_account.is_created(), revm_account.is_touched());
589
590 if !(account_created || account_touched) {
591 continue;
592 }
593
594 let account: Account = (revm_address, revm_account.info).into();
596 let account_modified_slots: Vec<Slot> = revm_account
597 .storage
598 .into_iter()
599 .filter_map(|(index, value)| match value.is_changed() {
600 true => Some(Slot::new(index.into(), value.present_value.into())),
601 false => None,
602 })
603 .collect();
604
605 if account_created && account.bytecode.is_some() {
606 deployed_contract_address = Some(account.address);
607 }
608
609 execution_changes.insert(account, account_modified_slots);
610 }
611 Ok((execution_changes, deployed_contract_address))
612}
613
614pub fn default_trace(tracer_type: GethDebugTracerType, tx: TransactionExecution) -> GethTrace {
615 match tracer_type {
616 GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::FourByteTracer) => FourByteFrame::default().into(),
617 GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::CallTracer) => {
619 let (typ, to) = match tx.evm_input.to {
620 Some(_) => ("CALL".to_string(), tx.evm_input.to.map_into()),
621 None => ("CREATE".to_string(), tx.result.execution.deployed_contract_address.map_into()),
622 };
623
624 CallFrame {
625 from: tx.evm_input.from.into(),
626 to,
627 typ,
628 ..Default::default()
629 }
630 .into()
631 }
632 GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::MuxTracer) => MuxFrame::default().into(),
633 GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::FlatCallTracer) => FlatCallFrame::default().into(),
634 _ => NoopFrame::default().into(),
635 }
636}
637
638fn enhance_trace_with_decoded_errors(trace: &mut GethTrace) {
640 match trace {
641 GethTrace::CallTracer(call_frame) => {
642 enhance_call_frame_errors(call_frame);
643 }
644 _ => {
645 }
647 }
648}
649
650fn enhance_call_frame_errors(frame: &mut CallFrame) {
652 if let Some(error) = frame.error.as_ref()
653 && let Some(decoded_error) = frame.output.as_ref().and_then(|output| codegen::error_sig_opt(output))
654 {
655 frame.revert_reason = Some(format!("{error}: {decoded_error}"));
656 }
657
658 for nested_call in frame.calls.iter_mut() {
659 enhance_call_frame_errors(nested_call);
660 }
661}