signet_bundle/send/
driver.rs

1use crate::send::SignetEthBundle;
2use alloy::{hex, primitives::U256};
3use signet_evm::{DriveBundleResult, EvmErrored, EvmNeedsTx, SignetInspector, SignetLayered};
4use signet_types::{AggregateFills, AggregateOrders, MarketError, SignedPermitError};
5use std::borrow::Cow;
6use tracing::{debug, error};
7use trevm::{
8    helpers::Ctx,
9    inspectors::{Layered, TimeLimit},
10    revm::{
11        context::result::EVMError, inspector::InspectorEvmTr, Database, DatabaseCommit, Inspector,
12    },
13    trevm_bail, trevm_ensure, trevm_try, BundleDriver, BundleError,
14};
15
16/// Inspector used in the impl of [`BundleDriver`] for
17/// [`SignetEthBundleDriver`].
18pub type SignetEthBundleInsp<I> = Layered<TimeLimit, I>;
19
20/// The output of the [`SignetEthBundleDriver`].
21#[derive(Debug)]
22pub struct DriverOutput<Db, Insp>
23where
24    Db: Database,
25    Insp: Inspector<Ctx<Db>>,
26{
27    /// The host evm used to run the bundle.
28    pub host_evm: Option<signet_evm::EvmNeedsTx<Db, Insp>>,
29
30    /// Total gas used by this bundle during execution.
31    pub total_gas_used: u64,
32
33    /// Total host gas used by this bundle during execution.
34    pub total_host_gas_used: u64,
35
36    /// Beneficiary balance increase during execution.
37    pub beneficiary_balance_increase: U256,
38
39    /// Running aggregate of fills during execution.
40    pub bundle_fills: AggregateFills,
41
42    /// Running aggregate of orders during execution.
43    pub bundle_orders: AggregateOrders,
44}
45
46impl<Db, Insp> DriverOutput<Db, Insp>
47where
48    Db: Database,
49    Insp: Inspector<Ctx<Db>>,
50{
51    /// Increase the total gas used by the given amount.
52    pub const fn use_gas(&mut self, gas: u64) {
53        self.total_gas_used = self.total_gas_used.saturating_add(gas);
54    }
55
56    /// Increase the total host gas used by the given amount.
57    pub const fn use_host_gas(&mut self, gas: u64) {
58        self.total_host_gas_used = self.total_host_gas_used.saturating_add(gas);
59    }
60
61    /// Absorb fills and orders into the running totals.
62    pub fn absorb(&mut self, fills: &AggregateFills, orders: &AggregateOrders) {
63        self.bundle_fills.absorb(fills);
64        self.bundle_orders.absorb(orders);
65    }
66
67    /// Record an increase in the beneficiary balance.
68    pub const fn record_beneficiary_increase(&mut self, increase: U256) {
69        self.beneficiary_balance_increase =
70            self.beneficiary_balance_increase.saturating_add(increase);
71    }
72}
73
74/// Errors while running a [`SignetEthBundle`] on the EVM.
75#[derive(thiserror::Error)]
76pub enum SignetEthBundleError<Db: Database> {
77    /// Bundle error.
78    #[error(transparent)]
79    Bundle(#[from] BundleError<Db>),
80
81    /// SignedPermitError.
82    #[error(transparent)]
83    SignetPermit(#[from] SignedPermitError),
84
85    /// Contract error.
86    #[error(transparent)]
87    Contract(#[from] alloy::contract::Error),
88
89    /// Market error.
90    #[error(transparent)]
91    Market(#[from] MarketError),
92
93    /// Host simulation error.
94    #[error("{0}")]
95    HostSimulation(&'static str),
96}
97
98impl<Db: Database> core::fmt::Debug for SignetEthBundleError<Db> {
99    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100        match self {
101            SignetEthBundleError::Bundle(bundle_error) => {
102                f.debug_tuple("BundleError").field(bundle_error).finish()
103            }
104            SignetEthBundleError::SignetPermit(signed_order_error) => {
105                f.debug_tuple("SignedPermitError").field(signed_order_error).finish()
106            }
107            SignetEthBundleError::Contract(contract_error) => {
108                f.debug_tuple("ContractError").field(contract_error).finish()
109            }
110            SignetEthBundleError::Market(market_error) => {
111                f.debug_tuple("MarketError").field(market_error).finish()
112            }
113            SignetEthBundleError::HostSimulation(msg) => {
114                f.debug_tuple("HostSimulationError").field(msg).finish()
115            }
116        }
117    }
118}
119
120impl<Db: Database> From<EVMError<Db::Error>> for SignetEthBundleError<Db> {
121    fn from(err: EVMError<Db::Error>) -> Self {
122        Self::Bundle(BundleError::from(err))
123    }
124}
125
126/// Driver for applying a Signet Ethereum bundle to an EVM.
127#[derive(Debug)]
128pub struct SignetEthBundleDriver<'a, 'b, Db, Insp>
129where
130    Db: Database,
131    Insp: Inspector<Ctx<Db>>,
132{
133    /// The bundle to apply.
134    bundle: &'a SignetEthBundle,
135
136    /// Reference to the fill state to check against.
137    pub fill_state: Cow<'b, AggregateFills>,
138
139    /// Execution deadline for this bundle. This limits the total WALLCLOCK
140    /// time spent simulating the bundle.
141    deadline: std::time::Instant,
142
143    // -- Accumulated outputs below here--
144    output: DriverOutput<Db, Insp>,
145}
146
147impl<'a, 'b, Db, Insp> SignetEthBundleDriver<'a, 'b, Db, Insp>
148where
149    Db: Database,
150    Insp: Inspector<Ctx<Db>>,
151{
152    /// Creates a new [`SignetEthBundleDriver`] with the given bundle and
153    /// response.
154    pub fn new(
155        bundle: &'a SignetEthBundle,
156        host_evm: signet_evm::EvmNeedsTx<Db, Insp>,
157        deadline: std::time::Instant,
158    ) -> Self {
159        Self::new_with_fill_state(bundle, host_evm, deadline, Default::default())
160    }
161
162    /// Creates a new [`SignetEthBundleDriver`] with the given bundle,
163    /// response, and aggregate fills.
164    ///
165    /// This is useful for testing, and for combined host-rollup simulation.
166    pub fn new_with_fill_state(
167        bundle: &'a SignetEthBundle,
168        host_evm: signet_evm::EvmNeedsTx<Db, Insp>,
169        deadline: std::time::Instant,
170        fill_state: Cow<'b, AggregateFills>,
171    ) -> Self {
172        Self {
173            bundle,
174            fill_state,
175            deadline,
176            output: DriverOutput {
177                host_evm: Some(host_evm),
178                total_gas_used: 0,
179                total_host_gas_used: 0,
180                beneficiary_balance_increase: U256::ZERO,
181                bundle_fills: AggregateFills::default(),
182                bundle_orders: AggregateOrders::default(),
183            },
184        }
185    }
186
187    /// Get a reference to the bundle.
188    pub const fn bundle(&self) -> &SignetEthBundle {
189        self.bundle
190    }
191
192    /// Get the deadline for this driver.
193    pub const fn deadline(&self) -> std::time::Instant {
194        self.deadline
195    }
196
197    /// Get the total gas used by this driver during tx execution.
198    pub const fn total_gas_used(&self) -> u64 {
199        self.output.total_gas_used
200    }
201
202    /// Get the beneficiary balance increase for this driver during tx execution.
203    pub const fn beneficiary_balance_increase(&self) -> U256 {
204        self.output.beneficiary_balance_increase
205    }
206
207    /// Take the aggregate orders and fills from this driver.
208    pub fn into_outputs(self) -> DriverOutput<Db, Insp> {
209        self.output
210    }
211}
212
213impl<RuDb, HostDb, RuInsp, HostInsp> BundleDriver<RuDb, SignetLayered<Layered<TimeLimit, RuInsp>>>
214    for SignetEthBundleDriver<'_, '_, HostDb, HostInsp>
215where
216    RuDb: Database + DatabaseCommit,
217    RuInsp: Inspector<Ctx<RuDb>>,
218    HostDb: Database + DatabaseCommit,
219    HostInsp: Inspector<Ctx<HostDb>>,
220{
221    type Error = SignetEthBundleError<RuDb>;
222
223    fn run_bundle(
224        &mut self,
225        mut trevm: EvmNeedsTx<RuDb, SignetEthBundleInsp<RuInsp>>,
226    ) -> DriveBundleResult<Self, RuDb, SignetEthBundleInsp<RuInsp>> {
227        let bundle = &self.bundle.bundle;
228        // -- STATELESS CHECKS --
229
230        // Ensure that the bundle has transactions
231        trevm_ensure!(!bundle.txs.is_empty(), trevm, BundleError::BundleEmpty.into());
232
233        // Check if the block we're in is valid for this bundle. Both must match
234        trevm_ensure!(
235            trevm.block_number().to::<u64>() == bundle.block_number,
236            trevm,
237            BundleError::BlockNumberMismatch.into()
238        );
239
240        // Check if the state block number is valid (not 0, and not a tag)
241        let timestamp = trevm.block_timestamp();
242        trevm_ensure!(
243            self.bundle.is_valid_at_timestamp(timestamp.to()),
244            trevm,
245            BundleError::TimestampOutOfRange.into()
246        );
247
248        // Decode and validate the transactions in the bundle
249        let host_txs = trevm_try!(self.bundle.decode_and_validate_host_txs(), trevm);
250        let txs = trevm_try!(self.bundle.decode_and_validate_txs(), trevm);
251
252        // -- STATEFUL ACTIONS --
253
254        // Get the beneficiary address and its initial balance
255        let beneficiary = trevm.beneficiary();
256        let inital_beneficiary_balance =
257            trevm_try!(trevm.try_read_balance(beneficiary).map_err(EVMError::Database), trevm);
258
259        // -- HOST PORTION --
260
261        // We simply run all host transactions first, accumulating their state
262        // changes into the host_evm's state. If any reverts, we error out the
263        // simulation.
264        for tx in host_txs.into_iter() {
265            self.output.host_evm = Some(trevm_try!(
266                self.output
267                    .host_evm
268                    .take()
269                    .expect("host_evm missing")
270                    .run_tx(&tx)
271                    .and_then(|mut htrevm| {
272                        let result = htrevm.result();
273                        if let Some(output) = result.output()  {
274                            if !result.is_success() {
275                                debug!(output = hex::encode(output), "host transaction reverted");
276                            }
277                        }
278
279                        trevm_ensure!(
280                            result.is_success(),
281                            htrevm,
282                            EVMError::Custom("host transaction reverted".to_string())
283                        );
284
285                        // Accumulate gas used
286                        self.output.use_host_gas(result.gas_used());
287
288                        // The host fills go in the bundle fills.
289                        let host_fills = htrevm
290                            .inner_mut_unchecked()
291                            .inspector
292                            .as_mut_detector()
293                            .take_aggregates()
294                            .0;
295                        self.output.bundle_fills.absorb(&host_fills);
296
297                        Ok(htrevm.accept_state())
298                    })
299                    .map_err(|err| {
300                        error!(err = %err.error(), err_dbg = ?err.error(), "error while running host transaction");
301                        SignetEthBundleError::HostSimulation("host simulation error")
302                    }),
303                trevm
304            ));
305        }
306
307        // -- ROLLUP PORTION --
308        for tx in txs.into_iter() {
309            let _span = tracing::debug_span!("bundle_tx_loop", tx_hash = %tx.hash()).entered();
310
311            // Update the inner deadline.
312            let limit = trevm.inner_mut_unchecked().ctx_inspector().1.outer_mut().outer_mut();
313            *limit = TimeLimit::new(self.deadline - std::time::Instant::now());
314
315            let tx_hash = tx.hash();
316
317            // Temporary rebinding of trevm within each loop iteration.
318            // The type of t is `EvmTransacted`, while the type of trevm is
319            // `EvmNeedsTx`.
320            let mut t = trevm.run_tx(&tx).map_err(EvmErrored::err_into).inspect_err(
321                |err| error!(err = %err.error(), "error while running rollup transaction"),
322            )?;
323
324            // Check the result of the transaction.
325            let result = t.result();
326            let gas_used = result.gas_used();
327
328            // EVM Execution succeeded.
329            // We now check if the orders are valid with the bundle's fills
330            // state. If not, and the tx is not marked as revertible by the
331            // bundle, we error our simulation.
332            if result.is_success() {
333                let (tx_fills, tx_orders) =
334                    t.inner_mut_unchecked().inspector.as_mut_detector().take_aggregates();
335
336                // These clones are inefficient. We can optimize later if
337                // needed.
338                let mut candidate_fills = self.output.bundle_fills.clone();
339                let mut candidate_orders = self.output.bundle_orders.clone();
340
341                // The candidate is the updated
342                candidate_fills.absorb(&tx_fills);
343                candidate_orders.absorb(&tx_orders);
344
345                // Then we check that the fills are sufficient against the
346                // provided fill state. This does nothing on error.
347                if self.fill_state.check_ru_tx_events(&candidate_fills, &candidate_orders).is_err()
348                {
349                    if self.bundle.reverting_tx_hashes().contains(tx_hash) {
350                        debug!("transaction marked as revertible, reverting");
351                        trevm = t.reject();
352                        continue;
353                    } else {
354                        debug!("transaction dropped due to insufficient fills, not marked as revertible");
355                        return Err(t.errored(BundleError::BundleReverted.into()));
356                    }
357                }
358
359                // Now we accept the fills and order candidates
360                self.output.bundle_fills = candidate_fills;
361                self.output.bundle_orders = candidate_orders;
362            } else {
363                // EVM Execution did not succeed.
364                // If not success, we are in a revert or halt. If the tx is
365                // not marked as revertible by the bundle, we error our
366                // simulation.
367                if !self.bundle.reverting_tx_hashes().contains(tx_hash) {
368                    debug!("transaction reverted, not marked as revertible");
369                    return Err(t.errored(BundleError::BundleReverted.into()));
370                }
371            }
372
373            // If we did not shortcut return/continue, we accept the state
374            // changes from this transaction.
375            self.output.use_gas(gas_used);
376            trevm = t.accept_state()
377        }
378
379        // -- CLEANUP --
380
381        let beneficiary_balance =
382            trevm_try!(trevm.try_read_balance(beneficiary).map_err(EVMError::Database), trevm);
383
384        self.output.record_beneficiary_increase(
385            beneficiary_balance.saturating_sub(inital_beneficiary_balance),
386        );
387
388        Ok(trevm)
389    }
390
391    fn post_bundle(
392        &mut self,
393        _trevm: &EvmNeedsTx<RuDb, SignetEthBundleInsp<RuInsp>>,
394    ) -> Result<(), Self::Error> {
395        Ok(())
396    }
397}