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#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
50pub(crate) enum BlockExecutionKind {
51 #[default]
53 Complete,
54 TransactionPrefix,
56}
57
58#[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#[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
86pub(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
103pub(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#[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 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#[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
236pub struct AnvilBlockExecutor<E> {
242 evm: E,
244 parent_hash: B256,
246 spec_id: SpecId,
248 ethereum_transitions: Option<EthereumBlockTransitions>,
250 receipt_builder: FoundryReceiptBuilder,
252 receipts: Vec<FoundryReceiptEnvelope>,
254 gas_used: u64,
256 blob_gas_used: u64,
258 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 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 pub(crate) fn with_state_changes(mut self) -> Self {
299 self.state_changes = Some(Vec::new());
300 self
301 }
302
303 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 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
484pub struct ExecutedPoolTransactions<T> {
486 pub included: Vec<Arc<PoolTransaction<T>>>,
488 pub invalid: Vec<Arc<PoolTransaction<T>>>,
490 pub not_yet_valid: Vec<Arc<PoolTransaction<T>>>,
493 pub tx_info: Vec<TransactionInfo>,
495 pub txs: Vec<MaybeImpersonatedTransaction<T>>,
497}
498
499pub 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#[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 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 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 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 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 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
679pub 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}