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, OnStateHook, StateChangePreBlockSource, StateChangeSource, StateDB, TxResult,
21 },
22 eth::{
23 EthTxResult,
24 receipt_builder::{ReceiptBuilder, ReceiptBuilderCtx},
25 },
26};
27use alloy_primitives::{Address, B256, Bytes};
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 FoundryTxType::Deposit => {
70 unreachable!("deposit receipts are built in commit_transaction")
71 }
72 FoundryTxType::Tempo => FoundryReceiptEnvelope::Tempo(receipt),
73 }
74 }
75}
76
77#[derive(Debug)]
81pub struct AnvilTxResult<H> {
82 pub inner: EthTxResult<H, FoundryTxType>,
83 pub sender: Address,
84}
85
86impl<H> TxResult for AnvilTxResult<H> {
87 type HaltReason = H;
88
89 fn result(&self) -> &ResultAndState<Self::HaltReason> {
90 self.inner.result()
91 }
92}
93
94pub struct AnvilBlockExecutor<E> {
100 evm: E,
102 parent_hash: B256,
104 spec_id: SpecId,
106 receipt_builder: FoundryReceiptBuilder,
108 receipts: Vec<FoundryReceiptEnvelope>,
110 gas_used: u64,
112 blob_gas_used: u64,
114 state_hook: Option<Box<dyn OnStateHook>>,
116}
117
118impl<E: fmt::Debug> fmt::Debug for AnvilBlockExecutor<E> {
119 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120 f.debug_struct("AnvilBlockExecutor")
121 .field("evm", &self.evm)
122 .field("parent_hash", &self.parent_hash)
123 .field("spec_id", &self.spec_id)
124 .field("gas_used", &self.gas_used)
125 .field("blob_gas_used", &self.blob_gas_used)
126 .field("receipts", &self.receipts.len())
127 .finish_non_exhaustive()
128 }
129}
130
131impl<E> AnvilBlockExecutor<E> {
132 pub fn new(evm: E, parent_hash: B256, spec_id: SpecId) -> Self {
134 Self {
135 evm,
136 parent_hash,
137 spec_id,
138 receipt_builder: FoundryReceiptBuilder,
139 receipts: Vec::new(),
140 gas_used: 0,
141 blob_gas_used: 0,
142 state_hook: None,
143 }
144 }
145}
146
147impl<E> BlockExecutor for AnvilBlockExecutor<E>
148where
149 E: Evm<
150 DB: StateDB,
151 Tx: FromRecoveredTx<FoundryTxEnvelope> + FromTxWithEncoded<FoundryTxEnvelope>,
152 >,
153{
154 type Transaction = FoundryTxEnvelope;
155 type Receipt = FoundryReceiptEnvelope;
156 type Evm = E;
157 type Result = AnvilTxResult<E::HaltReason>;
158
159 fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> {
160 if self.spec_id >= SpecId::PRAGUE {
162 let result = self
163 .evm
164 .transact_system_call(
165 eip4788::SYSTEM_ADDRESS,
166 eip2935::HISTORY_STORAGE_ADDRESS,
167 Bytes::copy_from_slice(self.parent_hash.as_slice()),
168 )
169 .map_err(BlockExecutionError::other)?;
170
171 if let Some(hook) = &mut self.state_hook {
172 hook.on_state(
173 StateChangeSource::PreBlock(StateChangePreBlockSource::BlockHashesContract),
174 &result.state,
175 );
176 }
177 self.evm.db_mut().commit(result.state);
178 }
179 Ok(())
180 }
181
182 fn execute_transaction_without_commit(
183 &mut self,
184 tx: impl ExecutableTx<Self>,
185 ) -> Result<Self::Result, BlockExecutionError> {
186 let (tx_env, tx) = tx.into_parts();
187
188 let block_available_gas = self.evm.block().gas_limit() - self.gas_used;
189 if tx.tx().gas_limit() > block_available_gas {
190 return Err(BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas {
191 transaction_gas_limit: tx.tx().gas_limit(),
192 block_available_gas,
193 }
194 .into());
195 }
196
197 let sender = *tx.signer();
198
199 let result = self.evm.transact(tx_env).map_err(|err| {
200 let hash = tx.tx().trie_hash();
201 BlockExecutionError::evm(err, hash)
202 })?;
203
204 Ok(AnvilTxResult {
205 inner: EthTxResult {
206 result,
207 blob_gas_used: tx.tx().blob_gas_used().unwrap_or_default(),
208 tx_type: tx.tx().tx_type(),
209 },
210 sender,
211 })
212 }
213
214 fn commit_transaction(&mut self, output: Self::Result) -> Result<u64, BlockExecutionError> {
215 let AnvilTxResult {
216 inner: EthTxResult { result: ResultAndState { result, state }, blob_gas_used, tx_type },
217 sender,
218 } = output;
219
220 if let Some(hook) = &mut self.state_hook {
221 hook.on_state(StateChangeSource::Transaction(self.receipts.len()), &state);
222 }
223
224 let gas_used = result.gas_used();
225 self.gas_used += gas_used;
226
227 if self.spec_id >= SpecId::CANCUN {
228 self.blob_gas_used = self.blob_gas_used.saturating_add(blob_gas_used);
229 }
230
231 let receipt = if tx_type == FoundryTxType::Deposit {
232 let deposit_nonce = state.get(&sender).map(|acc| acc.info.nonce);
233 let receipt = alloy_consensus::Receipt {
234 status: Eip658Value::Eip658(result.is_success()),
235 cumulative_gas_used: self.gas_used,
236 logs: result.into_logs(),
237 }
238 .with_bloom();
239 FoundryReceiptEnvelope::Deposit(op_alloy_consensus::OpDepositReceiptWithBloom {
240 receipt: op_alloy_consensus::OpDepositReceipt {
241 inner: receipt.receipt,
242 deposit_nonce,
243 deposit_receipt_version: deposit_nonce.map(|_| 1),
244 },
245 logs_bloom: receipt.logs_bloom,
246 })
247 } else {
248 self.receipt_builder.build_receipt(ReceiptBuilderCtx {
249 tx_type,
250 evm: &self.evm,
251 result,
252 state: &state,
253 cumulative_gas_used: self.gas_used,
254 })
255 };
256
257 self.receipts.push(receipt);
258 self.evm.db_mut().commit(state);
259
260 Ok(gas_used)
261 }
262
263 fn finish(
264 self,
265 ) -> Result<(Self::Evm, BlockExecutionResult<FoundryReceiptEnvelope>), BlockExecutionError>
266 {
267 Ok((
268 self.evm,
269 BlockExecutionResult {
270 receipts: self.receipts,
271 requests: Default::default(),
272 gas_used: self.gas_used,
273 blob_gas_used: self.blob_gas_used,
274 },
275 ))
276 }
277
278 fn set_state_hook(&mut self, hook: Option<Box<dyn OnStateHook>>) {
279 self.state_hook = hook;
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<Evm: Evm<DB: Database + Debug, Inspector = AnvilInspector>>,
341 B::Transaction: Transaction + Encodable2718 + Clone,
342 B::Receipt: TxReceipt,
343 <B::Result as TxResult>::HaltReason: Clone + IntoInstructionResult,
344 <B::Evm as Evm>::Tx: FromTxWithEncoded<B::Transaction> + FoundryTransaction,
345{
346 let gas_limit = executor.evm().block().gas_limit();
347
348 let mut included = Vec::new();
349 let mut invalid = Vec::new();
350 let mut not_yet_valid = Vec::new();
351 let mut tx_info: Vec<TransactionInfo> = Vec::new();
352 let mut transactions = Vec::new();
353 let mut blob_gas_used = 0u64;
354
355 for pool_tx in pool_transactions {
356 let pending = &pool_tx.pending_transaction;
357 let sender = *pending.sender();
358
359 let account = match executor.evm_mut().db_mut().basic(sender).map(|a| a.unwrap_or_default())
360 {
361 Ok(acc) => acc,
362 Err(err) => {
363 trace!(target: "backend", ?err, "db error for tx {:?}, skipping", pool_tx.hash());
364 continue;
365 }
366 };
367
368 let tx_env =
369 build_tx_env_for_pending::<B::Transaction, <B::Evm as Evm>::Tx>(pending, cheats);
370
371 let cumulative_gas =
373 executor.receipts().last().map(|r| r.cumulative_gas_used()).unwrap_or(0);
374 let max_block_gas = cumulative_gas.saturating_add(pending.transaction.gas_limit());
375 if !gas_config.disable_block_gas_limit && max_block_gas > gas_limit {
376 trace!(target: "backend", tx_gas_limit = %pending.transaction.gas_limit(), ?pool_tx, "block gas limit exhausting, skipping transaction");
377 continue;
378 }
379
380 if gas_config.tx_gas_limit_cap.is_none()
382 && pending.transaction.gas_limit() > gas_config.tx_gas_limit_cap_resolved
383 {
384 trace!(target: "backend", tx_gas_limit = %pending.transaction.gas_limit(), ?pool_tx, "transaction gas limit exhausting, skipping transaction");
385 continue;
386 }
387
388 let tx_blob_gas = pending.transaction.blob_gas_used().unwrap_or(0);
390 if blob_gas_used.saturating_add(tx_blob_gas) > gas_config.max_blob_gas_per_block {
391 trace!(target: "backend", blob_gas = %tx_blob_gas, ?pool_tx, "block blob gas limit exhausting, skipping transaction");
392 continue;
393 }
394
395 if let Err(err) = validator(pending, &account) {
397 warn!(target: "backend", "Skipping invalid tx execution [{:?}] {}", pool_tx.hash(), err);
398 invalid.push(pool_tx.clone());
399 continue;
400 }
401
402 let nonce = account.nonce;
403
404 let recovered = Recovered::new_unchecked(pending.transaction.as_ref().clone(), sender);
405 trace!(target: "backend", "[{:?}] executing", pool_tx.hash());
406 match executor.execute_transaction_without_commit((tx_env, recovered)) {
407 Ok(result) => {
408 let exec_result = result.result().result.clone();
409 let gas_used = result.result().result.gas_used();
410
411 executor.commit_transaction(result).expect("commit failed");
412
413 let traces =
414 executor.evm_mut().inspector_mut().finish_transaction(inspector_config);
415
416 if gas_config.is_cancun {
417 blob_gas_used = blob_gas_used.saturating_add(tx_blob_gas);
418 }
419
420 let (exit_reason, out, _logs) = match exec_result {
421 ExecutionResult::Success { reason, logs, output, .. } => {
422 (reason.into(), Some(output), logs)
423 }
424 ExecutionResult::Revert { output, .. } => {
425 (InstructionResult::Revert, Some(Output::Call(output)), Vec::new())
426 }
427 ExecutionResult::Halt { reason, .. } => {
428 (reason.into_instruction_result(), None, Vec::new())
429 }
430 };
431
432 if exit_reason == InstructionResult::OutOfGas {
433 warn!(target: "backend", "[{:?}] executed with out of gas", pool_tx.hash());
434 }
435
436 trace!(target: "backend", ?exit_reason, ?gas_used, "[{:?}] executed with out={:?}", pool_tx.hash(), out);
437 trace!(target: "backend::executor", "transacted [{:?}], result: {:?} gas {}", pool_tx.hash(), exit_reason, gas_used);
438
439 let contract_address = pending.transaction.to().is_none().then(|| {
440 let addr = sender.create(nonce);
441 trace!(target: "backend", "Contract creation tx: computed address {:?}", addr);
442 addr
443 });
444
445 let transaction_index = tx_info.len() as u64;
447 let info = TransactionInfo {
448 transaction_hash: pool_tx.hash(),
449 transaction_index,
450 from: sender,
451 to: pending.transaction.to(),
452 contract_address,
453 traces,
454 exit: exit_reason,
455 out: out.map(Output::into_data),
456 nonce,
457 gas_used,
458 };
459
460 included.push(pool_tx.clone());
461 tx_info.push(info);
462 transactions.push(pending.transaction.clone());
463 }
464 Err(err) => {
465 let err_str = err.to_string();
466 if err_str.contains("not valid yet") {
467 trace!(target: "backend", "[{:?}] transaction not valid yet, will retry later", pool_tx.hash());
468 not_yet_valid.push(pool_tx.clone());
469 } else if err.as_validation().is_some() {
470 warn!(target: "backend", "Skipping invalid tx [{:?}]: {}", pool_tx.hash(), err);
471 invalid.push(pool_tx.clone());
472 } else {
473 trace!(target: "backend", ?err, "tx execution error, skipping {:?}", pool_tx.hash());
474 }
475 }
476 }
477 }
478
479 ExecutedPoolTransactions { included, invalid, not_yet_valid, tx_info, txs: transactions }
480}
481
482pub fn build_tx_env_for_pending<Tx, T>(tx: &PendingTransaction<Tx>, cheats: &CheatsManager) -> T
484where
485 Tx: Transaction + Encodable2718,
486 T: FromTxWithEncoded<Tx> + FoundryTransaction,
487{
488 let encoded = tx.transaction.encoded_2718().into();
489 let mut tx_env: T =
490 FromTxWithEncoded::from_encoded_tx(tx.transaction.as_ref(), *tx.sender(), encoded);
491
492 if let Some(signed_auths) = tx.transaction.authorization_list()
493 && cheats.has_recover_overrides()
494 {
495 let auth_list = tx_env.authorization_list_mut();
496 let cheated_auths = signed_auths
497 .iter()
498 .zip(take(auth_list))
499 .map(|(signed_auth, either_auth)| {
500 either_auth.right_and_then(|recovered_auth| {
501 if recovered_auth.authority().is_none()
502 && let Ok(signature) = signed_auth.signature()
503 && let Some(override_addr) =
504 cheats.get_recover_override(&signature.as_bytes().into())
505 {
506 Either::Right(RecoveredAuthorization::new_unchecked(
507 recovered_auth.into_parts().0,
508 RecoveredAuthority::Valid(override_addr),
509 ))
510 } else {
511 Either::Right(recovered_auth)
512 }
513 })
514 })
515 .collect();
516 *tx_env.authorization_list_mut() = cheated_auths;
517 }
518
519 tx_env
520}