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, 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}
122
123impl<E: fmt::Debug> fmt::Debug for AnvilBlockExecutor<E> {
124 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125 f.debug_struct("AnvilBlockExecutor")
126 .field("evm", &self.evm)
127 .field("parent_hash", &self.parent_hash)
128 .field("spec_id", &self.spec_id)
129 .field("gas_used", &self.gas_used)
130 .field("blob_gas_used", &self.blob_gas_used)
131 .field("receipts", &self.receipts.len())
132 .finish_non_exhaustive()
133 }
134}
135
136impl<E> AnvilBlockExecutor<E> {
137 pub const fn new(evm: E, parent_hash: B256, spec_id: SpecId) -> Self {
139 Self {
140 evm,
141 parent_hash,
142 spec_id,
143 receipt_builder: FoundryReceiptBuilder,
144 receipts: Vec::new(),
145 gas_used: 0,
146 blob_gas_used: 0,
147 }
148 }
149}
150
151impl<E> BlockExecutor for AnvilBlockExecutor<E>
152where
153 E: Evm<
154 DB: StateDB,
155 Tx: FromRecoveredTx<FoundryTxEnvelope> + FromTxWithEncoded<FoundryTxEnvelope>,
156 >,
157{
158 type Transaction = FoundryTxEnvelope;
159 type Receipt = FoundryReceiptEnvelope;
160 type Evm = E;
161 type Result = AnvilTxResult<E::HaltReason>;
162
163 fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> {
164 if self.spec_id >= SpecId::PRAGUE {
166 let result = self
167 .evm
168 .transact_system_call(
169 eip4788::SYSTEM_ADDRESS,
170 eip2935::HISTORY_STORAGE_ADDRESS,
171 Bytes::copy_from_slice(self.parent_hash.as_slice()),
172 )
173 .map_err(BlockExecutionError::other)?;
174
175 self.evm.db_mut().commit(result.state);
176 }
177 Ok(())
178 }
179
180 fn execute_transaction_without_commit(
181 &mut self,
182 tx: impl ExecutableTx<Self>,
183 ) -> Result<Self::Result, BlockExecutionError> {
184 let (tx_env, tx) = tx.into_parts();
185
186 let block_available_gas = self.evm.block().gas_limit() - self.gas_used;
187 if tx.tx().gas_limit() > block_available_gas {
188 return Err(BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas {
189 transaction_gas_limit: tx.tx().gas_limit(),
190 block_available_gas,
191 }
192 .into());
193 }
194
195 let sender = *tx.signer();
196
197 let result = self.evm.transact(tx_env).map_err(|err| {
198 let hash = tx.tx().trie_hash();
199 BlockExecutionError::evm(err, hash)
200 })?;
201
202 Ok(AnvilTxResult {
203 inner: EthTxResult {
204 result,
205 blob_gas_used: tx.tx().blob_gas_used().unwrap_or_default(),
206 tx_type: tx.tx().tx_type(),
207 },
208 sender,
209 })
210 }
211
212 fn commit_transaction(&mut self, output: Self::Result) -> GasOutput {
213 let AnvilTxResult {
214 inner: EthTxResult { result: ResultAndState { result, state }, blob_gas_used, tx_type },
215 #[cfg_attr(not(feature = "optimism"), allow(unused_variables))]
216 sender,
217 } = output;
218
219 let gas_used = result.tx_gas_used();
220 self.gas_used += gas_used;
221
222 if self.spec_id >= SpecId::CANCUN {
223 self.blob_gas_used = self.blob_gas_used.saturating_add(blob_gas_used);
224 }
225
226 #[cfg(feature = "optimism")]
227 let receipt = if tx_type == FoundryTxType::Deposit {
228 let deposit_nonce = state.get(&sender).map(|acc| acc.info.nonce);
229 let receipt = alloy_consensus::Receipt {
230 status: Eip658Value::Eip658(result.is_success()),
231 cumulative_gas_used: self.gas_used,
232 logs: result.into_logs(),
233 }
234 .with_bloom();
235 FoundryReceiptEnvelope::Deposit(op_alloy_consensus::OpDepositReceiptWithBloom {
236 receipt: op_alloy_consensus::OpDepositReceipt {
237 inner: receipt.receipt,
238 deposit_nonce,
239 deposit_receipt_version: deposit_nonce.map(|_| 1),
240 },
241 logs_bloom: receipt.logs_bloom,
242 })
243 } else {
244 self.receipt_builder.build_receipt(ReceiptBuilderCtx {
245 tx_type,
246 evm: &self.evm,
247 result,
248 state: &state,
249 cumulative_gas_used: self.gas_used,
250 })
251 };
252 #[cfg(not(feature = "optimism"))]
253 let receipt = self.receipt_builder.build_receipt(ReceiptBuilderCtx {
254 tx_type,
255 evm: &self.evm,
256 result,
257 state: &state,
258 cumulative_gas_used: self.gas_used,
259 });
260
261 self.receipts.push(receipt);
262 self.evm.db_mut().commit(state);
263
264 GasOutput::new(gas_used)
265 }
266
267 fn finish(
268 self,
269 ) -> Result<(Self::Evm, BlockExecutionResult<FoundryReceiptEnvelope>), BlockExecutionError>
270 {
271 Ok((
272 self.evm,
273 BlockExecutionResult {
274 receipts: self.receipts,
275 requests: Default::default(),
276 gas_used: self.gas_used,
277 blob_gas_used: self.blob_gas_used,
278 },
279 ))
280 }
281
282 fn evm_mut(&mut self) -> &mut Self::Evm {
283 &mut self.evm
284 }
285
286 fn evm(&self) -> &Self::Evm {
287 &self.evm
288 }
289
290 fn receipts(&self) -> &[FoundryReceiptEnvelope] {
291 &self.receipts
292 }
293}
294
295pub struct ExecutedPoolTransactions<T> {
297 pub included: Vec<Arc<PoolTransaction<T>>>,
299 pub invalid: Vec<Arc<PoolTransaction<T>>>,
301 pub not_yet_valid: Vec<Arc<PoolTransaction<T>>>,
304 pub tx_info: Vec<TransactionInfo>,
306 pub txs: Vec<MaybeImpersonatedTransaction<T>>,
308}
309
310pub struct PoolTxGasConfig {
316 pub disable_block_gas_limit: bool,
317 pub tx_gas_limit_cap: Option<u64>,
318 pub tx_gas_limit_cap_resolved: u64,
319 pub max_blob_gas_per_block: u64,
320 pub is_cancun: bool,
321}
322
323#[allow(clippy::type_complexity)]
328pub fn execute_pool_transactions<B>(
329 executor: &mut B,
330 pool_transactions: &[Arc<PoolTransaction<B::Transaction>>],
331 gas_config: &PoolTxGasConfig,
332 inspector_config: &InspectorTxConfig,
333 cheats: &CheatsManager,
334 validator: &dyn Fn(
335 &PendingTransaction<B::Transaction>,
336 &AccountInfo,
337 ) -> Result<(), InvalidTransactionError>,
338) -> ExecutedPoolTransactions<B::Transaction>
339where
340 B: BlockExecutor<
341 Transaction = FoundryTxEnvelope,
342 Evm: Evm<DB: Database + Debug, Inspector = AnvilInspector>,
343 >,
344 B::Receipt: TxReceipt,
345 <B::Result as TxResult>::HaltReason: Clone + IntoInstructionResult,
346 <B::Evm as Evm>::Tx: FromTxWithEncoded<B::Transaction> + FoundryTransaction,
347{
348 let gas_limit = executor.evm().block().gas_limit();
349
350 let mut included = Vec::new();
351 let mut invalid = Vec::new();
352 let mut not_yet_valid = Vec::new();
353 let mut tx_info: Vec<TransactionInfo> = Vec::new();
354 let mut transactions = Vec::new();
355 let mut blob_gas_used = 0u64;
356
357 for pool_tx in pool_transactions {
358 let pending = &pool_tx.pending_transaction;
359 let sender = *pending.sender();
360 let block_timestamp = executor.evm().block().timestamp();
361
362 if let FoundryTxEnvelope::Tempo(aa_tx) = pending.transaction.as_ref()
363 && let Some(valid_after) = aa_tx.tx().valid_after
364 && U256::from(valid_after.get()) > block_timestamp
365 {
366 trace!(target: "backend", "[{:?}] transaction not valid yet, will retry later", pool_tx.hash());
367 not_yet_valid.push(pool_tx.clone());
368 continue;
369 }
370
371 let account = match executor.evm_mut().db_mut().basic(sender).map(|a| a.unwrap_or_default())
372 {
373 Ok(acc) => acc,
374 Err(err) => {
375 trace!(target: "backend", ?err, "db error for tx {:?}, skipping", pool_tx.hash());
376 continue;
377 }
378 };
379
380 let tx_env =
381 build_tx_env_for_pending::<B::Transaction, <B::Evm as Evm>::Tx>(pending, cheats);
382
383 let cumulative_gas =
385 executor.receipts().last().map(|r| r.cumulative_gas_used()).unwrap_or(0);
386 let max_block_gas = cumulative_gas.saturating_add(pending.transaction.gas_limit());
387 if !gas_config.disable_block_gas_limit && max_block_gas > gas_limit {
388 trace!(target: "backend", tx_gas_limit = %pending.transaction.gas_limit(), ?pool_tx, "block gas limit exhausting, skipping transaction");
389 continue;
390 }
391
392 if gas_config.tx_gas_limit_cap.is_none()
394 && pending.transaction.gas_limit() > gas_config.tx_gas_limit_cap_resolved
395 {
396 trace!(target: "backend", tx_gas_limit = %pending.transaction.gas_limit(), ?pool_tx, "transaction gas limit exhausting, skipping transaction");
397 continue;
398 }
399
400 let tx_blob_gas = pending.transaction.blob_gas_used().unwrap_or(0);
402 if blob_gas_used.saturating_add(tx_blob_gas) > gas_config.max_blob_gas_per_block {
403 trace!(target: "backend", blob_gas = %tx_blob_gas, ?pool_tx, "block blob gas limit exhausting, skipping transaction");
404 continue;
405 }
406
407 if let Err(err) = validator(pending, &account) {
409 warn!(target: "backend", "Skipping invalid tx execution [{:?}] {}", pool_tx.hash(), err);
410 invalid.push(pool_tx.clone());
411 continue;
412 }
413
414 let nonce = account.nonce;
415
416 let recovered = Recovered::new_unchecked(pending.transaction.as_ref().clone(), sender);
417 trace!(target: "backend", "[{:?}] executing", pool_tx.hash());
418 match executor.execute_transaction_without_commit((tx_env, recovered)) {
419 Ok(result) => {
420 let exec_result = result.result().result.clone();
421 let gas_used = result.result().result.tx_gas_used();
422
423 executor.commit_transaction(result);
424
425 let traces =
426 executor.evm_mut().inspector_mut().finish_transaction(inspector_config);
427
428 if gas_config.is_cancun {
429 blob_gas_used = blob_gas_used.saturating_add(tx_blob_gas);
430 }
431
432 let (exit_reason, out, _logs) = match exec_result {
433 ExecutionResult::Success { reason, logs, output, .. } => {
434 (reason.into(), Some(output), logs)
435 }
436 ExecutionResult::Revert { output, .. } => {
437 (InstructionResult::Revert, Some(Output::Call(output)), Vec::new())
438 }
439 ExecutionResult::Halt { reason, .. } => {
440 (reason.into_instruction_result(), None, Vec::new())
441 }
442 };
443
444 if exit_reason == InstructionResult::OutOfGas {
445 warn!(target: "backend", "[{:?}] executed with out of gas", pool_tx.hash());
446 }
447
448 trace!(target: "backend", ?exit_reason, ?gas_used, "[{:?}] executed with out={:?}", pool_tx.hash(), out);
449 trace!(target: "backend::executor", "transacted [{:?}], result: {:?} gas {}", pool_tx.hash(), exit_reason, gas_used);
450
451 let contract_address = pending.transaction.to().is_none().then(|| {
452 let addr = sender.create(nonce);
453 trace!(target: "backend", "Contract creation tx: computed address {:?}", addr);
454 addr
455 });
456
457 let transaction_index = tx_info.len() as u64;
459 let info = TransactionInfo {
460 transaction_hash: pool_tx.hash(),
461 transaction_index,
462 from: sender,
463 to: pending.transaction.to(),
464 contract_address,
465 traces,
466 exit: exit_reason,
467 out: out.map(Output::into_data),
468 nonce,
469 gas_used,
470 };
471
472 included.push(pool_tx.clone());
473 tx_info.push(info);
474 transactions.push(pending.transaction.clone());
475 }
476 Err(err) => {
477 if err.as_validation().is_some() {
478 warn!(target: "backend", "Skipping invalid tx [{:?}]: {}", pool_tx.hash(), err);
479 invalid.push(pool_tx.clone());
480 } else {
481 trace!(target: "backend", ?err, "tx execution error, skipping {:?}", pool_tx.hash());
482 }
483 }
484 }
485 }
486
487 ExecutedPoolTransactions { included, invalid, not_yet_valid, tx_info, txs: transactions }
488}
489
490pub fn build_tx_env_for_pending<Tx, T>(tx: &PendingTransaction<Tx>, cheats: &CheatsManager) -> T
492where
493 Tx: Transaction + Encodable2718,
494 T: FromTxWithEncoded<Tx> + FoundryTransaction,
495{
496 let encoded = tx.transaction.encoded_2718().into();
497 let mut tx_env: T =
498 FromTxWithEncoded::from_encoded_tx(tx.transaction.as_ref(), *tx.sender(), encoded);
499
500 if let Some(signed_auths) = tx.transaction.authorization_list()
501 && cheats.has_recover_overrides()
502 {
503 let auth_list = tx_env.authorization_list_mut();
504 let cheated_auths = signed_auths
505 .iter()
506 .zip(take(auth_list))
507 .map(|(signed_auth, either_auth)| {
508 either_auth.right_and_then(|recovered_auth| {
509 if recovered_auth.authority().is_none()
510 && let Ok(signature) = signed_auth.signature()
511 && let Some(override_addr) =
512 cheats.get_recover_override(&signature.as_bytes().into())
513 {
514 Either::Right(RecoveredAuthorization::new_unchecked(
515 recovered_auth.into_parts().0,
516 RecoveredAuthority::Valid(override_addr),
517 ))
518 } else {
519 Either::Right(recovered_auth)
520 }
521 })
522 })
523 .collect();
524 *tx_env.authorization_list_mut() = cheated_auths;
525 }
526
527 tx_env
528}