Skip to main content

foundry_evm_core/evm/
block_context.rs

1use alloy_consensus::BlockHeader;
2use alloy_evm::FromRecoveredTx;
3use alloy_network::{BlockResponse, TransactionResponse};
4use alloy_provider::Provider;
5use alloy_rpc_types::{BlockNumberOrTag, BlockTransactions};
6use eyre::{Result, WrapErr};
7use foundry_evm_networks::NetworkConfigs;
8
9use super::{BlockResponseFor, ChainFor, FoundryEvmNetwork, TxEnvFor};
10use crate::FoundryChain;
11
12/// Transaction metadata for an exact block and its two ancestors.
13#[derive(Clone, Debug)]
14pub struct BlockContext<FEN: FoundryEvmNetwork> {
15    grandparent: Vec<TxEnvFor<FEN>>,
16    parent: Vec<TxEnvFor<FEN>>,
17    current: Vec<TxEnvFor<FEN>>,
18}
19
20impl<FEN: FoundryEvmNetwork> BlockContext<FEN> {
21    /// Creates block context from grandparent, parent, and current block transactions.
22    pub const fn new(
23        grandparent: Vec<TxEnvFor<FEN>>,
24        parent: Vec<TxEnvFor<FEN>>,
25        current: Vec<TxEnvFor<FEN>>,
26    ) -> Self {
27        Self { grandparent, parent, current }
28    }
29
30    /// Fetches all transaction bodies needed to replay transactions in `block` exactly.
31    pub async fn fetch<P: Provider<FEN::Network>>(
32        provider: &P,
33        block: &BlockResponseFor<FEN>,
34    ) -> Result<Self> {
35        let current = transaction_envs::<FEN>(block)?;
36        let parent = fetch_parent::<FEN, P>(provider, block).await?;
37        let grandparent = if let Some(parent) = &parent {
38            fetch_parent::<FEN, P>(provider, parent).await?
39        } else {
40            None
41        };
42
43        Ok(Self::new(
44            grandparent.as_ref().map(transaction_envs::<FEN>).transpose()?.unwrap_or_default(),
45            parent.as_ref().map(transaction_envs::<FEN>).transpose()?.unwrap_or_default(),
46            current,
47        ))
48    }
49
50    /// Builds context for the transaction at `index` in the current block.
51    pub fn transaction(&self, index: usize) -> ChainFor<FEN> {
52        ChainFor::<FEN>::for_block(&self.grandparent, &self.parent, &self.current, index)
53    }
54
55    /// Returns a cursor positioned immediately before `index` in the current block.
56    pub fn before_transaction(mut self, index: usize) -> Result<Self> {
57        if index > self.current.len() {
58            eyre::bail!(
59                "transaction index {index} exceeds block transaction count {}",
60                self.current.len()
61            );
62        }
63        self.current.truncate(index);
64        Ok(self)
65    }
66
67    /// Returns a cursor positioned at the start of a child block.
68    pub fn into_child(mut self) -> Self {
69        self.grandparent = std::mem::take(&mut self.parent);
70        self.parent = std::mem::take(&mut self.current);
71        self
72    }
73
74    /// Builds context for the next transaction at the cursor's current block position.
75    pub fn next_transaction(&self, tx: &TxEnvFor<FEN>) -> ChainFor<FEN> {
76        let mut current = self.current.clone();
77        let index = current.len();
78        current.push(tx.clone());
79        ChainFor::<FEN>::for_block(&self.grandparent, &self.parent, &current, index)
80    }
81
82    /// Records a committed transaction at the cursor's current block position.
83    pub fn record_transaction(&mut self, tx: TxEnvFor<FEN>) {
84        self.current.push(tx);
85    }
86
87    /// Advances the cursor to the start of the next block.
88    pub fn advance_block(&mut self) {
89        self.grandparent = std::mem::take(&mut self.parent);
90        self.parent = std::mem::take(&mut self.current);
91    }
92}
93
94/// Builds context for a synthetic transaction executed on top of `block_number`.
95pub async fn context_for_child_transaction<FEN, P>(
96    provider: &P,
97    block_number: u64,
98    tx: &TxEnvFor<FEN>,
99    networks: NetworkConfigs,
100) -> Result<ChainFor<FEN>>
101where
102    FEN: FoundryEvmNetwork,
103    P: Provider<FEN::Network>,
104{
105    if !networks.is_monad() {
106        return Ok(ChainFor::<FEN>::for_transaction(tx));
107    }
108
109    let block = provider
110        .get_block(BlockNumberOrTag::Number(block_number).into())
111        .full()
112        .await?
113        .ok_or_else(|| eyre::eyre!("block {block_number} not found while building EVM context"))?;
114    let parent = fetch_parent::<FEN, P>(provider, &block).await?;
115    let current = transaction_envs::<FEN>(&block)?;
116    let parent = parent.as_ref().map(transaction_envs::<FEN>).transpose()?.unwrap_or_default();
117
118    Ok(BlockContext::<FEN>::new(Vec::new(), parent, current).into_child().next_transaction(tx))
119}
120
121async fn fetch_parent<FEN, P>(
122    provider: &P,
123    block: &BlockResponseFor<FEN>,
124) -> Result<Option<BlockResponseFor<FEN>>>
125where
126    FEN: FoundryEvmNetwork,
127    P: Provider<FEN::Network>,
128{
129    let parent_hash = block.header().parent_hash();
130    if parent_hash.is_zero() {
131        return Ok(None);
132    }
133
134    provider
135        .get_block_by_hash(parent_hash)
136        .full()
137        .await
138        .wrap_err_with(|| format!("failed to fetch ancestor block {parent_hash}"))?
139        .map(Some)
140        .ok_or_else(|| eyre::eyre!("ancestor block {parent_hash} not found"))
141}
142
143fn transaction_envs<FEN: FoundryEvmNetwork>(
144    block: &BlockResponseFor<FEN>,
145) -> Result<Vec<TxEnvFor<FEN>>> {
146    let BlockTransactions::Full(transactions) = block.transactions() else {
147        eyre::bail!("block {} does not contain full transactions", block.header().number());
148    };
149    Ok(transactions
150        .iter()
151        .map(|tx| TxEnvFor::<FEN>::from_recovered_tx(tx.as_ref(), tx.from()))
152        .collect())
153}