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, Transaction, TransactionEnvelope, TxReceipt,
10 transaction::{Either, Recovered},
11};
12use alloy_eips::{
13 Encodable2718, eip2935, eip4788,
14 eip7702::{RecoveredAuthority, RecoveredAuthorization},
15};
16use alloy_evm::{
17 Evm, FromRecoveredTx, FromTxWithEncoded, RecoveredTx,
18 block::{
19 BlockExecutionError, BlockExecutionResult, BlockExecutor, BlockValidationError,
20 ExecutableTx, GasOutput, OnStateHook, StateDB, TxResult,
21 },
22 eth::{
23 EthTxResult,
24 receipt_builder::{ReceiptBuilder, ReceiptBuilderCtx},
25 },
26};
27use alloy_primitives::{Address, B256, Bytes, U256};
28use anvil_core::eth::transaction::{
29 MaybeImpersonatedTransaction, PendingTransaction, TransactionInfo,
30};
31use foundry_evm::core::{env::FoundryTransaction, evm::IntoInstructionResult};
32use foundry_primitives::{FoundryReceiptEnvelope, FoundryTxEnvelope, FoundryTxType};
33use revm::{
34 Database, DatabaseCommit,
35 context::Block as RevmBlock,
36 context_interface::result::{ExecutionResult, Output, ResultAndState},
37 interpreter::InstructionResult,
38 primitives::hardfork::SpecId,
39 state::AccountInfo,
40};
41use std::{fmt, fmt::Debug, mem::take, sync::Arc};
42
43#[derive(Debug, Default, Clone, Copy)]
45#[non_exhaustive]
46pub struct FoundryReceiptBuilder;
47
48impl ReceiptBuilder for FoundryReceiptBuilder {
49 type Transaction = FoundryTxEnvelope;
50 type Receipt = FoundryReceiptEnvelope;
51
52 fn build_receipt<E: Evm>(
53 &self,
54 ctx: ReceiptBuilderCtx<'_, FoundryTxType, E>,
55 ) -> FoundryReceiptEnvelope {
56 let receipt = alloy_consensus::Receipt {
57 status: Eip658Value::Eip658(ctx.result.is_success()),
58 cumulative_gas_used: ctx.cumulative_gas_used,
59 logs: ctx.result.into_logs(),
60 }
61 .with_bloom();
62
63 match ctx.tx_type {
64 FoundryTxType::Legacy => FoundryReceiptEnvelope::Legacy(receipt),
65 FoundryTxType::Eip2930 => FoundryReceiptEnvelope::Eip2930(receipt),
66 FoundryTxType::Eip1559 => FoundryReceiptEnvelope::Eip1559(receipt),
67 FoundryTxType::Eip4844 => FoundryReceiptEnvelope::Eip4844(receipt),
68 FoundryTxType::Eip7702 => FoundryReceiptEnvelope::Eip7702(receipt),
69 #[cfg(feature = "optimism")]
70 FoundryTxType::Deposit => {
71 unreachable!("deposit receipts are built in commit_transaction")
72 }
73 #[cfg(feature = "optimism")]
74 FoundryTxType::PostExec => FoundryReceiptEnvelope::PostExec(receipt),
75 FoundryTxType::Tempo => FoundryReceiptEnvelope::Tempo(receipt),
76 }
77 }
78}
79
80#[derive(Debug)]
84pub struct AnvilTxResult<H> {
85 pub inner: EthTxResult<H, FoundryTxType>,
86 pub sender: Address,
87}
88
89impl<H: Send + 'static> TxResult for AnvilTxResult<H> {
90 type HaltReason = H;
91
92 fn result(&self) -> &ResultAndState<Self::HaltReason> {
93 self.inner.result()
94 }
95
96 fn into_result(self) -> ResultAndState<Self::HaltReason> {
97 self.inner.into_result()
98 }
99}
100
101pub struct AnvilBlockExecutor<E> {
107 evm: E,
109 parent_hash: B256,
111 spec_id: SpecId,
113 receipt_builder: FoundryReceiptBuilder,
115 receipts: Vec<FoundryReceiptEnvelope>,
117 gas_used: u64,
119 blob_gas_used: u64,
121 state_hook: Option<Box<dyn OnStateHook>>,
123}
124
125impl<E: fmt::Debug> fmt::Debug for AnvilBlockExecutor<E> {
126 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127 f.debug_struct("AnvilBlockExecutor")
128 .field("evm", &self.evm)
129 .field("parent_hash", &self.parent_hash)
130 .field("spec_id", &self.spec_id)
131 .field("gas_used", &self.gas_used)
132 .field("blob_gas_used", &self.blob_gas_used)
133 .field("receipts", &self.receipts.len())
134 .finish_non_exhaustive()
135 }
136}
137
138impl<E> AnvilBlockExecutor<E> {
139 pub fn new(evm: E, parent_hash: B256, spec_id: SpecId) -> Self {
141 Self {
142 evm,
143 parent_hash,
144 spec_id,
145 receipt_builder: FoundryReceiptBuilder,
146 receipts: Vec::new(),
147 gas_used: 0,
148 blob_gas_used: 0,
149 state_hook: None,
150 }
151 }
152}
153
154impl<E> BlockExecutor for AnvilBlockExecutor<E>
155where
156 E: Evm<
157 DB: StateDB,
158 Tx: FromRecoveredTx<FoundryTxEnvelope> + FromTxWithEncoded<FoundryTxEnvelope>,
159 >,
160{
161 type Transaction = FoundryTxEnvelope;
162 type Receipt = FoundryReceiptEnvelope;
163 type Evm = E;
164 type Result = AnvilTxResult<E::HaltReason>;
165
166 fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> {
167 if self.spec_id >= SpecId::PRAGUE {
169 let result = self
170 .evm
171 .transact_system_call(
172 eip4788::SYSTEM_ADDRESS,
173 eip2935::HISTORY_STORAGE_ADDRESS,
174 Bytes::copy_from_slice(self.parent_hash.as_slice()),
175 )
176 .map_err(BlockExecutionError::other)?;
177
178 if let Some(hook) = &mut self.state_hook {
179 hook.on_state(&result.state);
180 }
181 self.evm.db_mut().commit(result.state);
182 }
183 Ok(())
184 }
185
186 fn execute_transaction_without_commit(
187 &mut self,
188 tx: impl ExecutableTx<Self>,
189 ) -> Result<Self::Result, BlockExecutionError> {
190 let (tx_env, tx) = tx.into_parts();
191
192 let block_available_gas = self.evm.block().gas_limit() - self.gas_used;
193 if tx.tx().gas_limit() > block_available_gas {
194 return Err(BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas {
195 transaction_gas_limit: tx.tx().gas_limit(),
196 block_available_gas,
197 }
198 .into());
199 }
200
201 let sender = *tx.signer();
202
203 let result = self.evm.transact(tx_env).map_err(|err| {
204 let hash = tx.tx().trie_hash();
205 BlockExecutionError::evm(err, hash)
206 })?;
207
208 Ok(AnvilTxResult {
209 inner: EthTxResult {
210 result,
211 blob_gas_used: tx.tx().blob_gas_used().unwrap_or_default(),
212 tx_type: tx.tx().tx_type(),
213 },
214 sender,
215 })
216 }
217
218 fn commit_transaction(&mut self, output: Self::Result) -> GasOutput {
219 let AnvilTxResult {
220 inner: EthTxResult { result: ResultAndState { result, state }, blob_gas_used, tx_type },
221 #[cfg_attr(not(feature = "optimism"), allow(unused_variables))]
222 sender,
223 } = output;
224
225 if let Some(hook) = &mut self.state_hook {
226 hook.on_state(&state);
227 }
228
229 let gas_used = result.tx_gas_used();
230 self.gas_used += gas_used;
231
232 if self.spec_id >= SpecId::CANCUN {
233 self.blob_gas_used = self.blob_gas_used.saturating_add(blob_gas_used);
234 }
235
236 #[cfg(feature = "optimism")]
237 let receipt = if tx_type == FoundryTxType::Deposit {
238 let deposit_nonce = state.get(&sender).map(|acc| acc.info.nonce);
239 let receipt = alloy_consensus::Receipt {
240 status: Eip658Value::Eip658(result.is_success()),
241 cumulative_gas_used: self.gas_used,
242 logs: result.into_logs(),
243 }
244 .with_bloom();
245 FoundryReceiptEnvelope::Deposit(op_alloy_consensus::OpDepositReceiptWithBloom {
246 receipt: op_alloy_consensus::OpDepositReceipt {
247 inner: receipt.receipt,
248 deposit_nonce,
249 deposit_receipt_version: deposit_nonce.map(|_| 1),
250 },
251 logs_bloom: receipt.logs_bloom,
252 })
253 } else {
254 self.receipt_builder.build_receipt(ReceiptBuilderCtx {
255 tx_type,
256 evm: &self.evm,
257 result,
258 state: &state,
259 cumulative_gas_used: self.gas_used,
260 })
261 };
262 #[cfg(not(feature = "optimism"))]
263 let receipt = self.receipt_builder.build_receipt(ReceiptBuilderCtx {
264 tx_type,
265 evm: &self.evm,
266 result,
267 state: &state,
268 cumulative_gas_used: self.gas_used,
269 });
270
271 self.receipts.push(receipt);
272 self.evm.db_mut().commit(state);
273
274 GasOutput::new(gas_used)
275 }
276
277 fn finish(
278 self,
279 ) -> Result<(Self::Evm, BlockExecutionResult<FoundryReceiptEnvelope>), BlockExecutionError>
280 {
281 Ok((
282 self.evm,
283 BlockExecutionResult {
284 receipts: self.receipts,
285 requests: Default::default(),
286 gas_used: self.gas_used,
287 blob_gas_used: self.blob_gas_used,
288 },
289 ))
290 }
291
292 fn evm_mut(&mut self) -> &mut Self::Evm {
293 &mut self.evm
294 }
295
296 fn evm(&self) -> &Self::Evm {
297 &self.evm
298 }
299
300 fn receipts(&self) -> &[FoundryReceiptEnvelope] {
301 &self.receipts
302 }
303}
304
305pub struct ExecutedPoolTransactions<T> {
307 pub included: Vec<Arc<PoolTransaction<T>>>,
309 pub invalid: Vec<Arc<PoolTransaction<T>>>,
311 pub not_yet_valid: Vec<Arc<PoolTransaction<T>>>,
314 pub tx_info: Vec<TransactionInfo>,
316 pub txs: Vec<MaybeImpersonatedTransaction<T>>,
318}
319
320pub struct PoolTxGasConfig {
326 pub disable_block_gas_limit: bool,
327 pub tx_gas_limit_cap: Option<u64>,
328 pub tx_gas_limit_cap_resolved: u64,
329 pub max_blob_gas_per_block: u64,
330 pub is_cancun: bool,
331}
332
333#[allow(clippy::type_complexity)]
338pub fn execute_pool_transactions<B>(
339 executor: &mut B,
340 pool_transactions: &[Arc<PoolTransaction<B::Transaction>>],
341 gas_config: &PoolTxGasConfig,
342 inspector_config: &InspectorTxConfig,
343 cheats: &CheatsManager,
344 validator: &dyn Fn(
345 &PendingTransaction<B::Transaction>,
346 &AccountInfo,
347 ) -> Result<(), InvalidTransactionError>,
348) -> ExecutedPoolTransactions<B::Transaction>
349where
350 B: BlockExecutor<
351 Transaction = FoundryTxEnvelope,
352 Evm: Evm<DB: Database + Debug, Inspector = AnvilInspector>,
353 >,
354 B::Receipt: TxReceipt,
355 <B::Result as TxResult>::HaltReason: Clone + IntoInstructionResult,
356 <B::Evm as Evm>::Tx: FromTxWithEncoded<B::Transaction> + FoundryTransaction,
357{
358 let gas_limit = executor.evm().block().gas_limit();
359
360 let mut included = Vec::new();
361 let mut invalid = Vec::new();
362 let mut not_yet_valid = Vec::new();
363 let mut tx_info: Vec<TransactionInfo> = Vec::new();
364 let mut transactions = Vec::new();
365 let mut blob_gas_used = 0u64;
366
367 for pool_tx in pool_transactions {
368 let pending = &pool_tx.pending_transaction;
369 let sender = *pending.sender();
370 let block_timestamp = executor.evm().block().timestamp();
371
372 if let FoundryTxEnvelope::Tempo(aa_tx) = pending.transaction.as_ref()
373 && let Some(valid_after) = aa_tx.tx().valid_after
374 && U256::from(valid_after.get()) > block_timestamp
375 {
376 trace!(target: "backend", "[{:?}] transaction not valid yet, will retry later", pool_tx.hash());
377 not_yet_valid.push(pool_tx.clone());
378 continue;
379 }
380
381 let account = match executor.evm_mut().db_mut().basic(sender).map(|a| a.unwrap_or_default())
382 {
383 Ok(acc) => acc,
384 Err(err) => {
385 trace!(target: "backend", ?err, "db error for tx {:?}, skipping", pool_tx.hash());
386 continue;
387 }
388 };
389
390 let tx_env =
391 build_tx_env_for_pending::<B::Transaction, <B::Evm as Evm>::Tx>(pending, cheats);
392
393 let cumulative_gas =
395 executor.receipts().last().map(|r| r.cumulative_gas_used()).unwrap_or(0);
396 let max_block_gas = cumulative_gas.saturating_add(pending.transaction.gas_limit());
397 if !gas_config.disable_block_gas_limit && max_block_gas > gas_limit {
398 trace!(target: "backend", tx_gas_limit = %pending.transaction.gas_limit(), ?pool_tx, "block gas limit exhausting, skipping transaction");
399 continue;
400 }
401
402 if gas_config.tx_gas_limit_cap.is_none()
404 && pending.transaction.gas_limit() > gas_config.tx_gas_limit_cap_resolved
405 {
406 trace!(target: "backend", tx_gas_limit = %pending.transaction.gas_limit(), ?pool_tx, "transaction gas limit exhausting, skipping transaction");
407 continue;
408 }
409
410 let tx_blob_gas = pending.transaction.blob_gas_used().unwrap_or(0);
412 if blob_gas_used.saturating_add(tx_blob_gas) > gas_config.max_blob_gas_per_block {
413 trace!(target: "backend", blob_gas = %tx_blob_gas, ?pool_tx, "block blob gas limit exhausting, skipping transaction");
414 continue;
415 }
416
417 if let Err(err) = validator(pending, &account) {
419 warn!(target: "backend", "Skipping invalid tx execution [{:?}] {}", pool_tx.hash(), err);
420 invalid.push(pool_tx.clone());
421 continue;
422 }
423
424 let nonce = account.nonce;
425
426 let recovered = Recovered::new_unchecked(pending.transaction.as_ref().clone(), sender);
427 trace!(target: "backend", "[{:?}] executing", pool_tx.hash());
428 match executor.execute_transaction_without_commit((tx_env, recovered)) {
429 Ok(result) => {
430 let exec_result = result.result().result.clone();
431 let gas_used = result.result().result.tx_gas_used();
432
433 executor.commit_transaction(result);
434
435 let traces =
436 executor.evm_mut().inspector_mut().finish_transaction(inspector_config);
437
438 if gas_config.is_cancun {
439 blob_gas_used = blob_gas_used.saturating_add(tx_blob_gas);
440 }
441
442 let (exit_reason, out, _logs) = match exec_result {
443 ExecutionResult::Success { reason, logs, output, .. } => {
444 (reason.into(), Some(output), logs)
445 }
446 ExecutionResult::Revert { output, .. } => {
447 (InstructionResult::Revert, Some(Output::Call(output)), Vec::new())
448 }
449 ExecutionResult::Halt { reason, .. } => {
450 (reason.into_instruction_result(), None, Vec::new())
451 }
452 };
453
454 if exit_reason == InstructionResult::OutOfGas {
455 warn!(target: "backend", "[{:?}] executed with out of gas", pool_tx.hash());
456 }
457
458 trace!(target: "backend", ?exit_reason, ?gas_used, "[{:?}] executed with out={:?}", pool_tx.hash(), out);
459 trace!(target: "backend::executor", "transacted [{:?}], result: {:?} gas {}", pool_tx.hash(), exit_reason, gas_used);
460
461 let contract_address = pending.transaction.to().is_none().then(|| {
462 let addr = sender.create(nonce);
463 trace!(target: "backend", "Contract creation tx: computed address {:?}", addr);
464 addr
465 });
466
467 let transaction_index = tx_info.len() as u64;
469 let info = TransactionInfo {
470 transaction_hash: pool_tx.hash(),
471 transaction_index,
472 from: sender,
473 to: pending.transaction.to(),
474 contract_address,
475 traces,
476 exit: exit_reason,
477 out: out.map(Output::into_data),
478 nonce,
479 gas_used,
480 };
481
482 included.push(pool_tx.clone());
483 tx_info.push(info);
484 transactions.push(pending.transaction.clone());
485 }
486 Err(err) => {
487 if err.as_validation().is_some() {
488 warn!(target: "backend", "Skipping invalid tx [{:?}]: {}", pool_tx.hash(), err);
489 invalid.push(pool_tx.clone());
490 } else {
491 trace!(target: "backend", ?err, "tx execution error, skipping {:?}", pool_tx.hash());
492 }
493 }
494 }
495 }
496
497 ExecutedPoolTransactions { included, invalid, not_yet_valid, tx_info, txs: transactions }
498}
499
500pub fn build_tx_env_for_pending<Tx, T>(tx: &PendingTransaction<Tx>, cheats: &CheatsManager) -> T
502where
503 Tx: Transaction + Encodable2718,
504 T: FromTxWithEncoded<Tx> + FoundryTransaction,
505{
506 let encoded = tx.transaction.encoded_2718().into();
507 let mut tx_env: T =
508 FromTxWithEncoded::from_encoded_tx(tx.transaction.as_ref(), *tx.sender(), encoded);
509
510 if let Some(signed_auths) = tx.transaction.authorization_list()
511 && cheats.has_recover_overrides()
512 {
513 let auth_list = tx_env.authorization_list_mut();
514 let cheated_auths = signed_auths
515 .iter()
516 .zip(take(auth_list))
517 .map(|(signed_auth, either_auth)| {
518 either_auth.right_and_then(|recovered_auth| {
519 if recovered_auth.authority().is_none()
520 && let Ok(signature) = signed_auth.signature()
521 && let Some(override_addr) =
522 cheats.get_recover_override(&signature.as_bytes().into())
523 {
524 Either::Right(RecoveredAuthorization::new_unchecked(
525 recovered_auth.into_parts().0,
526 RecoveredAuthority::Valid(override_addr),
527 ))
528 } else {
529 Either::Right(recovered_auth)
530 }
531 })
532 })
533 .collect();
534 *tx_env.authorization_list_mut() = cheated_auths;
535 }
536
537 tx_env
538}