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