signet_bundle/call/
driver.rs

1use crate::{SignetCallBundle, SignetCallBundleResponse};
2use alloy::{consensus::TxEnvelope, primitives::U256};
3use signet_evm::{DriveBundleResult, EvmNeedsTx, EvmTransacted, SignetInspector, SignetLayered};
4use std::fmt::Debug;
5use tracing::{debug_span, instrument, Level};
6use trevm::{
7    helpers::Ctx,
8    revm::{context::result::EVMError, Database, DatabaseCommit, Inspector},
9    trevm_bail, trevm_ensure, trevm_try, BundleDriver, BundleError,
10};
11
12/// A call bundle driver for the Signet EVM.
13///
14/// This type allows for the simulation of a [`SignetCallBundle`], outputting
15/// the results of the simulation in a [`SignetCallBundleResponse`].
16#[derive(Debug)]
17pub struct SignetBundleDriver<'a> {
18    /// The bundle to drive.
19    bundle: &'a SignetCallBundle,
20    /// The accumulated results of the bundle, if applicable.
21    response: SignetCallBundleResponse,
22}
23
24impl<'a> From<&'a SignetCallBundle> for SignetBundleDriver<'a> {
25    fn from(bundle: &'a SignetCallBundle) -> Self {
26        Self::new(bundle)
27    }
28}
29
30impl<'a> SignetBundleDriver<'a> {
31    /// Create a new bundle driver with the given bundle and response.
32    pub fn new(bundle: &'a SignetCallBundle) -> Self {
33        Self { bundle, response: Default::default() }
34    }
35}
36
37impl SignetBundleDriver<'_> {
38    /// Get a reference to the bundle.
39    pub const fn bundle(&self) -> &SignetCallBundle {
40        self.bundle
41    }
42
43    /// Get a reference to the response.
44    pub const fn response(&self) -> &SignetCallBundleResponse {
45        &self.response
46    }
47
48    /// Take the response from the bundle driver. This consumes
49    pub fn into_response(self) -> SignetCallBundleResponse {
50        self.response
51    }
52
53    /// Clear the driver, resetting the response.
54    pub fn clear(&mut self) -> SignetCallBundleResponse {
55        std::mem::take(&mut self.response)
56    }
57
58    /// Check the aggregate fills, accept the result, accumulate the transaction
59    /// details into the response.
60    fn accept_and_accumulate<Db, Insp>(
61        &mut self,
62        trevm: EvmTransacted<Db, Insp>,
63        tx: &TxEnvelope,
64        pre_sim_coinbase_balance: &mut U256,
65        basefee: u64,
66    ) -> DriveBundleResult<Self, Db, Insp>
67    where
68        Db: Database + DatabaseCommit,
69        Insp: Inspector<Ctx<Db>>,
70    {
71        let beneficiary = trevm.beneficiary();
72
73        let (execution_result, mut trevm) = trevm.accept();
74
75        // Get the post simulation coinbase balance
76        let post_sim_coinbase_balance = trevm_try!(
77            trevm
78                .try_read_balance(beneficiary)
79                .map_err(EVMError::Database)
80                .map_err(BundleError::from),
81            trevm
82        );
83
84        // Calculate the coinbase diff
85        let coinbase_diff = post_sim_coinbase_balance.saturating_sub(*pre_sim_coinbase_balance);
86
87        // Accumulate the transaction
88        trevm_try!(
89            self.response.accumulate_tx(tx, coinbase_diff, basefee, execution_result),
90            trevm
91        );
92
93        // update the coinbase balance
94        *pre_sim_coinbase_balance = post_sim_coinbase_balance;
95
96        Ok(trevm)
97    }
98}
99
100// [`BundleDriver`] Implementation for [`SignetCallBundle`].
101// This is useful mainly for the `signet_simBundle` endpoint,
102// which is used to simulate a signet bundle while respecting aggregate fills.
103impl<Db, Insp> BundleDriver<Db, SignetLayered<Insp>> for SignetBundleDriver<'_>
104where
105    Db: Database + DatabaseCommit,
106    Insp: Inspector<Ctx<Db>>,
107{
108    type Error = BundleError<Db>;
109
110    #[instrument(skip_all, level = Level::DEBUG)]
111    fn run_bundle(&mut self, trevm: EvmNeedsTx<Db, Insp>) -> DriveBundleResult<Self, Db, Insp> {
112        // convenience binding to make usage later less verbose
113        let bundle = &self.bundle.bundle;
114
115        // Ensure that the bundle has transactions
116        trevm_ensure!(!bundle.txs.is_empty(), trevm, BundleError::BundleEmpty);
117
118        // Check if the block we're in is valid for this bundle. Both must match
119        trevm_ensure!(
120            trevm.block_number() == bundle.block_number,
121            trevm,
122            BundleError::BlockNumberMismatch
123        );
124        // Set the state block number this simulation was based on
125        self.response.state_block_number = trevm.block_number();
126
127        // Check if the state block number is valid (not 0, and not a tag)
128        trevm_ensure!(
129            bundle.state_block_number.is_number()
130                && bundle.state_block_number.as_number().unwrap_or_default() != 0,
131            trevm,
132            BundleError::BlockNumberMismatch
133        );
134
135        // Decode and validate the transactions in the bundle
136        let txs = trevm_try!(self.bundle.decode_and_validate_txs(), trevm);
137
138        trevm.try_with_block(self.bundle, |mut trevm| {
139            // Get the coinbase and basefee from the block
140            // NB: Do not move these outside the `try_with_block` closure, as
141            // they may be rewritten by the bundle
142            let coinbase = trevm.beneficiary();
143            let basefee = trevm.block().basefee;
144
145            // Cache the pre simulation coinbase balance, so we can use it to calculate the coinbase diff after every tx simulated.
146            let initial_coinbase_balance = trevm_try!(
147                trevm
148                    .try_read_balance(coinbase)
149                    .map_err(EVMError::Database)
150                    .map_err(BundleError::from),
151                trevm
152            );
153
154            // Stack vars to keep track of the coinbase balance across txs.
155            let mut pre_sim_coinbase_balance = initial_coinbase_balance;
156
157            let span = debug_span!("bundle loop", count = txs.len()).entered();
158            for (idx, tx) in txs.iter().enumerate() {
159                let _span = debug_span!("tx loop", tx = %tx.tx_hash(), idx).entered();
160                let run_result = trevm.run_tx(tx);
161
162                let transacted_trevm = run_result.map_err(|e| e.map_err(Into::into))?;
163
164                // Set the trevm instance to the committed one
165                trevm = self.accept_and_accumulate(
166                    transacted_trevm,
167                    tx,
168                    &mut pre_sim_coinbase_balance,
169                    basefee,
170                )?;
171            }
172            drop(span);
173
174            // Accumulate the total results
175            self.response.coinbase_diff =
176                pre_sim_coinbase_balance.saturating_sub(initial_coinbase_balance);
177            self.response.eth_sent_to_coinbase =
178                self.response.coinbase_diff.saturating_sub(self.response.gas_fees);
179            self.response.bundle_gas_price = self
180                .response
181                .coinbase_diff
182                .checked_div(U256::from(self.response.total_gas_used))
183                .unwrap_or_default();
184            self.response.bundle_hash = self.bundle.bundle_hash();
185
186            // Taking these clears the order detector
187            let (orders, fills) =
188                trevm.inner_mut_unchecked().inspector.as_mut_detector().take_aggregates();
189            self.response.orders = orders;
190            self.response.fills = fills;
191
192            // return the final state
193            Ok(trevm)
194        })
195    }
196
197    fn post_bundle(&mut self, _trevm: &EvmNeedsTx<Db, Insp>) -> Result<(), Self::Error> {
198        Ok(())
199    }
200}