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 Encodable2718, eip2935, eip4788,
22 eip7685::EMPTY_REQUESTS_HASH,
23 eip7702::{RecoveredAuthority, RecoveredAuthorization},
24 eip7840::BlobParams,
25};
26use alloy_evm::{
27 EthEvmFactory, Evm, EvmEnv, EvmFactory, FromRecoveredTx,
28 eth::EthEvmContext,
29 precompiles::{DynPrecompile, Precompile, PrecompilesMap},
30};
31use alloy_op_evm::OpEvmFactory;
32use alloy_primitives::{B256, Bloom, BloomInput, Bytes, Log};
33use anvil_core::eth::{
34 block::{BlockInfo, create_block},
35 transaction::{PendingTransaction, TransactionInfo},
36};
37use foundry_evm::{
38 backend::DatabaseError,
39 core::{either_evm::EitherEvm, precompiles::EC_RECOVER},
40 traces::{CallTraceDecoder, CallTraceNode},
41};
42use foundry_evm_networks::NetworkConfigs;
43use foundry_primitives::{FoundryReceiptEnvelope, FoundryTxEnvelope};
44use op_revm::{OpContext, OpTransaction};
45use revm::{
46 Database, Inspector,
47 context::{Block as RevmBlock, Cfg, TxEnv},
48 context_interface::result::{EVMError, ExecutionResult, Output},
49 interpreter::InstructionResult,
50 primitives::hardfork::SpecId,
51};
52use std::{fmt::Debug, sync::Arc};
53
54#[derive(Debug)]
56pub struct ExecutedTransaction {
57 transaction: Arc<PoolTransaction>,
58 exit_reason: InstructionResult,
59 out: Option<Output>,
60 gas_used: u64,
61 logs: Vec<Log>,
62 traces: Vec<CallTraceNode>,
63 nonce: u64,
64}
65
66impl ExecutedTransaction {
69 fn create_receipt(&self, cumulative_gas_used: &mut u64) -> FoundryReceiptEnvelope {
71 let logs = self.logs.clone();
72 *cumulative_gas_used = cumulative_gas_used.saturating_add(self.gas_used);
73
74 let status_code = u8::from(self.exit_reason.is_ok());
76 let receipt_with_bloom: ReceiptWithBloom = Receipt {
77 status: (status_code == 1).into(),
78 cumulative_gas_used: *cumulative_gas_used,
79 logs,
80 }
81 .into();
82
83 match self.transaction.pending_transaction.transaction.as_ref() {
84 FoundryTxEnvelope::Legacy(_) => FoundryReceiptEnvelope::Legacy(receipt_with_bloom),
85 FoundryTxEnvelope::Eip2930(_) => FoundryReceiptEnvelope::Eip2930(receipt_with_bloom),
86 FoundryTxEnvelope::Eip1559(_) => FoundryReceiptEnvelope::Eip1559(receipt_with_bloom),
87 FoundryTxEnvelope::Eip4844(_) => FoundryReceiptEnvelope::Eip4844(receipt_with_bloom),
88 FoundryTxEnvelope::Eip7702(_) => FoundryReceiptEnvelope::Eip7702(receipt_with_bloom),
89 FoundryTxEnvelope::Deposit(_tx) => {
90 FoundryReceiptEnvelope::Deposit(op_alloy_consensus::OpDepositReceiptWithBloom {
91 receipt: op_alloy_consensus::OpDepositReceipt {
92 inner: receipt_with_bloom.receipt,
93 deposit_nonce: Some(0),
94 deposit_receipt_version: Some(1),
95 },
96 logs_bloom: receipt_with_bloom.logs_bloom,
97 })
98 }
99 FoundryTxEnvelope::Tempo(_) => todo!(),
101 }
102 }
103}
104
105#[derive(Clone, Debug)]
107pub struct ExecutedTransactions {
108 pub block: BlockInfo,
110 pub included: Vec<Arc<PoolTransaction>>,
112 pub invalid: Vec<Arc<PoolTransaction>>,
115}
116
117pub struct TransactionExecutor<'a, Db: ?Sized, V: TransactionValidator> {
119 pub db: &'a mut Db,
121 pub validator: &'a V,
123 pub pending: std::vec::IntoIter<Arc<PoolTransaction>>,
125 pub evm_env: EvmEnv,
126 pub parent_hash: B256,
127 pub gas_used: u64,
129 pub blob_gas_used: u64,
131 pub enable_steps_tracing: bool,
132 pub networks: NetworkConfigs,
133 pub print_logs: bool,
134 pub print_traces: bool,
135 pub call_trace_decoder: Arc<CallTraceDecoder>,
137 pub precompile_factory: Option<Arc<dyn PrecompileFactory>>,
139 pub blob_params: BlobParams,
140 pub cheats: CheatsManager,
141}
142
143impl<DB: Db + ?Sized, V: TransactionValidator> TransactionExecutor<'_, DB, V> {
144 pub fn execute(mut self) -> ExecutedTransactions {
146 let mut transactions = Vec::new();
147 let mut transaction_infos = Vec::new();
148 let mut receipts = Vec::new();
149 let mut bloom = Bloom::default();
150 let mut cumulative_gas_used = 0u64;
151 let mut invalid = Vec::new();
152 let mut included = Vec::new();
153 let gas_limit = self.evm_env.block_env().gas_limit;
154 let parent_hash = self.parent_hash;
155 let block_number = self.evm_env.block_env().number;
156 let difficulty = self.evm_env.block_env().difficulty;
157 let mix_hash = self.evm_env.block_env().prevrandao;
158 let beneficiary = self.evm_env.block_env().beneficiary;
159 let timestamp = self.evm_env.block_env().timestamp;
160 let base_fee = if self.evm_env.cfg_env().spec.is_enabled_in(SpecId::LONDON) {
161 Some(self.evm_env.block_env().basefee)
162 } else {
163 None
164 };
165
166 let is_shanghai = self.evm_env.cfg_env().spec >= SpecId::SHANGHAI;
167 let is_cancun = self.evm_env.cfg_env().spec >= SpecId::CANCUN;
168 let is_prague = self.evm_env.cfg_env().spec >= SpecId::PRAGUE;
169 let excess_blob_gas =
170 if is_cancun { self.evm_env.block_env().blob_excess_gas() } else { None };
171 let mut cumulative_blob_gas_used = if is_cancun { Some(0u64) } else { None };
172
173 if is_prague && !block_number.is_zero() {
175 let env = Env::new(self.evm_env.clone(), Default::default(), self.networks);
176 let mut inspector = AnvilInspector::default();
177 let mut evm = new_evm_with_inspector(&mut *self.db, &env, &mut inspector);
178 match evm.transact_system_call(
180 eip4788::SYSTEM_ADDRESS,
181 eip2935::HISTORY_STORAGE_ADDRESS,
182 Bytes::copy_from_slice(parent_hash.as_slice()),
183 ) {
184 Ok(result) => {
185 self.db.commit(result.state);
186 }
187 Err(err) => {
188 warn!(target: "backend", "EIP-2935 system call failed: {:?}", err);
189 }
190 }
191 }
192
193 for tx in self.into_iter() {
194 let tx = match tx {
195 TransactionExecutionOutcome::Executed(tx) => {
196 included.push(tx.transaction.clone());
197 tx
198 }
199 TransactionExecutionOutcome::BlockGasExhausted(tx) => {
200 trace!(target: "backend", tx_gas_limit = %tx.pending_transaction.transaction.gas_limit(), ?tx, "block gas limit exhausting, skipping transaction");
201 continue;
202 }
203 TransactionExecutionOutcome::BlobGasExhausted(tx) => {
204 trace!(target: "backend", blob_gas = %tx.pending_transaction.transaction.blob_gas_used().unwrap_or_default(), ?tx, "block blob gas limit exhausting, skipping transaction");
205 continue;
206 }
207 TransactionExecutionOutcome::TransactionGasExhausted(tx) => {
208 trace!(target: "backend", tx_gas_limit = %tx.pending_transaction.transaction.gas_limit(), ?tx, "transaction gas limit exhausting, skipping transaction");
209 continue;
210 }
211 TransactionExecutionOutcome::Invalid(tx, _) => {
212 trace!(target: "backend", ?tx, "skipping invalid transaction");
213 invalid.push(tx);
214 continue;
215 }
216 TransactionExecutionOutcome::DatabaseError(_, err) => {
217 trace!(target: "backend", ?err, "Failed to execute transaction due to database error");
220 continue;
221 }
222 };
223 if is_cancun {
224 let tx_blob_gas =
225 tx.transaction.pending_transaction.transaction.blob_gas_used().unwrap_or(0);
226 cumulative_blob_gas_used =
227 Some(cumulative_blob_gas_used.unwrap_or(0u64).saturating_add(tx_blob_gas));
228 }
229 let receipt = tx.create_receipt(&mut cumulative_gas_used);
230
231 let ExecutedTransaction { transaction, logs, out, traces, exit_reason: exit, .. } = tx;
232 build_logs_bloom(&logs, &mut bloom);
233
234 let sender = *transaction.pending_transaction.sender();
237 let contract_address = if transaction.pending_transaction.transaction.to().is_none() {
238 let addr = sender.create(tx.nonce);
239 trace!(target: "backend", "Contract creation tx: computed address {:?}", addr);
240 Some(addr)
241 } else {
242 None
243 };
244
245 let transaction_index = transaction_infos.len() as u64;
246 let info = TransactionInfo {
247 transaction_hash: transaction.hash(),
248 transaction_index,
249 from: *transaction.pending_transaction.sender(),
250 to: transaction.pending_transaction.transaction.to(),
251 contract_address,
252 traces,
253 exit,
254 out: out.map(Output::into_data),
255 nonce: tx.nonce,
256 gas_used: tx.gas_used,
257 };
258
259 transaction_infos.push(info);
260 receipts.push(receipt);
261 transactions.push(transaction.pending_transaction.transaction.clone());
262 }
263
264 let receipts_root = calculate_receipt_root(&receipts);
265
266 let header = Header {
267 parent_hash,
268 ommers_hash: Default::default(),
269 beneficiary,
270 state_root: self.db.maybe_state_root().unwrap_or_default(),
271 transactions_root: Default::default(), receipts_root,
273 logs_bloom: bloom,
274 difficulty,
275 number: block_number.saturating_to(),
276 gas_limit,
277 gas_used: cumulative_gas_used,
278 timestamp: timestamp.saturating_to(),
279 extra_data: Default::default(),
280 mix_hash: mix_hash.unwrap_or_default(),
281 nonce: Default::default(),
282 base_fee_per_gas: base_fee,
283 parent_beacon_block_root: is_cancun.then_some(Default::default()),
284 blob_gas_used: cumulative_blob_gas_used,
285 excess_blob_gas,
286 withdrawals_root: is_shanghai.then_some(EMPTY_WITHDRAWALS),
287 requests_hash: is_prague.then_some(EMPTY_REQUESTS_HASH),
288 };
289
290 let block = create_block(header, transactions);
291 let block = BlockInfo { block, transactions: transaction_infos, receipts };
292 ExecutedTransactions { block, included, invalid }
293 }
294
295 fn env_for(&self, tx: &PendingTransaction) -> Env {
296 let mut tx_env: OpTransaction<TxEnv> =
297 FromRecoveredTx::from_recovered_tx(tx.transaction.as_ref(), *tx.sender());
298
299 if let FoundryTxEnvelope::Eip7702(tx_7702) = tx.transaction.as_ref()
300 && self.cheats.has_recover_overrides()
301 {
302 let cheated_auths = tx_7702
304 .tx()
305 .authorization_list
306 .iter()
307 .zip(tx_env.base.authorization_list)
308 .map(|(signed_auth, either_auth)| {
309 either_auth.right_and_then(|recovered_auth| {
310 if recovered_auth.authority().is_none()
311 && let Ok(signature) = signed_auth.signature()
312 && let Some(override_addr) =
313 self.cheats.get_recover_override(&signature.as_bytes().into())
314 {
315 Either::Right(RecoveredAuthorization::new_unchecked(
316 recovered_auth.into_parts().0,
317 RecoveredAuthority::Valid(override_addr),
318 ))
319 } else {
320 Either::Right(recovered_auth)
321 }
322 })
323 })
324 .collect();
325 tx_env.base.authorization_list = cheated_auths;
326 }
327
328 if self.networks.is_optimism() {
329 tx_env.enveloped_tx = Some(tx.transaction.encoded_2718().into());
330 }
331
332 Env::new(self.evm_env.clone(), tx_env, self.networks)
333 }
334}
335
336#[derive(Debug)]
338pub enum TransactionExecutionOutcome {
339 Executed(ExecutedTransaction),
341 Invalid(Arc<PoolTransaction>, InvalidTransactionError),
343 BlockGasExhausted(Arc<PoolTransaction>),
345 BlobGasExhausted(Arc<PoolTransaction>),
347 TransactionGasExhausted(Arc<PoolTransaction>),
349 DatabaseError(Arc<PoolTransaction>, DatabaseError),
351}
352
353impl<DB: Db + ?Sized, V: TransactionValidator> Iterator for &mut TransactionExecutor<'_, DB, V> {
354 type Item = TransactionExecutionOutcome;
355
356 fn next(&mut self) -> Option<Self::Item> {
357 let transaction = self.pending.next()?;
358 let sender = *transaction.pending_transaction.sender();
359 let account = match self.db.basic(sender).map(|acc| acc.unwrap_or_default()) {
360 Ok(account) => account,
361 Err(err) => return Some(TransactionExecutionOutcome::DatabaseError(transaction, err)),
362 };
363 let env = self.env_for(&transaction.pending_transaction);
364
365 let max_block_gas = self.gas_used.saturating_add(env.tx.base.gas_limit);
367 if !env.evm_env.cfg_env.disable_block_gas_limit
368 && max_block_gas > env.evm_env.block_env.gas_limit
369 {
370 return Some(TransactionExecutionOutcome::BlockGasExhausted(transaction));
371 }
372
373 if env.evm_env.cfg_env.tx_gas_limit_cap.is_none()
375 && transaction.pending_transaction.transaction.gas_limit()
376 > env.evm_env.cfg_env().tx_gas_limit_cap()
377 {
378 return Some(TransactionExecutionOutcome::TransactionGasExhausted(transaction));
379 }
380
381 let max_blob_gas = self.blob_gas_used.saturating_add(
383 transaction.pending_transaction.transaction.blob_gas_used().unwrap_or(0),
384 );
385 if max_blob_gas > self.blob_params.max_blob_gas_per_block() {
386 return Some(TransactionExecutionOutcome::BlobGasExhausted(transaction));
387 }
388
389 if let Err(err) = self.validator.validate_pool_transaction_for(
391 &transaction.pending_transaction,
392 &account,
393 &env,
394 ) {
395 warn!(target: "backend", "Skipping invalid tx execution [{:?}] {}", transaction.hash(), err);
396 return Some(TransactionExecutionOutcome::Invalid(transaction, err));
397 }
398
399 let nonce = account.nonce;
400
401 let mut inspector = AnvilInspector::default().with_tracing();
402 if self.enable_steps_tracing {
403 inspector = inspector.with_steps_tracing();
404 }
405 if self.print_logs {
406 inspector = inspector.with_log_collector();
407 }
408 if self.print_traces {
409 inspector = inspector.with_trace_printer();
410 }
411
412 let exec_result = {
413 let mut evm = new_evm_with_inspector(&mut *self.db, &env, &mut inspector);
414 self.networks.inject_precompiles(evm.precompiles_mut());
415
416 if let Some(factory) = &self.precompile_factory {
417 evm.precompiles_mut().extend_precompiles(factory.precompiles());
418 }
419
420 let cheats = Arc::new(self.cheats.clone());
421 if cheats.has_recover_overrides() {
422 let cheat_ecrecover = CheatEcrecover::new(Arc::clone(&cheats));
423 evm.precompiles_mut().apply_precompile(&EC_RECOVER, move |_| {
424 Some(DynPrecompile::new_stateful(
425 cheat_ecrecover.precompile_id().clone(),
426 move |input| cheat_ecrecover.call(input),
427 ))
428 });
429 }
430
431 trace!(target: "backend", "[{:?}] executing", transaction.hash());
432 match evm.transact_commit(env.tx) {
434 Ok(exec_result) => exec_result,
435 Err(err) => {
436 warn!(target: "backend", "[{:?}] failed to execute: {:?}", transaction.hash(), err);
437 match err {
438 EVMError::Database(err) => {
439 return Some(TransactionExecutionOutcome::DatabaseError(
440 transaction,
441 err,
442 ));
443 }
444 EVMError::Transaction(err) => {
445 return Some(TransactionExecutionOutcome::Invalid(
446 transaction,
447 err.into(),
448 ));
449 }
450 e => panic!("failed to execute transaction: {e}"),
453 }
454 }
455 }
456 };
457
458 if self.print_traces {
459 inspector.print_traces(self.call_trace_decoder.clone());
460 }
461 inspector.print_logs();
462
463 let (exit_reason, gas_used, out, logs) = match exec_result {
464 ExecutionResult::Success { reason, gas_used, logs, output, .. } => {
465 (reason.into(), gas_used, Some(output), Some(logs))
466 }
467 ExecutionResult::Revert { gas_used, output } => {
468 (InstructionResult::Revert, gas_used, Some(Output::Call(output)), None)
469 }
470 ExecutionResult::Halt { reason, gas_used } => {
471 (op_haltreason_to_instruction_result(reason), gas_used, None, None)
472 }
473 };
474
475 if exit_reason == InstructionResult::OutOfGas {
476 warn!(target: "backend", "[{:?}] executed with out of gas", transaction.hash())
478 }
479
480 trace!(target: "backend", ?exit_reason, ?gas_used, "[{:?}] executed with out={:?}", transaction.hash(), out);
481
482 self.gas_used = self.gas_used.saturating_add(gas_used);
484
485 if let Some(blob_gas) = transaction.pending_transaction.transaction.blob_gas_used() {
487 self.blob_gas_used = self.blob_gas_used.saturating_add(blob_gas);
488 }
489
490 trace!(target: "backend::executor", "transacted [{:?}], result: {:?} gas {}", transaction.hash(), exit_reason, gas_used);
491
492 let tx = ExecutedTransaction {
493 transaction,
494 exit_reason,
495 out,
496 gas_used,
497 logs: logs.unwrap_or_default(),
498 traces: inspector.tracer.map(|t| t.into_traces().into_nodes()).unwrap_or_default(),
499 nonce,
500 };
501
502 Some(TransactionExecutionOutcome::Executed(tx))
503 }
504}
505
506fn build_logs_bloom(logs: &[Log], bloom: &mut Bloom) {
508 for log in logs {
509 bloom.accrue(BloomInput::Raw(&log.address[..]));
510 for topic in log.topics() {
511 bloom.accrue(BloomInput::Raw(&topic[..]));
512 }
513 }
514}
515
516pub fn new_evm_with_inspector<DB, I>(
518 db: DB,
519 env: &Env,
520 inspector: I,
521) -> EitherEvm<DB, I, PrecompilesMap>
522where
523 DB: Database<Error = DatabaseError> + Debug,
524 I: Inspector<EthEvmContext<DB>> + Inspector<OpContext<DB>>,
525{
526 if env.networks.is_optimism() {
527 let evm_env = EvmEnv::new(
528 env.evm_env
529 .cfg_env
530 .clone()
531 .with_spec_and_mainnet_gas_params(op_revm::OpSpecId::ISTHMUS),
532 env.evm_env.block_env.clone(),
533 );
534 EitherEvm::Op(OpEvmFactory::default().create_evm_with_inspector(db, evm_env, inspector))
535 } else {
536 EitherEvm::Eth(EthEvmFactory::default().create_evm_with_inspector(
537 db,
538 env.evm_env.clone(),
539 inspector,
540 ))
541 }
542}