Skip to main content

anvil/eth/backend/
executor.rs

1use crate::{
2    eth::{
3        backend::cheats::CheatsManager, error::InvalidTransactionError,
4        pool::transactions::PoolTransaction,
5    },
6    mem::inspector::{AnvilInspector, InspectorTxConfig},
7};
8use alloy_consensus::{
9    Eip658Value, Receipt, ReceiptWithBloom, Transaction, TransactionEnvelope, TxReceipt,
10    transaction::{Either, Recovered},
11};
12use alloy_eips::{
13    Encodable2718, eip2935, eip4788,
14    eip6110::DEPOSIT_REQUEST_TYPE,
15    eip7685::Requests,
16    eip7702::{RecoveredAuthority, RecoveredAuthorization},
17};
18use alloy_evm::{
19    Evm, FromRecoveredTx, FromTxWithEncoded, RecoveredTx,
20    block::{
21        BlockExecutionError, BlockExecutionResult, BlockExecutor, BlockValidationError,
22        ExecutableTx, GasOutput, StateDB, SystemCaller, TxResult,
23    },
24    eth::{
25        EthTxResult,
26        eip6110::parse_deposits_from_receipts,
27        receipt_builder::{ReceiptBuilder, ReceiptBuilderCtx},
28        spec::EthExecutorSpec,
29    },
30};
31use alloy_hardforks::{EthereumHardfork, EthereumHardforks, ForkCondition};
32use alloy_primitives::{Address, B256, Bytes, Log, U256};
33use anvil_core::eth::transaction::{
34    MaybeImpersonatedTransaction, PendingTransaction, TransactionInfo,
35};
36use foundry_evm::core::{env::FoundryTransaction, evm::IntoInstructionResult};
37use foundry_primitives::{FoundryReceiptEnvelope, FoundryTxEnvelope, FoundryTxType};
38use revm::{
39    Database, DatabaseCommit,
40    context::Block as RevmBlock,
41    context_interface::result::{ExecutionResult, Output, ResultAndState},
42    interpreter::InstructionResult,
43    primitives::hardfork::SpecId,
44    state::{AccountInfo, EvmState},
45};
46use std::{fmt, fmt::Debug, mem::take, sync::Arc};
47
48/// Determines whether an executor produces a complete block or a historical transaction prefix.
49#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
50pub(crate) enum BlockExecutionKind {
51    /// Apply all pre- and post-block transitions.
52    #[default]
53    Complete,
54    /// Apply block-start transitions, but do not drain post-block request queues.
55    TransactionPrefix,
56}
57
58/// Ethereum-only consensus transition configuration for an Anvil block executor.
59#[derive(Clone, Copy, Debug)]
60pub(crate) struct EthereumBlockTransitions {
61    pub(crate) hardfork: EthereumHardfork,
62    pub(crate) deposit_contract_address: Address,
63    pub(crate) parent_beacon_block_root: Option<B256>,
64    pub(crate) execution_kind: BlockExecutionKind,
65}
66
67/// A hardfork specification whose configured Ethereum fork is active from genesis.
68#[derive(Clone, Copy, Debug)]
69struct ActiveEthereumSpec {
70    hardfork: EthereumHardfork,
71    deposit_contract_address: Address,
72}
73
74impl EthereumHardforks for ActiveEthereumSpec {
75    fn ethereum_fork_activation(&self, fork: EthereumHardfork) -> ForkCondition {
76        if fork <= self.hardfork { ForkCondition::ZERO_TIMESTAMP } else { ForkCondition::Never }
77    }
78}
79
80impl EthExecutorSpec for ActiveEthereumSpec {
81    fn deposit_contract_address(&self) -> Option<Address> {
82        Some(self.deposit_contract_address)
83    }
84}
85
86/// Applies canonical Ethereum block-start transitions in consensus order.
87pub(crate) fn apply_ethereum_pre_execution_changes<E>(
88    evm: &mut E,
89    parent_hash: B256,
90    transitions: EthereumBlockTransitions,
91) -> Result<(), BlockExecutionError>
92where
93    E: Evm<DB: DatabaseCommit>,
94{
95    let mut caller = SystemCaller::new(ActiveEthereumSpec {
96        hardfork: transitions.hardfork,
97        deposit_contract_address: transitions.deposit_contract_address,
98    });
99    caller.apply_blockhashes_contract_call(parent_hash, evm)?;
100    caller.apply_beacon_root_contract_call(transitions.parent_beacon_block_root, evm)
101}
102
103/// Collects deposits before draining the withdrawal and consolidation request queues.
104pub(crate) fn apply_ethereum_post_execution_changes<E>(
105    evm: &mut E,
106    transitions: EthereumBlockTransitions,
107    receipts: &[FoundryReceiptEnvelope],
108) -> Result<Requests, BlockExecutionError>
109where
110    E: Evm<DB: DatabaseCommit>,
111{
112    if transitions.hardfork < EthereumHardfork::Prague {
113        return Ok(Requests::default());
114    }
115
116    let spec = ActiveEthereumSpec {
117        hardfork: transitions.hardfork,
118        deposit_contract_address: transitions.deposit_contract_address,
119    };
120    let mut requests = Requests::default();
121    append_deposit_requests(spec, receipts, &mut requests)?;
122    SystemCaller::new(spec).append_post_execution_changes(evm, &mut requests)?;
123    Ok(requests)
124}
125
126fn append_deposit_requests(
127    spec: ActiveEthereumSpec,
128    receipts: &[FoundryReceiptEnvelope],
129    requests: &mut Requests,
130) -> Result<(), BlockExecutionError> {
131    let deposits = parse_deposits_from_receipts(spec, receipts)?;
132    if !deposits.is_empty() {
133        requests.push_request_with_type(DEPOSIT_REQUEST_TYPE, deposits);
134    }
135    Ok(())
136}
137
138/// Receipt builder for Foundry/Anvil that handles all transaction types
139#[derive(Debug, Default, Clone, Copy)]
140#[non_exhaustive]
141pub struct FoundryReceiptBuilder;
142
143impl FoundryReceiptBuilder {
144    fn wrap_receipt(
145        tx_type: FoundryTxType,
146        receipt: ReceiptWithBloom<Receipt>,
147    ) -> FoundryReceiptEnvelope {
148        match tx_type {
149            FoundryTxType::Legacy => FoundryReceiptEnvelope::Legacy(receipt),
150            FoundryTxType::Eip2930 => FoundryReceiptEnvelope::Eip2930(receipt),
151            FoundryTxType::Eip1559 => FoundryReceiptEnvelope::Eip1559(receipt),
152            FoundryTxType::Eip4844 => FoundryReceiptEnvelope::Eip4844(receipt),
153            FoundryTxType::Eip7702 => FoundryReceiptEnvelope::Eip7702(receipt),
154            #[cfg(feature = "optimism")]
155            FoundryTxType::Deposit => {
156                unreachable!("deposit receipts require fork-specific metadata")
157            }
158            #[cfg(feature = "optimism")]
159            FoundryTxType::PostExec => FoundryReceiptEnvelope::PostExec(receipt),
160            FoundryTxType::Tempo => FoundryReceiptEnvelope::Tempo(receipt),
161        }
162    }
163
164    /// Builds a typed receipt for an RPC-simulated transaction.
165    pub(crate) fn build_simulated_receipt(
166        tx_type: FoundryTxType,
167        result: &ExecutionResult,
168        logs: Vec<Log>,
169        cumulative_gas_used: u64,
170        post_state: Option<B256>,
171        deposit_nonce: Option<u64>,
172        deposit_receipt_version: Option<u64>,
173    ) -> FoundryReceiptEnvelope {
174        let status = post_state
175            .map(Eip658Value::PostState)
176            .unwrap_or_else(|| Eip658Value::Eip658(result.is_success()));
177        let receipt = Receipt { status, cumulative_gas_used, logs }.with_bloom();
178        #[cfg(feature = "optimism")]
179        if tx_type == FoundryTxType::Deposit {
180            return FoundryReceiptEnvelope::Deposit(
181                op_alloy_consensus::OpDepositReceiptWithBloom {
182                    receipt: op_alloy_consensus::OpDepositReceipt {
183                        inner: receipt.receipt,
184                        deposit_nonce,
185                        deposit_receipt_version,
186                    },
187                    logs_bloom: receipt.logs_bloom,
188                },
189            );
190        }
191        #[cfg(not(feature = "optimism"))]
192        let _ = (deposit_nonce, deposit_receipt_version);
193        Self::wrap_receipt(tx_type, receipt)
194    }
195}
196
197impl ReceiptBuilder for FoundryReceiptBuilder {
198    type Transaction = FoundryTxEnvelope;
199    type Receipt = FoundryReceiptEnvelope;
200
201    fn build_receipt<E: Evm>(
202        &self,
203        ctx: ReceiptBuilderCtx<'_, FoundryTxType, E>,
204    ) -> FoundryReceiptEnvelope {
205        let receipt = Receipt {
206            status: Eip658Value::Eip658(ctx.result.is_success()),
207            cumulative_gas_used: ctx.cumulative_gas_used,
208            logs: ctx.result.into_logs(),
209        }
210        .with_bloom();
211        Self::wrap_receipt(ctx.tx_type, receipt)
212    }
213}
214
215/// Result of executing a transaction in [`AnvilBlockExecutor`].
216///
217/// Wraps [`EthTxResult`] with the sender address, needed for deposit nonce resolution.
218#[derive(Debug)]
219pub struct AnvilTxResult<H> {
220    pub inner: EthTxResult<H, FoundryTxType>,
221    pub sender: Address,
222}
223
224impl<H: Send + 'static> TxResult for AnvilTxResult<H> {
225    type HaltReason = H;
226
227    fn result(&self) -> &ResultAndState<Self::HaltReason> {
228        self.inner.result()
229    }
230
231    fn into_result(self) -> ResultAndState<Self::HaltReason> {
232        self.inner.into_result()
233    }
234}
235
236/// Block executor for Anvil that implements [`BlockExecutor`].
237///
238/// Wraps an EVM instance and produces [`FoundryReceiptEnvelope`] receipts.
239/// Validation (gas limits, blob gas, transaction validity) is handled by the
240/// caller before transactions are fed to this executor.
241pub struct AnvilBlockExecutor<E> {
242    /// The EVM instance used for execution.
243    evm: E,
244    /// Parent block hash — needed for EIP-2935 system call.
245    parent_hash: B256,
246    /// The active spec id, used to gate hardfork-specific behavior.
247    spec_id: SpecId,
248    /// Canonical Ethereum consensus transitions, disabled for other networks.
249    ethereum_transitions: Option<EthereumBlockTransitions>,
250    /// Receipt builder.
251    receipt_builder: FoundryReceiptBuilder,
252    /// Receipts of executed transactions.
253    receipts: Vec<FoundryReceiptEnvelope>,
254    /// Total gas used by transactions in this block.
255    gas_used: u64,
256    /// Blob gas used by the block.
257    blob_gas_used: u64,
258    /// State changes captured for deferred publication.
259    state_changes: Option<Vec<EvmState>>,
260}
261
262impl<E: fmt::Debug> fmt::Debug for AnvilBlockExecutor<E> {
263    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
264        f.debug_struct("AnvilBlockExecutor")
265            .field("evm", &self.evm)
266            .field("parent_hash", &self.parent_hash)
267            .field("spec_id", &self.spec_id)
268            .field("ethereum_transitions", &self.ethereum_transitions)
269            .field("gas_used", &self.gas_used)
270            .field("blob_gas_used", &self.blob_gas_used)
271            .field("receipts", &self.receipts.len())
272            .finish_non_exhaustive()
273    }
274}
275
276impl<E> AnvilBlockExecutor<E> {
277    /// Creates a new [`AnvilBlockExecutor`].
278    pub(crate) const fn new(
279        evm: E,
280        parent_hash: B256,
281        spec_id: SpecId,
282        ethereum_transitions: Option<EthereumBlockTransitions>,
283    ) -> Self {
284        Self {
285            evm,
286            parent_hash,
287            spec_id,
288            ethereum_transitions,
289            receipt_builder: FoundryReceiptBuilder,
290            receipts: Vec::new(),
291            gas_used: 0,
292            blob_gas_used: 0,
293            state_changes: None,
294        }
295    }
296
297    /// Captures every committed changeset so callers can publish them after block execution.
298    pub(crate) fn with_state_changes(mut self) -> Self {
299        self.state_changes = Some(Vec::new());
300        self
301    }
302
303    /// Takes the captured changesets.
304    pub(crate) fn take_state_changes(&mut self) -> Vec<EvmState> {
305        self.state_changes.take().unwrap_or_default()
306    }
307}
308
309impl<E> BlockExecutor for AnvilBlockExecutor<E>
310where
311    E: Evm<
312            DB: StateDB,
313            Tx: FromRecoveredTx<FoundryTxEnvelope> + FromTxWithEncoded<FoundryTxEnvelope>,
314        >,
315{
316    type Transaction = FoundryTxEnvelope;
317    type Receipt = FoundryReceiptEnvelope;
318    type Evm = E;
319    type Result = AnvilTxResult<E::HaltReason>;
320
321    fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> {
322        if let Some(transitions) = self.ethereum_transitions {
323            // Historical fork-prefix publication needs the individual changesets after executing
324            // against its disposable overlay. Preserve canonical ordering while capturing them.
325            if let Some(state_changes) = &mut self.state_changes {
326                if transitions.hardfork >= EthereumHardfork::Prague {
327                    let result = self
328                        .evm
329                        .transact_system_call(
330                            eip4788::SYSTEM_ADDRESS,
331                            eip2935::HISTORY_STORAGE_ADDRESS,
332                            Bytes::copy_from_slice(self.parent_hash.as_slice()),
333                        )
334                        .map_err(BlockExecutionError::other)?;
335                    state_changes.push(result.state.clone());
336                    self.evm.db_mut().commit(result.state);
337                }
338                if transitions.hardfork >= EthereumHardfork::Cancun {
339                    let parent_beacon_block_root = transitions
340                        .parent_beacon_block_root
341                        .ok_or(BlockValidationError::MissingParentBeaconBlockRoot)?;
342                    let result = self
343                        .evm
344                        .transact_system_call(
345                            eip4788::SYSTEM_ADDRESS,
346                            eip4788::BEACON_ROOTS_ADDRESS,
347                            Bytes::copy_from_slice(parent_beacon_block_root.as_slice()),
348                        )
349                        .map_err(BlockExecutionError::other)?;
350                    state_changes.push(result.state.clone());
351                    self.evm.db_mut().commit(result.state);
352                }
353                return Ok(());
354            }
355            apply_ethereum_pre_execution_changes(&mut self.evm, self.parent_hash, transitions)?;
356        }
357        Ok(())
358    }
359
360    fn execute_transaction_without_commit(
361        &mut self,
362        tx: impl ExecutableTx<Self>,
363    ) -> Result<Self::Result, BlockExecutionError> {
364        let (tx_env, tx) = tx.into_parts();
365
366        let block_available_gas = self.evm.block().gas_limit() - self.gas_used;
367        if tx.tx().gas_limit() > block_available_gas {
368            return Err(BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas {
369                transaction_gas_limit: tx.tx().gas_limit(),
370                block_available_gas,
371            }
372            .into());
373        }
374
375        let sender = *tx.signer();
376
377        let result = self.evm.transact(tx_env).map_err(|err| {
378            let hash = tx.tx().trie_hash();
379            BlockExecutionError::evm(err, hash)
380        })?;
381
382        Ok(AnvilTxResult {
383            inner: EthTxResult {
384                result,
385                blob_gas_used: tx.tx().blob_gas_used().unwrap_or_default(),
386                tx_type: tx.tx().tx_type(),
387            },
388            sender,
389        })
390    }
391
392    fn commit_transaction(&mut self, output: Self::Result) -> GasOutput {
393        let AnvilTxResult {
394            inner: EthTxResult { result: ResultAndState { result, state }, blob_gas_used, tx_type },
395            #[cfg_attr(not(feature = "optimism"), allow(unused_variables))]
396            sender,
397        } = output;
398
399        let gas_used = result.tx_gas_used();
400        self.gas_used += gas_used;
401
402        if self.spec_id >= SpecId::CANCUN {
403            self.blob_gas_used = self.blob_gas_used.saturating_add(blob_gas_used);
404        }
405
406        #[cfg(feature = "optimism")]
407        let receipt = if tx_type == FoundryTxType::Deposit {
408            let deposit_nonce = state.get(&sender).map(|acc| acc.info.nonce);
409            let receipt = alloy_consensus::Receipt {
410                status: Eip658Value::Eip658(result.is_success()),
411                cumulative_gas_used: self.gas_used,
412                logs: result.into_logs(),
413            }
414            .with_bloom();
415            FoundryReceiptEnvelope::Deposit(op_alloy_consensus::OpDepositReceiptWithBloom {
416                receipt: op_alloy_consensus::OpDepositReceipt {
417                    inner: receipt.receipt,
418                    deposit_nonce,
419                    deposit_receipt_version: deposit_nonce.map(|_| 1),
420                },
421                logs_bloom: receipt.logs_bloom,
422            })
423        } else {
424            self.receipt_builder.build_receipt(ReceiptBuilderCtx {
425                tx_type,
426                evm: &self.evm,
427                result,
428                state: &state,
429                cumulative_gas_used: self.gas_used,
430            })
431        };
432        #[cfg(not(feature = "optimism"))]
433        let receipt = self.receipt_builder.build_receipt(ReceiptBuilderCtx {
434            tx_type,
435            evm: &self.evm,
436            result,
437            state: &state,
438            cumulative_gas_used: self.gas_used,
439        });
440
441        if let Some(state_changes) = &mut self.state_changes {
442            state_changes.push(state.clone());
443        }
444        self.receipts.push(receipt);
445        self.evm.db_mut().commit(state);
446
447        GasOutput::new(gas_used)
448    }
449
450    fn finish(
451        mut self,
452    ) -> Result<(Self::Evm, BlockExecutionResult<FoundryReceiptEnvelope>), BlockExecutionError>
453    {
454        let requests = match self.ethereum_transitions {
455            Some(transitions) if transitions.execution_kind == BlockExecutionKind::Complete => {
456                apply_ethereum_post_execution_changes(&mut self.evm, transitions, &self.receipts)?
457            }
458            _ => Requests::default(),
459        };
460        Ok((
461            self.evm,
462            BlockExecutionResult {
463                receipts: self.receipts,
464                requests,
465                gas_used: self.gas_used,
466                blob_gas_used: self.blob_gas_used,
467            },
468        ))
469    }
470
471    fn evm_mut(&mut self) -> &mut Self::Evm {
472        &mut self.evm
473    }
474
475    fn evm(&self) -> &Self::Evm {
476        &self.evm
477    }
478
479    fn receipts(&self) -> &[FoundryReceiptEnvelope] {
480        &self.receipts
481    }
482}
483
484/// Result of executing pool transactions against a block executor.
485pub struct ExecutedPoolTransactions<T> {
486    /// Successfully included transactions.
487    pub included: Vec<Arc<PoolTransaction<T>>>,
488    /// Transactions that failed validation.
489    pub invalid: Vec<Arc<PoolTransaction<T>>>,
490    /// Transactions skipped because they're not yet valid (e.g., valid_after in the future).
491    /// These remain in the pool and should be retried later.
492    pub not_yet_valid: Vec<Arc<PoolTransaction<T>>>,
493    /// Per-transaction execution info.
494    pub tx_info: Vec<TransactionInfo>,
495    /// The raw pending transactions that were included (in order).
496    pub txs: Vec<MaybeImpersonatedTransaction<T>>,
497}
498
499/// Gas-related configuration for pool transaction execution.
500///
501/// Bundles parameters that cannot be derived from the generic `Evm` trait
502/// (which doesn't expose `cfg()`), so callers construct this from `EvmEnv`
503/// before calling [`execute_pool_transactions`].
504pub struct PoolTxGasConfig {
505    pub disable_block_gas_limit: bool,
506    pub tx_gas_limit_cap: Option<u64>,
507    pub tx_gas_limit_cap_resolved: u64,
508    pub max_blob_gas_per_block: u64,
509    pub is_cancun: bool,
510}
511
512/// Executes pool transactions against a block executor, handling validation,
513/// execution, commit, inspector drain, and result collection.
514///
515/// This is the shared core of `do_mine_block` and `with_pending_block`.
516#[allow(clippy::type_complexity)]
517pub fn execute_pool_transactions<B>(
518    executor: &mut B,
519    pool_transactions: &[Arc<PoolTransaction<B::Transaction>>],
520    gas_config: &PoolTxGasConfig,
521    inspector_config: &InspectorTxConfig,
522    cheats: &CheatsManager,
523    validator: &dyn Fn(
524        &PoolTransaction<B::Transaction>,
525        &AccountInfo,
526    ) -> Result<(), InvalidTransactionError>,
527) -> ExecutedPoolTransactions<B::Transaction>
528where
529    B: BlockExecutor<
530            Transaction = FoundryTxEnvelope,
531            Evm: Evm<DB: Database + Debug, Inspector = AnvilInspector>,
532        >,
533    B::Receipt: TxReceipt,
534    <B::Result as TxResult>::HaltReason: Clone + IntoInstructionResult,
535    <B::Evm as Evm>::Tx: FromTxWithEncoded<B::Transaction> + FoundryTransaction,
536{
537    let gas_limit = executor.evm().block().gas_limit();
538
539    let mut included = Vec::new();
540    let mut invalid = Vec::new();
541    let mut not_yet_valid = Vec::new();
542    let mut tx_info: Vec<TransactionInfo> = Vec::new();
543    let mut transactions = Vec::new();
544    let mut blob_gas_used = 0u64;
545
546    for pool_tx in pool_transactions {
547        let pending = &pool_tx.pending_transaction;
548        let sender = *pending.sender();
549        let block_timestamp = executor.evm().block().timestamp();
550
551        if let FoundryTxEnvelope::Tempo(aa_tx) = pending.transaction.as_ref()
552            && let Some(valid_after) = aa_tx.tx().valid_after
553            && U256::from(valid_after.get()) > block_timestamp
554        {
555            trace!(target: "backend", "[{:?}] transaction not valid yet, will retry later", pool_tx.hash());
556            not_yet_valid.push(pool_tx.clone());
557            continue;
558        }
559
560        let account = match executor.evm_mut().db_mut().basic(sender).map(|a| a.unwrap_or_default())
561        {
562            Ok(acc) => acc,
563            Err(err) => {
564                trace!(target: "backend", ?err, "db error for tx {:?}, skipping", pool_tx.hash());
565                continue;
566            }
567        };
568
569        let tx_env =
570            build_tx_env_for_pending::<B::Transaction, <B::Evm as Evm>::Tx>(pending, cheats);
571
572        // Gas limit checks
573        let cumulative_gas =
574            executor.receipts().last().map(|r| r.cumulative_gas_used()).unwrap_or(0);
575        let max_block_gas = cumulative_gas.saturating_add(pending.transaction.gas_limit());
576        if !gas_config.disable_block_gas_limit && max_block_gas > gas_limit {
577            trace!(target: "backend", tx_gas_limit = %pending.transaction.gas_limit(), ?pool_tx, "block gas limit exhausting, skipping transaction");
578            continue;
579        }
580
581        // Osaka EIP-7825 tx gas limit cap check
582        if gas_config.tx_gas_limit_cap.is_none()
583            && pending.transaction.gas_limit() > gas_config.tx_gas_limit_cap_resolved
584        {
585            trace!(target: "backend", tx_gas_limit = %pending.transaction.gas_limit(), ?pool_tx, "transaction gas limit exhausting, skipping transaction");
586            continue;
587        }
588
589        // Blob gas check
590        let tx_blob_gas = pending.transaction.blob_gas_used().unwrap_or(0);
591        if blob_gas_used.saturating_add(tx_blob_gas) > gas_config.max_blob_gas_per_block {
592            trace!(target: "backend", blob_gas = %tx_blob_gas, ?pool_tx, "block blob gas limit exhausting, skipping transaction");
593            continue;
594        }
595
596        // Validate
597        if let Err(err) = validator(pool_tx, &account) {
598            warn!(target: "backend", "Skipping invalid tx execution [{:?}] {}", pool_tx.hash(), err);
599            invalid.push(pool_tx.clone());
600            continue;
601        }
602
603        let nonce = account.nonce;
604
605        let recovered = Recovered::new_unchecked(pending.transaction.as_ref().clone(), sender);
606        trace!(target: "backend", "[{:?}] executing", pool_tx.hash());
607        match executor.execute_transaction_without_commit((tx_env, recovered)) {
608            Ok(result) => {
609                let exec_result = result.result().result.clone();
610                let gas_used = result.result().result.tx_gas_used();
611
612                executor.commit_transaction(result);
613
614                let traces =
615                    executor.evm_mut().inspector_mut().finish_transaction(inspector_config);
616
617                if gas_config.is_cancun {
618                    blob_gas_used = blob_gas_used.saturating_add(tx_blob_gas);
619                }
620
621                let (exit_reason, out, _logs) = match exec_result {
622                    ExecutionResult::Success { reason, logs, output, .. } => {
623                        (reason.into(), Some(output), logs)
624                    }
625                    ExecutionResult::Revert { output, .. } => {
626                        (InstructionResult::Revert, Some(Output::Call(output)), Vec::new())
627                    }
628                    ExecutionResult::Halt { reason, .. } => {
629                        (reason.into_instruction_result(), None, Vec::new())
630                    }
631                };
632
633                if exit_reason == InstructionResult::OutOfGas {
634                    warn!(target: "backend", "[{:?}] executed with out of gas", pool_tx.hash());
635                }
636
637                trace!(target: "backend", ?exit_reason, ?gas_used, "[{:?}] executed with out={:?}", pool_tx.hash(), out);
638                trace!(target: "backend::executor", "transacted [{:?}], result: {:?} gas {}", pool_tx.hash(), exit_reason, gas_used);
639
640                let contract_address = pending.transaction.to().is_none().then(|| {
641                    let addr = sender.create(nonce);
642                    trace!(target: "backend", "Contract creation tx: computed address {:?}", addr);
643                    addr
644                });
645
646                // TODO: replace `TransactionInfo` with alloy receipt/transaction types
647                let transaction_index = tx_info.len() as u64;
648                let info = TransactionInfo {
649                    transaction_hash: pool_tx.hash(),
650                    transaction_index,
651                    from: sender,
652                    to: pending.transaction.to(),
653                    contract_address,
654                    traces,
655                    exit: exit_reason,
656                    out: out.map(Output::into_data),
657                    nonce,
658                    gas_used,
659                };
660
661                included.push(pool_tx.clone());
662                tx_info.push(info);
663                transactions.push(pending.transaction.clone());
664            }
665            Err(err) => {
666                if err.as_validation().is_some() {
667                    warn!(target: "backend", "Skipping invalid tx [{:?}]: {}", pool_tx.hash(), err);
668                    invalid.push(pool_tx.clone());
669                } else {
670                    trace!(target: "backend", ?err, "tx execution error, skipping {:?}", pool_tx.hash());
671                }
672            }
673        }
674    }
675
676    ExecutedPoolTransactions { included, invalid, not_yet_valid, tx_info, txs: transactions }
677}
678
679/// Builds the EVM transaction env from a pending pool transaction.
680pub fn build_tx_env_for_pending<Tx, T>(tx: &PendingTransaction<Tx>, cheats: &CheatsManager) -> T
681where
682    Tx: Transaction + Encodable2718,
683    T: FromTxWithEncoded<Tx> + FoundryTransaction,
684{
685    let encoded = tx.transaction.encoded_2718().into();
686    let mut tx_env: T =
687        FromTxWithEncoded::from_encoded_tx(tx.transaction.as_ref(), *tx.sender(), encoded);
688
689    if let Some(signed_auths) = tx.transaction.authorization_list()
690        && cheats.has_recover_overrides()
691    {
692        let auth_list = tx_env.authorization_list_mut();
693        let cheated_auths = signed_auths
694            .iter()
695            .zip(take(auth_list))
696            .map(|(signed_auth, either_auth)| {
697                either_auth.right_and_then(|recovered_auth| {
698                    if recovered_auth.authority().is_none()
699                        && let Ok(signature) = signed_auth.signature()
700                        && let Some(override_addr) =
701                            cheats.get_recover_override(&signature.as_bytes().into())
702                    {
703                        Either::Right(RecoveredAuthorization::new_unchecked(
704                            recovered_auth.into_parts().0,
705                            RecoveredAuthority::Valid(override_addr),
706                        ))
707                    } else {
708                        Either::Right(recovered_auth)
709                    }
710                })
711            })
712            .collect();
713        *tx_env.authorization_list_mut() = cheated_auths;
714    }
715
716    tx_env
717}
718
719#[cfg(test)]
720mod tests {
721    use super::*;
722    use alloy_eips::{
723        eip6110::MAINNET_DEPOSIT_CONTRACT_ADDRESS, eip7002::WITHDRAWAL_REQUEST_TYPE,
724        eip7251::CONSOLIDATION_REQUEST_TYPE,
725    };
726    use alloy_sol_types::{SolEvent, sol};
727
728    sol! {
729        event DepositEvent(
730            bytes pubkey,
731            bytes withdrawal_credentials,
732            bytes amount,
733            bytes signature,
734            bytes index
735        );
736    }
737
738    #[test]
739    fn prague_requests_use_consensus_order() {
740        let event = DepositEvent {
741            pubkey: Bytes::from(vec![0x11; 48]),
742            withdrawal_credentials: Bytes::from(vec![0x22; 32]),
743            amount: Bytes::from(vec![0x33; 8]),
744            signature: Bytes::from(vec![0x44; 96]),
745            index: Bytes::from(vec![0x55; 8]),
746        };
747        let log = DepositEvent::encode_log(&Log {
748            address: MAINNET_DEPOSIT_CONTRACT_ADDRESS,
749            data: event,
750        });
751        let receipt =
752            Receipt { status: Eip658Value::Eip658(true), cumulative_gas_used: 0, logs: vec![log] }
753                .with_bloom();
754        let receipts = [FoundryReceiptEnvelope::Legacy(receipt)];
755        let mut requests = Requests::default();
756
757        append_deposit_requests(
758            ActiveEthereumSpec {
759                hardfork: EthereumHardfork::Prague,
760                deposit_contract_address: MAINNET_DEPOSIT_CONTRACT_ADDRESS,
761            },
762            &receipts,
763            &mut requests,
764        )
765        .unwrap();
766        requests.push_request_with_type(WITHDRAWAL_REQUEST_TYPE, [0xaa]);
767        requests.push_request_with_type(CONSOLIDATION_REQUEST_TYPE, [0xbb]);
768
769        assert_eq!(
770            requests.iter().map(|request| request[0]).collect::<Vec<_>>(),
771            [DEPOSIT_REQUEST_TYPE, WITHDRAWAL_REQUEST_TYPE, CONSOLIDATION_REQUEST_TYPE]
772        );
773    }
774
775    #[test]
776    fn deposit_requests_use_configured_contract_address() {
777        let configured_address = Address::repeat_byte(0x42);
778        let event = DepositEvent {
779            pubkey: Bytes::from(vec![0x11; 48]),
780            withdrawal_credentials: Bytes::from(vec![0x22; 32]),
781            amount: Bytes::from(vec![0x33; 8]),
782            signature: Bytes::from(vec![0x44; 96]),
783            index: Bytes::from(vec![0x55; 8]),
784        };
785        let configured_log =
786            DepositEvent::encode_log(&Log { address: configured_address, data: event.clone() });
787        let mainnet_log = DepositEvent::encode_log(&Log {
788            address: MAINNET_DEPOSIT_CONTRACT_ADDRESS,
789            data: event,
790        });
791        let receipt = Receipt {
792            status: Eip658Value::Eip658(true),
793            cumulative_gas_used: 0,
794            logs: vec![mainnet_log, configured_log],
795        }
796        .with_bloom();
797        let receipts = [FoundryReceiptEnvelope::Legacy(receipt)];
798        let mut requests = Requests::default();
799
800        append_deposit_requests(
801            ActiveEthereumSpec {
802                hardfork: EthereumHardfork::Prague,
803                deposit_contract_address: configured_address,
804            },
805            &receipts,
806            &mut requests,
807        )
808        .unwrap();
809
810        let request = requests.first().expect("configured deposit should be collected");
811        assert_eq!(request[0], DEPOSIT_REQUEST_TYPE);
812        assert_eq!(request.len(), 1 + 48 + 32 + 8 + 96 + 8);
813    }
814}