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#[cfg(feature = "optimism")]
49pub(crate) mod optimism;
50
51#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
53pub(crate) enum BlockExecutionKind {
54 #[default]
56 Complete,
57 TransactionPrefix,
59}
60
61#[derive(Clone, Copy, Debug)]
63pub(crate) struct EthereumBlockTransitions {
64 pub(crate) hardfork: EthereumHardfork,
65 pub(crate) deposit_contract_address: Address,
66 pub(crate) parent_beacon_block_root: Option<B256>,
67 pub(crate) execution_kind: BlockExecutionKind,
68}
69
70#[derive(Clone, Copy, Debug)]
72struct ActiveEthereumSpec {
73 hardfork: EthereumHardfork,
74 deposit_contract_address: Address,
75}
76
77impl EthereumHardforks for ActiveEthereumSpec {
78 fn ethereum_fork_activation(&self, fork: EthereumHardfork) -> ForkCondition {
79 if fork <= self.hardfork { ForkCondition::ZERO_TIMESTAMP } else { ForkCondition::Never }
80 }
81}
82
83impl EthExecutorSpec for ActiveEthereumSpec {
84 fn deposit_contract_address(&self) -> Option<Address> {
85 Some(self.deposit_contract_address)
86 }
87}
88
89pub(crate) fn apply_ethereum_pre_execution_changes<E>(
91 evm: &mut E,
92 parent_hash: B256,
93 transitions: EthereumBlockTransitions,
94) -> Result<(), BlockExecutionError>
95where
96 E: Evm<DB: DatabaseCommit>,
97{
98 let mut caller = SystemCaller::new(ActiveEthereumSpec {
99 hardfork: transitions.hardfork,
100 deposit_contract_address: transitions.deposit_contract_address,
101 });
102 caller.apply_blockhashes_contract_call(parent_hash, evm)?;
103 caller.apply_beacon_root_contract_call(transitions.parent_beacon_block_root, evm)
104}
105
106pub(crate) fn apply_ethereum_post_execution_changes<E>(
108 evm: &mut E,
109 transitions: EthereumBlockTransitions,
110 receipts: &[FoundryReceiptEnvelope],
111) -> Result<Requests, BlockExecutionError>
112where
113 E: Evm<DB: DatabaseCommit>,
114{
115 if transitions.hardfork < EthereumHardfork::Prague {
116 return Ok(Requests::default());
117 }
118
119 let spec = ActiveEthereumSpec {
120 hardfork: transitions.hardfork,
121 deposit_contract_address: transitions.deposit_contract_address,
122 };
123 let mut requests = Requests::default();
124 append_deposit_requests(spec, receipts, &mut requests)?;
125 SystemCaller::new(spec).append_post_execution_changes(evm, &mut requests)?;
126 Ok(requests)
127}
128
129fn append_deposit_requests(
130 spec: ActiveEthereumSpec,
131 receipts: &[FoundryReceiptEnvelope],
132 requests: &mut Requests,
133) -> Result<(), BlockExecutionError> {
134 let deposits = parse_deposits_from_receipts(spec, receipts)?;
135 if !deposits.is_empty() {
136 requests.push_request_with_type(DEPOSIT_REQUEST_TYPE, deposits);
137 }
138 Ok(())
139}
140
141#[derive(Debug, Default, Clone, Copy)]
143#[non_exhaustive]
144pub struct FoundryReceiptBuilder;
145
146impl FoundryReceiptBuilder {
147 const fn wrap_receipt(
148 tx_type: FoundryTxType,
149 receipt: ReceiptWithBloom<Receipt>,
150 ) -> FoundryReceiptEnvelope {
151 match tx_type {
152 FoundryTxType::Legacy => FoundryReceiptEnvelope::Legacy(receipt),
153 FoundryTxType::Eip2930 => FoundryReceiptEnvelope::Eip2930(receipt),
154 FoundryTxType::Eip1559 => FoundryReceiptEnvelope::Eip1559(receipt),
155 FoundryTxType::Eip4844 => FoundryReceiptEnvelope::Eip4844(receipt),
156 FoundryTxType::Eip7702 => FoundryReceiptEnvelope::Eip7702(receipt),
157 #[cfg(feature = "optimism")]
158 FoundryTxType::Deposit => {
159 panic!("deposit receipts require fork-specific metadata")
160 }
161 #[cfg(feature = "optimism")]
162 FoundryTxType::PostExec => FoundryReceiptEnvelope::PostExec(receipt),
163 FoundryTxType::Tempo => FoundryReceiptEnvelope::Tempo(receipt),
164 }
165 }
166
167 pub(crate) fn build_simulated_receipt(
169 tx_type: FoundryTxType,
170 result: &ExecutionResult,
171 logs: Vec<Log>,
172 cumulative_gas_used: u64,
173 ) -> FoundryReceiptEnvelope {
174 let receipt =
175 Receipt { status: Eip658Value::Eip658(result.is_success()), cumulative_gas_used, logs }
176 .with_bloom();
177 Self::wrap_receipt(tx_type, receipt)
178 }
179}
180
181impl ReceiptBuilder for FoundryReceiptBuilder {
182 type Transaction = FoundryTxEnvelope;
183 type Receipt = FoundryReceiptEnvelope;
184
185 fn build_receipt<E: Evm>(
186 &self,
187 ctx: ReceiptBuilderCtx<'_, FoundryTxType, E>,
188 ) -> FoundryReceiptEnvelope {
189 let receipt = Receipt {
190 status: Eip658Value::Eip658(ctx.result.is_success()),
191 cumulative_gas_used: ctx.cumulative_gas_used,
192 logs: ctx.result.into_logs(),
193 }
194 .with_bloom();
195 Self::wrap_receipt(ctx.tx_type, receipt)
196 }
197}
198
199#[derive(Debug)]
203pub struct AnvilTxResult<H> {
204 pub inner: EthTxResult<H, FoundryTxType>,
205 pub sender: Address,
206}
207
208impl<H: Send + 'static> TxResult for AnvilTxResult<H> {
209 type HaltReason = H;
210
211 fn result(&self) -> &ResultAndState<Self::HaltReason> {
212 self.inner.result()
213 }
214
215 fn into_result(self) -> ResultAndState<Self::HaltReason> {
216 self.inner.into_result()
217 }
218}
219
220pub struct AnvilBlockExecutor<E> {
226 evm: E,
228 parent_hash: B256,
230 spec_id: SpecId,
232 ethereum_transitions: Option<EthereumBlockTransitions>,
234 receipt_builder: FoundryReceiptBuilder,
236 receipts: Vec<FoundryReceiptEnvelope>,
238 gas_used: u64,
240 blob_gas_used: u64,
242 max_blob_gas_per_block: u64,
244 optimism_jovian: bool,
246 state_changes: Option<Vec<EvmState>>,
248}
249
250impl<E: fmt::Debug> fmt::Debug for AnvilBlockExecutor<E> {
251 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252 let mut debug = f.debug_struct("AnvilBlockExecutor");
253 debug
254 .field("evm", &self.evm)
255 .field("parent_hash", &self.parent_hash)
256 .field("spec_id", &self.spec_id)
257 .field("ethereum_transitions", &self.ethereum_transitions)
258 .field("gas_used", &self.gas_used)
259 .field("blob_gas_used", &self.blob_gas_used)
260 .field("max_blob_gas_per_block", &self.max_blob_gas_per_block)
261 .field("optimism_jovian", &self.optimism_jovian);
262 debug.field("receipts", &self.receipts.len()).finish_non_exhaustive()
263 }
264}
265
266impl<E> AnvilBlockExecutor<E> {
267 pub(crate) const fn new(
269 evm: E,
270 parent_hash: B256,
271 spec_id: SpecId,
272 ethereum_transitions: Option<EthereumBlockTransitions>,
273 ) -> Self {
274 Self {
275 evm,
276 parent_hash,
277 spec_id,
278 ethereum_transitions,
279 receipt_builder: FoundryReceiptBuilder,
280 receipts: Vec::new(),
281 gas_used: 0,
282 blob_gas_used: 0,
283 max_blob_gas_per_block: u64::MAX,
284 optimism_jovian: false,
285 state_changes: None,
286 }
287 }
288
289 pub(crate) fn with_state_changes(mut self) -> Self {
291 self.state_changes = Some(Vec::new());
292 self
293 }
294
295 pub(crate) const fn with_max_blob_gas_per_block(mut self, limit: u64) -> Self {
297 self.max_blob_gas_per_block = limit;
298 self
299 }
300
301 pub(crate) fn take_state_changes(&mut self) -> Vec<EvmState> {
303 self.state_changes.take().unwrap_or_default()
304 }
305}
306
307impl<E> AnvilBlockExecutor<E>
308where
309 E: Evm<
310 DB: StateDB,
311 Tx: FromRecoveredTx<FoundryTxEnvelope> + FromTxWithEncoded<FoundryTxEnvelope>,
312 >,
313{
314 pub(crate) fn execute_transaction_without_commit_with<T, F>(
317 &mut self,
318 tx: T,
319 transact: F,
320 ) -> Result<AnvilTxResult<E::HaltReason>, BlockExecutionError>
321 where
322 T: ExecutableTx<Self>,
323 F: FnOnce(
324 &mut E,
325 E::Tx,
326 B256,
327 ) -> Result<ResultAndState<E::HaltReason>, BlockExecutionError>,
328 {
329 let (tx_env, tx) = tx.into_parts();
330
331 let block_available_gas = self.evm.block().gas_limit() - self.gas_used;
332 if tx.tx().gas_limit() > block_available_gas {
333 return Err(BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas {
334 transaction_gas_limit: tx.tx().gas_limit(),
335 block_available_gas,
336 }
337 .into());
338 }
339
340 let sender = *tx.signer();
341 let transaction_hash = tx.tx().trie_hash();
342 #[cfg(feature = "optimism")]
343 let blob_gas_used =
344 optimism::blob_gas_used(self.evm.db_mut(), tx.tx(), self.optimism_jovian)?;
345 #[cfg(not(feature = "optimism"))]
346 let blob_gas_used = tx.tx().blob_gas_used().unwrap_or_default();
347 let blob_gas_limit = block_blob_gas_limit(
348 self.optimism_jovian,
349 self.evm.block().gas_limit(),
350 self.max_blob_gas_per_block,
351 );
352 if self.blob_gas_used.saturating_add(blob_gas_used) > blob_gas_limit {
353 return Err(BlockExecutionError::msg("block blob gas limit exceeded"));
354 }
355 let result = transact(&mut self.evm, tx_env, transaction_hash)?;
356
357 Ok(AnvilTxResult {
358 inner: EthTxResult { result, blob_gas_used, tx_type: tx.tx().tx_type() },
359 sender,
360 })
361 }
362}
363
364impl<E> BlockExecutor for AnvilBlockExecutor<E>
365where
366 E: Evm<
367 DB: StateDB,
368 Tx: FromRecoveredTx<FoundryTxEnvelope> + FromTxWithEncoded<FoundryTxEnvelope>,
369 >,
370{
371 type Transaction = FoundryTxEnvelope;
372 type Receipt = FoundryReceiptEnvelope;
373 type Evm = E;
374 type Result = AnvilTxResult<E::HaltReason>;
375
376 fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> {
377 if let Some(transitions) = self.ethereum_transitions {
378 if let Some(state_changes) = &mut self.state_changes {
381 if transitions.hardfork >= EthereumHardfork::Prague {
382 let result = self
383 .evm
384 .transact_system_call(
385 eip4788::SYSTEM_ADDRESS,
386 eip2935::HISTORY_STORAGE_ADDRESS,
387 Bytes::copy_from_slice(self.parent_hash.as_slice()),
388 )
389 .map_err(BlockExecutionError::other)?;
390 state_changes.push(result.state.clone());
391 self.evm.db_mut().commit(result.state);
392 }
393 if transitions.hardfork >= EthereumHardfork::Cancun {
394 let parent_beacon_block_root = transitions
395 .parent_beacon_block_root
396 .ok_or(BlockValidationError::MissingParentBeaconBlockRoot)?;
397 let result = self
398 .evm
399 .transact_system_call(
400 eip4788::SYSTEM_ADDRESS,
401 eip4788::BEACON_ROOTS_ADDRESS,
402 Bytes::copy_from_slice(parent_beacon_block_root.as_slice()),
403 )
404 .map_err(BlockExecutionError::other)?;
405 state_changes.push(result.state.clone());
406 self.evm.db_mut().commit(result.state);
407 }
408 return Ok(());
409 }
410 apply_ethereum_pre_execution_changes(&mut self.evm, self.parent_hash, transitions)?;
411 }
412 Ok(())
413 }
414
415 fn execute_transaction_without_commit(
416 &mut self,
417 tx: impl ExecutableTx<Self>,
418 ) -> Result<Self::Result, BlockExecutionError> {
419 self.execute_transaction_without_commit_with(tx, |evm, tx_env, transaction_hash| {
420 evm.transact(tx_env).map_err(|err| BlockExecutionError::evm(err, transaction_hash))
421 })
422 }
423
424 fn commit_transaction(&mut self, output: Self::Result) -> GasOutput {
425 let AnvilTxResult {
426 inner: EthTxResult { result: ResultAndState { result, state }, blob_gas_used, tx_type },
427 #[cfg_attr(not(feature = "optimism"), allow(unused_variables))]
428 sender,
429 } = output;
430
431 let gas_used = result.tx_gas_used();
432 self.gas_used += gas_used;
433
434 if self.spec_id >= SpecId::CANCUN {
435 self.blob_gas_used = self.blob_gas_used.saturating_add(blob_gas_used);
436 }
437
438 #[cfg(feature = "optimism")]
439 let receipt = if tx_type == FoundryTxType::Deposit {
440 optimism::build_mined_deposit_receipt(result, &state, sender, self.gas_used)
441 } else {
442 self.receipt_builder.build_receipt(ReceiptBuilderCtx {
443 tx_type,
444 evm: &self.evm,
445 result,
446 state: &state,
447 cumulative_gas_used: self.gas_used,
448 })
449 };
450 #[cfg(not(feature = "optimism"))]
451 let receipt = self.receipt_builder.build_receipt(ReceiptBuilderCtx {
452 tx_type,
453 evm: &self.evm,
454 result,
455 state: &state,
456 cumulative_gas_used: self.gas_used,
457 });
458
459 if let Some(state_changes) = &mut self.state_changes {
460 state_changes.push(state.clone());
461 }
462 self.receipts.push(receipt);
463 self.evm.db_mut().commit(state);
464
465 GasOutput::new(gas_used)
466 }
467
468 fn finish(
469 mut self,
470 ) -> Result<(Self::Evm, BlockExecutionResult<FoundryReceiptEnvelope>), BlockExecutionError>
471 {
472 let requests = match self.ethereum_transitions {
473 Some(transitions) if transitions.execution_kind == BlockExecutionKind::Complete => {
474 apply_ethereum_post_execution_changes(&mut self.evm, transitions, &self.receipts)?
475 }
476 _ => Requests::default(),
477 };
478 Ok((
479 self.evm,
480 BlockExecutionResult {
481 receipts: self.receipts,
482 requests,
483 gas_used: self.gas_used,
484 blob_gas_used: self.blob_gas_used,
485 },
486 ))
487 }
488
489 fn evm_mut(&mut self) -> &mut Self::Evm {
490 &mut self.evm
491 }
492
493 fn evm(&self) -> &Self::Evm {
494 &self.evm
495 }
496
497 fn receipts(&self) -> &[FoundryReceiptEnvelope] {
498 &self.receipts
499 }
500}
501
502pub struct ExecutedPoolTransactions<T> {
504 pub included: Vec<Arc<PoolTransaction<T>>>,
506 pub invalid: Vec<Arc<PoolTransaction<T>>>,
508 pub not_yet_valid: Vec<Arc<PoolTransaction<T>>>,
511 pub tx_info: Vec<TransactionInfo>,
513 pub txs: Vec<MaybeImpersonatedTransaction<T>>,
515}
516
517pub struct PoolTxGasConfig {
523 pub disable_block_gas_limit: bool,
524 pub tx_gas_limit_cap: Option<u64>,
525 pub tx_gas_limit_cap_resolved: u64,
526 pub max_blob_gas_per_block: u64,
527 pub is_cancun: bool,
528}
529
530pub struct PoolTransactionHooks<BeforeTransaction, ExecuteTransaction, OnExecutionError> {
532 pub before_transaction: BeforeTransaction,
534 pub execute_transaction: ExecuteTransaction,
536 pub on_execution_error: OnExecutionError,
538}
539
540pub(crate) fn execute_pool_transaction<B>(
542 executor: &mut B,
543 tx_env: <B::Evm as Evm>::Tx,
544 recovered: Recovered<B::Transaction>,
545 _is_replay: bool,
546) -> Result<B::Result, BlockExecutionError>
547where
548 B: BlockExecutor,
549{
550 executor.execute_transaction_without_commit((tx_env, recovered))
551}
552
553#[allow(clippy::type_complexity)]
558pub fn execute_pool_transactions<B, BeforeTransaction, ExecuteTransaction, OnExecutionError>(
559 executor: &mut B,
560 pool_transactions: &[Arc<PoolTransaction<B::Transaction>>],
561 gas_config: &PoolTxGasConfig,
562 inspector_config: &InspectorTxConfig,
563 cheats: &CheatsManager,
564 validator: &dyn Fn(
565 &PoolTransaction<B::Transaction>,
566 &AccountInfo,
567 ) -> Result<(), InvalidTransactionError>,
568 hooks: &mut PoolTransactionHooks<BeforeTransaction, ExecuteTransaction, OnExecutionError>,
569) -> ExecutedPoolTransactions<B::Transaction>
570where
571 B: BlockExecutor<
572 Transaction = FoundryTxEnvelope,
573 Evm: Evm<DB: Database + Debug, Inspector = AnvilInspector>,
574 >,
575 B::Receipt: TxReceipt,
576 <B::Result as TxResult>::HaltReason: Clone + IntoInstructionResult,
577 <B::Evm as Evm>::Tx: FromTxWithEncoded<B::Transaction> + FoundryTransaction,
578 BeforeTransaction: FnMut(&mut B::Evm, &<B::Evm as Evm>::Tx),
579 ExecuteTransaction: FnMut(
580 &mut B,
581 <B::Evm as Evm>::Tx,
582 Recovered<B::Transaction>,
583 bool,
584 ) -> Result<B::Result, BlockExecutionError>,
585 OnExecutionError: FnMut(&mut B::Evm),
586{
587 let gas_limit = executor.evm().block().gas_limit();
588
589 let mut included = Vec::new();
590 let mut invalid = Vec::new();
591 let mut not_yet_valid = Vec::new();
592 let mut tx_info: Vec<TransactionInfo> = Vec::new();
593 let mut transactions = Vec::new();
594 let mut blob_gas_used = 0u64;
595
596 for pool_tx in pool_transactions {
597 let pending = &pool_tx.pending_transaction;
598 let sender = *pending.sender();
599 let block_timestamp = executor.evm().block().timestamp();
600
601 if let FoundryTxEnvelope::Tempo(aa_tx) = pending.transaction.as_ref()
602 && let Some(valid_after) = aa_tx.tx().valid_after
603 && U256::from(valid_after.get()) > block_timestamp
604 {
605 trace!(target: "backend", "[{:?}] transaction not valid yet, will retry later", pool_tx.hash());
606 not_yet_valid.push(pool_tx.clone());
607 continue;
608 }
609
610 let account = match executor.evm_mut().db_mut().basic(sender).map(|a| a.unwrap_or_default())
611 {
612 Ok(acc) => acc,
613 Err(err) => {
614 trace!(target: "backend", ?err, "db error for tx {:?}, skipping", pool_tx.hash());
615 continue;
616 }
617 };
618
619 let tx_env =
620 build_tx_env_for_pending::<B::Transaction, <B::Evm as Evm>::Tx>(pending, cheats);
621
622 let cumulative_gas =
624 executor.receipts().last().map(|r| r.cumulative_gas_used()).unwrap_or(0);
625 let max_block_gas = cumulative_gas.saturating_add(pending.transaction.gas_limit());
626 if !gas_config.disable_block_gas_limit && max_block_gas > gas_limit {
627 trace!(target: "backend", tx_gas_limit = %pending.transaction.gas_limit(), ?pool_tx, "block gas limit exhausting, skipping transaction");
628 continue;
629 }
630
631 if gas_config.tx_gas_limit_cap.is_none()
633 && pending.transaction.gas_limit() > gas_config.tx_gas_limit_cap_resolved
634 {
635 trace!(target: "backend", tx_gas_limit = %pending.transaction.gas_limit(), ?pool_tx, "transaction gas limit exhausting, skipping transaction");
636 continue;
637 }
638
639 let declared_blob_gas = pending.transaction.blob_gas_used().unwrap_or(0);
642 if blob_gas_used.saturating_add(declared_blob_gas) > gas_config.max_blob_gas_per_block {
643 trace!(target: "backend", blob_gas = %declared_blob_gas, ?pool_tx, "block blob gas limit exhausting, skipping transaction");
644 continue;
645 }
646
647 if let Err(err) = validator(pool_tx, &account) {
649 warn!(target: "backend", "Skipping invalid tx execution [{:?}] {}", pool_tx.hash(), err);
650 invalid.push(pool_tx.clone());
651 continue;
652 }
653
654 let nonce = account.nonce;
655
656 (hooks.before_transaction)(executor.evm_mut(), &tx_env);
657 let recovered = Recovered::new_unchecked(pending.transaction.as_ref().clone(), sender);
658 trace!(target: "backend", "[{:?}] executing", pool_tx.hash());
659 match (hooks.execute_transaction)(executor, tx_env, recovered, pool_tx.is_replay) {
660 Ok(result) => {
661 let exec_result = result.result().result.clone();
662 let gas_used = result.result().result.tx_gas_used();
663
664 executor.commit_transaction(result);
665
666 let traces =
667 executor.evm_mut().inspector_mut().finish_transaction(inspector_config);
668
669 if gas_config.is_cancun {
670 blob_gas_used = blob_gas_used.saturating_add(declared_blob_gas);
671 }
672
673 let (exit_reason, out, _logs) = match exec_result {
674 ExecutionResult::Success { reason, logs, output, .. } => {
675 (reason.into(), Some(output), logs)
676 }
677 ExecutionResult::Revert { output, .. } => {
678 (InstructionResult::Revert, Some(Output::Call(output)), Vec::new())
679 }
680 ExecutionResult::Halt { reason, .. } => {
681 (reason.into_instruction_result(), None, Vec::new())
682 }
683 };
684
685 if exit_reason == InstructionResult::OutOfGas {
686 warn!(target: "backend", "[{:?}] executed with out of gas", pool_tx.hash());
687 }
688
689 trace!(target: "backend", ?exit_reason, ?gas_used, "[{:?}] executed with out={:?}", pool_tx.hash(), out);
690 trace!(target: "backend::executor", "transacted [{:?}], result: {:?} gas {}", pool_tx.hash(), exit_reason, gas_used);
691
692 let contract_address = pending.transaction.to().is_none().then(|| {
693 let addr = sender.create(nonce);
694 trace!(target: "backend", "Contract creation tx: computed address {:?}", addr);
695 addr
696 });
697
698 let transaction_index = tx_info.len() as u64;
700 let info = TransactionInfo {
701 transaction_hash: pool_tx.hash(),
702 transaction_index,
703 from: sender,
704 to: pending.transaction.to(),
705 contract_address,
706 traces,
707 exit: exit_reason,
708 out: out.map(Output::into_data),
709 nonce,
710 gas_used,
711 };
712
713 included.push(pool_tx.clone());
714 tx_info.push(info);
715 transactions.push(pending.transaction.clone());
716 }
717 Err(err) => {
718 (hooks.on_execution_error)(executor.evm_mut());
719 executor.evm_mut().inspector_mut().discard_transaction(inspector_config);
720 if err.as_validation().is_some() {
721 warn!(target: "backend", "Skipping invalid tx [{:?}]: {}", pool_tx.hash(), err);
722 invalid.push(pool_tx.clone());
723 } else {
724 trace!(target: "backend", ?err, "tx execution error, skipping {:?}", pool_tx.hash());
725 }
726 }
727 }
728 }
729
730 ExecutedPoolTransactions { included, invalid, not_yet_valid, tx_info, txs: transactions }
731}
732
733pub fn build_tx_env_for_pending<Tx, T>(tx: &PendingTransaction<Tx>, cheats: &CheatsManager) -> T
735where
736 Tx: Transaction + Encodable2718,
737 T: FromTxWithEncoded<Tx> + FoundryTransaction,
738{
739 let encoded = tx.transaction.encoded_2718().into();
740 let mut tx_env: T =
741 FromTxWithEncoded::from_encoded_tx(tx.transaction.as_ref(), *tx.sender(), encoded);
742
743 if let Some(signed_auths) = tx.transaction.authorization_list()
744 && cheats.has_recover_overrides()
745 {
746 let auth_list = tx_env.authorization_list_mut();
747 let cheated_auths = signed_auths
748 .iter()
749 .zip(take(auth_list))
750 .map(|(signed_auth, either_auth)| {
751 either_auth.right_and_then(|recovered_auth| {
752 if recovered_auth.authority().is_none()
753 && let Ok(signature) = signed_auth.signature()
754 && let Some(override_addr) =
755 cheats.get_recover_override(&signature.as_bytes().into())
756 {
757 Either::Right(RecoveredAuthorization::new_unchecked(
758 recovered_auth.into_parts().0,
759 RecoveredAuthority::Valid(override_addr),
760 ))
761 } else {
762 Either::Right(recovered_auth)
763 }
764 })
765 })
766 .collect();
767 *tx_env.authorization_list_mut() = cheated_auths;
768 }
769
770 tx_env
771}
772
773pub(crate) const fn block_blob_gas_limit(
778 optimism_jovian: bool,
779 block_gas_limit: u64,
780 max_blob_gas_per_block: u64,
781) -> u64 {
782 if optimism_jovian { block_gas_limit } else { max_blob_gas_per_block }
783}
784
785#[cfg(test)]
786mod tests {
787 use super::*;
788 use alloy_eips::{
789 eip6110::MAINNET_DEPOSIT_CONTRACT_ADDRESS, eip7002::WITHDRAWAL_REQUEST_TYPE,
790 eip7251::CONSOLIDATION_REQUEST_TYPE,
791 };
792 use alloy_sol_types::{SolEvent, sol};
793
794 sol! {
795 event DepositEvent(
796 bytes pubkey,
797 bytes withdrawal_credentials,
798 bytes amount,
799 bytes signature,
800 bytes index
801 );
802 }
803
804 #[test]
805 fn prague_requests_use_consensus_order() {
806 let event = DepositEvent {
807 pubkey: Bytes::from(vec![0x11; 48]),
808 withdrawal_credentials: Bytes::from(vec![0x22; 32]),
809 amount: Bytes::from(vec![0x33; 8]),
810 signature: Bytes::from(vec![0x44; 96]),
811 index: Bytes::from(vec![0x55; 8]),
812 };
813 let log = DepositEvent::encode_log(&Log {
814 address: MAINNET_DEPOSIT_CONTRACT_ADDRESS,
815 data: event,
816 });
817 let receipt =
818 Receipt { status: Eip658Value::Eip658(true), cumulative_gas_used: 0, logs: vec![log] }
819 .with_bloom();
820 let receipts = [FoundryReceiptEnvelope::Legacy(receipt)];
821 let mut requests = Requests::default();
822
823 append_deposit_requests(
824 ActiveEthereumSpec {
825 hardfork: EthereumHardfork::Prague,
826 deposit_contract_address: MAINNET_DEPOSIT_CONTRACT_ADDRESS,
827 },
828 &receipts,
829 &mut requests,
830 )
831 .unwrap();
832 requests.push_request_with_type(WITHDRAWAL_REQUEST_TYPE, [0xaa]);
833 requests.push_request_with_type(CONSOLIDATION_REQUEST_TYPE, [0xbb]);
834
835 assert_eq!(
836 requests.iter().map(|request| request[0]).collect::<Vec<_>>(),
837 [DEPOSIT_REQUEST_TYPE, WITHDRAWAL_REQUEST_TYPE, CONSOLIDATION_REQUEST_TYPE]
838 );
839 }
840
841 #[test]
842 fn deposit_requests_use_configured_contract_address() {
843 let configured_address = Address::repeat_byte(0x42);
844 let event = DepositEvent {
845 pubkey: Bytes::from(vec![0x11; 48]),
846 withdrawal_credentials: Bytes::from(vec![0x22; 32]),
847 amount: Bytes::from(vec![0x33; 8]),
848 signature: Bytes::from(vec![0x44; 96]),
849 index: Bytes::from(vec![0x55; 8]),
850 };
851 let configured_log =
852 DepositEvent::encode_log(&Log { address: configured_address, data: event.clone() });
853 let mainnet_log = DepositEvent::encode_log(&Log {
854 address: MAINNET_DEPOSIT_CONTRACT_ADDRESS,
855 data: event,
856 });
857 let receipt = Receipt {
858 status: Eip658Value::Eip658(true),
859 cumulative_gas_used: 0,
860 logs: vec![mainnet_log, configured_log],
861 }
862 .with_bloom();
863 let receipts = [FoundryReceiptEnvelope::Legacy(receipt)];
864 let mut requests = Requests::default();
865
866 append_deposit_requests(
867 ActiveEthereumSpec {
868 hardfork: EthereumHardfork::Prague,
869 deposit_contract_address: configured_address,
870 },
871 &receipts,
872 &mut requests,
873 )
874 .unwrap();
875
876 let request = requests.first().expect("configured deposit should be collected");
877 assert_eq!(request[0], DEPOSIT_REQUEST_TYPE);
878 assert_eq!(request.len(), 1 + 48 + 32 + 8 + 96 + 8);
879 }
880}