1use crate::{
2 PrecompileFactory,
3 eth::{
4 backend::{
5 cheats::{CheatEcrecover, CheatsManager},
6 db::Db,
7 env::Env,
8 mem::op_haltreason_to_instruction_result,
9 validate::TransactionValidator,
10 },
11 error::InvalidTransactionError,
12 pool::transactions::PoolTransaction,
13 },
14 mem::inspector::AnvilInspector,
15};
16use alloy_consensus::{
17 Header, Receipt, ReceiptWithBloom, Transaction, constants::EMPTY_WITHDRAWALS,
18 proofs::calculate_receipt_root, transaction::Either,
19};
20use alloy_eips::{
21 eip7685::EMPTY_REQUESTS_HASH,
22 eip7702::{RecoveredAuthority, RecoveredAuthorization},
23 eip7840::BlobParams,
24};
25use alloy_evm::{
26 EthEvm, Evm, FromRecoveredTx,
27 eth::EthEvmContext,
28 precompiles::{DynPrecompile, Precompile, PrecompilesMap},
29};
30use alloy_op_evm::OpEvm;
31use alloy_primitives::{B256, Bloom, BloomInput, Log};
32use anvil_core::eth::{
33 block::{BlockInfo, create_block},
34 transaction::{PendingTransaction, TransactionInfo},
35};
36use foundry_evm::{
37 backend::DatabaseError,
38 core::{either_evm::EitherEvm, precompiles::EC_RECOVER},
39 traces::{CallTraceDecoder, CallTraceNode},
40};
41use foundry_evm_networks::NetworkConfigs;
42use foundry_primitives::{FoundryReceiptEnvelope, FoundryTxEnvelope};
43use op_revm::{L1BlockInfo, OpContext, OpTransaction, precompiles::OpPrecompiles};
44use revm::{
45 Database, DatabaseRef, Inspector, Journal,
46 context::{
47 Block as RevmBlock, BlockEnv, Cfg, CfgEnv, Evm as RevmEvm, JournalTr, LocalContext, TxEnv,
48 },
49 context_interface::result::{EVMError, ExecutionResult, Output},
50 database::WrapDatabaseRef,
51 handler::{EthPrecompiles, instructions::EthInstructions},
52 interpreter::InstructionResult,
53 precompile::{PrecompileSpecId, Precompiles},
54 primitives::hardfork::SpecId,
55};
56use std::{fmt::Debug, sync::Arc};
57
58#[derive(Debug)]
60pub struct ExecutedTransaction {
61 transaction: Arc<PoolTransaction>,
62 exit_reason: InstructionResult,
63 out: Option<Output>,
64 gas_used: u64,
65 logs: Vec<Log>,
66 traces: Vec<CallTraceNode>,
67 nonce: u64,
68}
69
70impl ExecutedTransaction {
73 fn create_receipt(&self, cumulative_gas_used: &mut u64) -> FoundryReceiptEnvelope {
75 let logs = self.logs.clone();
76 *cumulative_gas_used = cumulative_gas_used.saturating_add(self.gas_used);
77
78 let status_code = u8::from(self.exit_reason as u8 <= InstructionResult::SelfDestruct as u8);
80 let receipt_with_bloom: ReceiptWithBloom = Receipt {
81 status: (status_code == 1).into(),
82 cumulative_gas_used: *cumulative_gas_used,
83 logs,
84 }
85 .into();
86
87 match self.transaction.pending_transaction.transaction.as_ref() {
88 FoundryTxEnvelope::Legacy(_) => FoundryReceiptEnvelope::Legacy(receipt_with_bloom),
89 FoundryTxEnvelope::Eip2930(_) => FoundryReceiptEnvelope::Eip2930(receipt_with_bloom),
90 FoundryTxEnvelope::Eip1559(_) => FoundryReceiptEnvelope::Eip1559(receipt_with_bloom),
91 FoundryTxEnvelope::Eip4844(_) => FoundryReceiptEnvelope::Eip4844(receipt_with_bloom),
92 FoundryTxEnvelope::Eip7702(_) => FoundryReceiptEnvelope::Eip7702(receipt_with_bloom),
93 FoundryTxEnvelope::Deposit(_tx) => {
94 FoundryReceiptEnvelope::Deposit(op_alloy_consensus::OpDepositReceiptWithBloom {
95 receipt: op_alloy_consensus::OpDepositReceipt {
96 inner: receipt_with_bloom.receipt,
97 deposit_nonce: Some(0),
98 deposit_receipt_version: Some(1),
99 },
100 logs_bloom: receipt_with_bloom.logs_bloom,
101 })
102 }
103 }
104 }
105}
106
107#[derive(Clone, Debug)]
109pub struct ExecutedTransactions {
110 pub block: BlockInfo,
112 pub included: Vec<Arc<PoolTransaction>>,
114 pub invalid: Vec<Arc<PoolTransaction>>,
117}
118
119pub struct TransactionExecutor<'a, Db: ?Sized, V: TransactionValidator> {
121 pub db: &'a mut Db,
123 pub validator: &'a V,
125 pub pending: std::vec::IntoIter<Arc<PoolTransaction>>,
127 pub block_env: BlockEnv,
128 pub cfg_env: CfgEnv,
130 pub parent_hash: B256,
131 pub gas_used: u64,
133 pub blob_gas_used: u64,
135 pub enable_steps_tracing: bool,
136 pub networks: NetworkConfigs,
137 pub print_logs: bool,
138 pub print_traces: bool,
139 pub call_trace_decoder: Arc<CallTraceDecoder>,
141 pub precompile_factory: Option<Arc<dyn PrecompileFactory>>,
143 pub blob_params: BlobParams,
144 pub cheats: CheatsManager,
145}
146
147impl<DB: Db + ?Sized, V: TransactionValidator> TransactionExecutor<'_, DB, V> {
148 pub fn execute(mut self) -> ExecutedTransactions {
150 let mut transactions = Vec::new();
151 let mut transaction_infos = Vec::new();
152 let mut receipts = Vec::new();
153 let mut bloom = Bloom::default();
154 let mut cumulative_gas_used = 0u64;
155 let mut invalid = Vec::new();
156 let mut included = Vec::new();
157 let gas_limit = self.block_env.gas_limit;
158 let parent_hash = self.parent_hash;
159 let block_number = self.block_env.number;
160 let difficulty = self.block_env.difficulty;
161 let mix_hash = self.block_env.prevrandao;
162 let beneficiary = self.block_env.beneficiary;
163 let timestamp = self.block_env.timestamp;
164 let base_fee = if self.cfg_env.spec.is_enabled_in(SpecId::LONDON) {
165 Some(self.block_env.basefee)
166 } else {
167 None
168 };
169
170 let is_shanghai = self.cfg_env.spec >= SpecId::SHANGHAI;
171 let is_cancun = self.cfg_env.spec >= SpecId::CANCUN;
172 let is_prague = self.cfg_env.spec >= SpecId::PRAGUE;
173 let excess_blob_gas = if is_cancun { self.block_env.blob_excess_gas() } else { None };
174 let mut cumulative_blob_gas_used = if is_cancun { Some(0u64) } else { None };
175
176 for tx in self.into_iter() {
177 let tx = match tx {
178 TransactionExecutionOutcome::Executed(tx) => {
179 included.push(tx.transaction.clone());
180 tx
181 }
182 TransactionExecutionOutcome::BlockGasExhausted(tx) => {
183 trace!(target: "backend", tx_gas_limit = %tx.pending_transaction.transaction.gas_limit(), ?tx, "block gas limit exhausting, skipping transaction");
184 continue;
185 }
186 TransactionExecutionOutcome::BlobGasExhausted(tx) => {
187 trace!(target: "backend", blob_gas = %tx.pending_transaction.transaction.blob_gas_used().unwrap_or_default(), ?tx, "block blob gas limit exhausting, skipping transaction");
188 continue;
189 }
190 TransactionExecutionOutcome::TransactionGasExhausted(tx) => {
191 trace!(target: "backend", tx_gas_limit = %tx.pending_transaction.transaction.gas_limit(), ?tx, "transaction gas limit exhausting, skipping transaction");
192 continue;
193 }
194 TransactionExecutionOutcome::Invalid(tx, _) => {
195 trace!(target: "backend", ?tx, "skipping invalid transaction");
196 invalid.push(tx);
197 continue;
198 }
199 TransactionExecutionOutcome::DatabaseError(_, err) => {
200 trace!(target: "backend", ?err, "Failed to execute transaction due to database error");
203 continue;
204 }
205 };
206 if is_cancun {
207 let tx_blob_gas =
208 tx.transaction.pending_transaction.transaction.blob_gas_used().unwrap_or(0);
209 cumulative_blob_gas_used =
210 Some(cumulative_blob_gas_used.unwrap_or(0u64).saturating_add(tx_blob_gas));
211 }
212 let receipt = tx.create_receipt(&mut cumulative_gas_used);
213
214 let ExecutedTransaction { transaction, logs, out, traces, exit_reason: exit, .. } = tx;
215 build_logs_bloom(&logs, &mut bloom);
216
217 let contract_address = out.as_ref().and_then(|out| {
218 if let Output::Create(_, contract_address) = out {
219 trace!(target: "backend", "New contract deployed: at {:?}", contract_address);
220 *contract_address
221 } else {
222 None
223 }
224 });
225
226 let transaction_index = transaction_infos.len() as u64;
227 let info = TransactionInfo {
228 transaction_hash: transaction.hash(),
229 transaction_index,
230 from: *transaction.pending_transaction.sender(),
231 to: transaction.pending_transaction.transaction.to(),
232 contract_address,
233 traces,
234 exit,
235 out: out.map(Output::into_data),
236 nonce: tx.nonce,
237 gas_used: tx.gas_used,
238 };
239
240 transaction_infos.push(info);
241 receipts.push(receipt);
242 transactions.push(transaction.pending_transaction.transaction.clone());
243 }
244
245 let receipts_root = calculate_receipt_root(&receipts);
246
247 let header = Header {
248 parent_hash,
249 ommers_hash: Default::default(),
250 beneficiary,
251 state_root: self.db.maybe_state_root().unwrap_or_default(),
252 transactions_root: Default::default(), receipts_root,
254 logs_bloom: bloom,
255 difficulty,
256 number: block_number.saturating_to(),
257 gas_limit,
258 gas_used: cumulative_gas_used,
259 timestamp: timestamp.saturating_to(),
260 extra_data: Default::default(),
261 mix_hash: mix_hash.unwrap_or_default(),
262 nonce: Default::default(),
263 base_fee_per_gas: base_fee,
264 parent_beacon_block_root: is_cancun.then_some(Default::default()),
265 blob_gas_used: cumulative_blob_gas_used,
266 excess_blob_gas,
267 withdrawals_root: is_shanghai.then_some(EMPTY_WITHDRAWALS),
268 requests_hash: is_prague.then_some(EMPTY_REQUESTS_HASH),
269 };
270
271 let block = create_block(header, transactions);
272 let block = BlockInfo { block, transactions: transaction_infos, receipts };
273 ExecutedTransactions { block, included, invalid }
274 }
275
276 fn env_for(&self, tx: &PendingTransaction) -> Env {
277 let mut tx_env: OpTransaction<TxEnv> =
278 FromRecoveredTx::from_recovered_tx(tx.transaction.as_ref(), *tx.sender());
279
280 if let FoundryTxEnvelope::Eip7702(tx_7702) = tx.transaction.as_ref()
281 && self.cheats.has_recover_overrides()
282 {
283 let cheated_auths = tx_7702
285 .tx()
286 .authorization_list
287 .iter()
288 .zip(tx_env.base.authorization_list)
289 .map(|(signed_auth, either_auth)| {
290 either_auth.right_and_then(|recovered_auth| {
291 if recovered_auth.authority().is_none()
292 && let Ok(signature) = signed_auth.signature()
293 && let Some(override_addr) =
294 self.cheats.get_recover_override(&signature.as_bytes().into())
295 {
296 Either::Right(RecoveredAuthorization::new_unchecked(
297 recovered_auth.into_parts().0,
298 RecoveredAuthority::Valid(override_addr),
299 ))
300 } else {
301 Either::Right(recovered_auth)
302 }
303 })
304 })
305 .collect();
306 tx_env.base.authorization_list = cheated_auths;
307 }
308
309 if self.networks.is_optimism() {
310 tx_env.enveloped_tx = Some(alloy_rlp::encode(tx.transaction.as_ref()).into());
311 }
312
313 Env::new(self.cfg_env.clone(), self.block_env.clone(), tx_env, self.networks)
314 }
315}
316
317#[derive(Debug)]
319pub enum TransactionExecutionOutcome {
320 Executed(ExecutedTransaction),
322 Invalid(Arc<PoolTransaction>, InvalidTransactionError),
324 BlockGasExhausted(Arc<PoolTransaction>),
326 BlobGasExhausted(Arc<PoolTransaction>),
328 TransactionGasExhausted(Arc<PoolTransaction>),
330 DatabaseError(Arc<PoolTransaction>, DatabaseError),
332}
333
334impl<DB: Db + ?Sized, V: TransactionValidator> Iterator for &mut TransactionExecutor<'_, DB, V> {
335 type Item = TransactionExecutionOutcome;
336
337 fn next(&mut self) -> Option<Self::Item> {
338 let transaction = self.pending.next()?;
339 let sender = *transaction.pending_transaction.sender();
340 let account = match self.db.basic(sender).map(|acc| acc.unwrap_or_default()) {
341 Ok(account) => account,
342 Err(err) => return Some(TransactionExecutionOutcome::DatabaseError(transaction, err)),
343 };
344 let env = self.env_for(&transaction.pending_transaction);
345
346 let max_block_gas = self.gas_used.saturating_add(env.tx.base.gas_limit);
348 if !env.evm_env.cfg_env.disable_block_gas_limit
349 && max_block_gas > env.evm_env.block_env.gas_limit
350 {
351 return Some(TransactionExecutionOutcome::BlockGasExhausted(transaction));
352 }
353
354 if env.evm_env.cfg_env.tx_gas_limit_cap.is_none()
356 && transaction.pending_transaction.transaction.gas_limit()
357 > env.evm_env.cfg_env().tx_gas_limit_cap()
358 {
359 return Some(TransactionExecutionOutcome::TransactionGasExhausted(transaction));
360 }
361
362 let max_blob_gas = self.blob_gas_used.saturating_add(
364 transaction.pending_transaction.transaction.blob_gas_used().unwrap_or(0),
365 );
366 if max_blob_gas > self.blob_params.max_blob_gas_per_block() {
367 return Some(TransactionExecutionOutcome::BlobGasExhausted(transaction));
368 }
369
370 if let Err(err) = self.validator.validate_pool_transaction_for(
372 &transaction.pending_transaction,
373 &account,
374 &env,
375 ) {
376 warn!(target: "backend", "Skipping invalid tx execution [{:?}] {}", transaction.hash(), err);
377 return Some(TransactionExecutionOutcome::Invalid(transaction, err));
378 }
379
380 let nonce = account.nonce;
381
382 let mut inspector = AnvilInspector::default().with_tracing();
383 if self.enable_steps_tracing {
384 inspector = inspector.with_steps_tracing();
385 }
386 if self.print_logs {
387 inspector = inspector.with_log_collector();
388 }
389 if self.print_traces {
390 inspector = inspector.with_trace_printer();
391 }
392
393 let exec_result = {
394 let mut evm = new_evm_with_inspector(&mut *self.db, &env, &mut inspector);
395 self.networks.inject_precompiles(evm.precompiles_mut());
396
397 if let Some(factory) = &self.precompile_factory {
398 evm.precompiles_mut().extend_precompiles(factory.precompiles());
399 }
400
401 let cheats = Arc::new(self.cheats.clone());
402 if cheats.has_recover_overrides() {
403 let cheat_ecrecover = CheatEcrecover::new(Arc::clone(&cheats));
404 evm.precompiles_mut().apply_precompile(&EC_RECOVER, move |_| {
405 Some(DynPrecompile::new_stateful(
406 cheat_ecrecover.precompile_id().clone(),
407 move |input| cheat_ecrecover.call(input),
408 ))
409 });
410 }
411
412 trace!(target: "backend", "[{:?}] executing", transaction.hash());
413 match evm.transact_commit(env.tx) {
415 Ok(exec_result) => exec_result,
416 Err(err) => {
417 warn!(target: "backend", "[{:?}] failed to execute: {:?}", transaction.hash(), err);
418 match err {
419 EVMError::Database(err) => {
420 return Some(TransactionExecutionOutcome::DatabaseError(
421 transaction,
422 err,
423 ));
424 }
425 EVMError::Transaction(err) => {
426 return Some(TransactionExecutionOutcome::Invalid(
427 transaction,
428 err.into(),
429 ));
430 }
431 e => panic!("failed to execute transaction: {e}"),
434 }
435 }
436 }
437 };
438
439 if self.print_traces {
440 inspector.print_traces(self.call_trace_decoder.clone());
441 }
442 inspector.print_logs();
443
444 let (exit_reason, gas_used, out, logs) = match exec_result {
445 ExecutionResult::Success { reason, gas_used, logs, output, .. } => {
446 (reason.into(), gas_used, Some(output), Some(logs))
447 }
448 ExecutionResult::Revert { gas_used, output } => {
449 (InstructionResult::Revert, gas_used, Some(Output::Call(output)), None)
450 }
451 ExecutionResult::Halt { reason, gas_used } => {
452 (op_haltreason_to_instruction_result(reason), gas_used, None, None)
453 }
454 };
455
456 if exit_reason == InstructionResult::OutOfGas {
457 warn!(target: "backend", "[{:?}] executed with out of gas", transaction.hash())
459 }
460
461 trace!(target: "backend", ?exit_reason, ?gas_used, "[{:?}] executed with out={:?}", transaction.hash(), out);
462
463 self.gas_used = self.gas_used.saturating_add(gas_used);
465
466 if let Some(blob_gas) = transaction.pending_transaction.transaction.blob_gas_used() {
468 self.blob_gas_used = self.blob_gas_used.saturating_add(blob_gas);
469 }
470
471 trace!(target: "backend::executor", "transacted [{:?}], result: {:?} gas {}", transaction.hash(), exit_reason, gas_used);
472
473 let tx = ExecutedTransaction {
474 transaction,
475 exit_reason,
476 out,
477 gas_used,
478 logs: logs.unwrap_or_default(),
479 traces: inspector.tracer.map(|t| t.into_traces().into_nodes()).unwrap_or_default(),
480 nonce,
481 };
482
483 Some(TransactionExecutionOutcome::Executed(tx))
484 }
485}
486
487fn build_logs_bloom(logs: &[Log], bloom: &mut Bloom) {
489 for log in logs {
490 bloom.accrue(BloomInput::Raw(&log.address[..]));
491 for topic in log.topics() {
492 bloom.accrue(BloomInput::Raw(&topic[..]));
493 }
494 }
495}
496
497pub fn new_evm_with_inspector<DB, I>(
499 db: DB,
500 env: &Env,
501 inspector: I,
502) -> EitherEvm<DB, I, PrecompilesMap>
503where
504 DB: Database<Error = DatabaseError> + Debug,
505 I: Inspector<EthEvmContext<DB>> + Inspector<OpContext<DB>>,
506{
507 if env.networks.is_optimism() {
508 let op_cfg = env.evm_env.cfg_env.clone().with_spec(op_revm::OpSpecId::ISTHMUS);
509 let op_context = OpContext {
510 journaled_state: {
511 let mut journal = Journal::new(db);
512 journal.set_spec_id(env.evm_env.cfg_env.spec);
514 journal
515 },
516 block: env.evm_env.block_env.clone(),
517 cfg: op_cfg.clone(),
518 tx: env.tx.clone(),
519 chain: L1BlockInfo::default(),
520 local: LocalContext::default(),
521 error: Ok(()),
522 };
523
524 let op_precompiles = OpPrecompiles::new_with_spec(op_cfg.spec).precompiles();
525 let op_evm = op_revm::OpEvm(RevmEvm::new_with_inspector(
526 op_context,
527 inspector,
528 EthInstructions::default(),
529 PrecompilesMap::from_static(op_precompiles),
530 ));
531
532 let op = OpEvm::new(op_evm, true);
533
534 EitherEvm::Op(op)
535 } else {
536 let spec = env.evm_env.cfg_env.spec;
537 let eth_context = EthEvmContext {
538 journaled_state: {
539 let mut journal = Journal::new(db);
540 journal.set_spec_id(spec);
541 journal
542 },
543 block: env.evm_env.block_env.clone(),
544 cfg: env.evm_env.cfg_env.clone(),
545 tx: env.tx.base.clone(),
546 chain: (),
547 local: LocalContext::default(),
548 error: Ok(()),
549 };
550
551 let eth_precompiles = EthPrecompiles {
552 precompiles: Precompiles::new(PrecompileSpecId::from_spec_id(spec)),
553 spec,
554 }
555 .precompiles;
556 let eth_evm = RevmEvm::new_with_inspector(
557 eth_context,
558 inspector,
559 EthInstructions::default(),
560 PrecompilesMap::from_static(eth_precompiles),
561 );
562
563 let eth = EthEvm::new(eth_evm, true);
564
565 EitherEvm::Eth(eth)
566 }
567}
568
569pub fn new_evm_with_inspector_ref<'db, DB, I>(
571 db: &'db DB,
572 env: &Env,
573 inspector: &'db mut I,
574) -> EitherEvm<WrapDatabaseRef<&'db DB>, &'db mut I, PrecompilesMap>
575where
576 DB: DatabaseRef<Error = DatabaseError> + Debug + 'db + ?Sized,
577 I: Inspector<EthEvmContext<WrapDatabaseRef<&'db DB>>>
578 + Inspector<OpContext<WrapDatabaseRef<&'db DB>>>,
579 WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
580{
581 new_evm_with_inspector(WrapDatabaseRef(db), env, inspector)
582}