signet_bundle/send/
driver.rs

1use crate::send::SignetEthBundle;
2use alloy::primitives::U256;
3use signet_evm::{
4    DriveBundleResult, EvmErrored, EvmNeedsTx, EvmTransacted, SignetInspector, SignetLayered,
5};
6use signet_types::{AggregateFills, MarketError, SignedPermitError};
7use tracing::{debug, error};
8use trevm::{
9    helpers::Ctx,
10    inspectors::{Layered, TimeLimit},
11    revm::{
12        context::result::EVMError, inspector::InspectorEvmTr, Database, DatabaseCommit, Inspector,
13    },
14    trevm_bail, trevm_ensure, trevm_try, BundleDriver, BundleError,
15};
16
17/// Inspector used in the impl of [`BundleDriver`] for
18/// [`SignetEthBundleDriver`].
19pub type SignetEthBundleInsp<I> = Layered<TimeLimit, I>;
20
21/// Errors while running a [`SignetEthBundle`] on the EVM.
22#[derive(thiserror::Error)]
23pub enum SignetEthBundleError<Db: Database> {
24    /// Bundle error.
25    #[error(transparent)]
26    BundleError(#[from] BundleError<Db>),
27
28    /// SignedPermitError.
29    #[error(transparent)]
30    SignedPermitError(#[from] SignedPermitError),
31
32    /// Contract error.
33    #[error(transparent)]
34    ContractError(#[from] alloy::contract::Error),
35}
36
37impl<Db: Database> core::fmt::Debug for SignetEthBundleError<Db> {
38    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39        match self {
40            SignetEthBundleError::BundleError(bundle_error) => {
41                f.debug_tuple("BundleError").field(bundle_error).finish()
42            }
43            SignetEthBundleError::SignedPermitError(signed_order_error) => {
44                f.debug_tuple("SignedPermitError").field(signed_order_error).finish()
45            }
46            SignetEthBundleError::ContractError(contract_error) => {
47                f.debug_tuple("ContractError").field(contract_error).finish()
48            }
49        }
50    }
51}
52
53impl<Db: Database> From<EVMError<Db::Error>> for SignetEthBundleError<Db> {
54    fn from(err: EVMError<Db::Error>) -> Self {
55        Self::BundleError(BundleError::from(err))
56    }
57}
58
59/// Driver for applying a Signet Ethereum bundle to an EVM.
60#[derive(Debug, Clone)]
61pub struct SignetEthBundleDriver<'a> {
62    /// The bundle to apply.
63    bundle: &'a SignetEthBundle,
64
65    /// Execution deadline for this bundle. This limits the total WALLCLOCK
66    /// time spent simulating the bundle.
67    deadline: std::time::Instant,
68
69    /// Aggregate fills derived from the bundle's host fills.
70    agg_fills: AggregateFills,
71
72    /// Total gas used by this bundle during execution, an output of the driver.
73    total_gas_used: u64,
74    /// Beneficiary balance increase during execution, an output of the driver.
75    beneficiary_balance_increase: U256,
76}
77
78impl<'a> SignetEthBundleDriver<'a> {
79    /// Creates a new [`SignetEthBundleDriver`] with the given bundle and
80    /// response.
81    pub fn new(
82        bundle: &'a SignetEthBundle,
83        host_chain_id: u64,
84        deadline: std::time::Instant,
85    ) -> Self {
86        let mut agg_fills = AggregateFills::default();
87        if let Some(host_fills) = &bundle.host_fills {
88            agg_fills.add_signed_fill(host_chain_id, host_fills);
89        }
90
91        Self {
92            bundle,
93            deadline,
94            agg_fills,
95            total_gas_used: 0,
96            beneficiary_balance_increase: U256::ZERO,
97        }
98    }
99
100    /// Get a reference to the bundle.
101    pub const fn bundle(&self) -> &SignetEthBundle {
102        self.bundle
103    }
104
105    /// Get the deadline for this driver.
106    pub const fn deadline(&self) -> std::time::Instant {
107        self.deadline
108    }
109
110    /// Get the total gas used by this driver during tx execution.
111    pub const fn total_gas_used(&self) -> u64 {
112        self.total_gas_used
113    }
114
115    /// Get the beneficiary balance increase for this driver during tx execution.
116    pub const fn beneficiary_balance_increase(&self) -> U256 {
117        self.beneficiary_balance_increase
118    }
119
120    /// Get the aggregate fills for this driver.
121    ///
122    /// This may be used to check that the bundle does not overfill, by
123    /// inspecting the agg fills after execution.
124    pub const fn agg_fills(&self) -> &AggregateFills {
125        &self.agg_fills
126    }
127
128    /// Check the [`AggregateFills`], discard if invalid, otherwise accumulate
129    /// payable gas and call [`Self::accept_tx`].
130    ///
131    /// This path is used by
132    /// - [`TransactionSigned`] objects
133    /// - [`Transactor::Transact`] events
134    pub(crate) fn check_fills<Db, Insp>(
135        &mut self,
136        trevm: &mut EvmTransacted<Db, Insp>,
137    ) -> Result<(), MarketError>
138    where
139        Db: Database + DatabaseCommit,
140        Insp: Inspector<Ctx<Db>>,
141    {
142        // Taking these clears the context for reuse.
143        let (agg_orders, agg_fills) =
144            trevm.inner_mut_unchecked().inspector.as_mut_detector().take_aggregates();
145
146        // We check the AggregateFills here, and if it fails, we discard the
147        // transaction outcome and push a failure receipt.
148        self.agg_fills.checked_remove_ru_tx_events(&agg_orders, &agg_fills)
149    }
150}
151
152impl<Db, Insp> BundleDriver<Db, SignetLayered<Layered<TimeLimit, Insp>>>
153    for SignetEthBundleDriver<'_>
154where
155    Db: Database + DatabaseCommit,
156    Insp: Inspector<Ctx<Db>>,
157{
158    type Error = SignetEthBundleError<Db>;
159
160    fn run_bundle(
161        &mut self,
162        mut trevm: EvmNeedsTx<Db, SignetEthBundleInsp<Insp>>,
163    ) -> DriveBundleResult<Self, Db, SignetEthBundleInsp<Insp>> {
164        let bundle = &self.bundle.bundle;
165
166        // Reset the total gas used and beneficiary balance increase
167        // to 0 before running the bundle.
168        self.total_gas_used = 0;
169        self.beneficiary_balance_increase = U256::ZERO;
170
171        // Get the beneficiary address and its initial balance
172        let beneficiary = trevm.beneficiary();
173        let inital_beneficiary_balance =
174            trevm_try!(trevm.try_read_balance(beneficiary).map_err(EVMError::Database), trevm);
175
176        // Ensure that the bundle has transactions
177        trevm_ensure!(!bundle.txs.is_empty(), trevm, BundleError::BundleEmpty.into());
178
179        // Check if the block we're in is valid for this bundle. Both must match
180        trevm_ensure!(
181            trevm.block_number().to::<u64>() == bundle.block_number,
182            trevm,
183            BundleError::BlockNumberMismatch.into()
184        );
185
186        // Check if the state block number is valid (not 0, and not a tag)
187        let timestamp = trevm.block_timestamp();
188        trevm_ensure!(
189            self.bundle.is_valid_at_timestamp(timestamp.to()),
190            trevm,
191            BundleError::TimestampOutOfRange.into()
192        );
193
194        // Check that the `SignedFill` is valid at the timestamp.
195        if self.bundle().validate_fills_offchain(timestamp.to()).is_err() {
196            return Err(trevm.errored(BundleError::BundleReverted.into()));
197        }
198
199        // Decode and validate the transactions in the bundle
200        let txs = trevm_try!(self.bundle.decode_and_validate_txs(), trevm);
201
202        for tx in txs.into_iter() {
203            let _span = tracing::debug_span!("bundle_tx_loop", tx_hash = %tx.hash()).entered();
204
205            // Update the inner deadline.
206            let limit = trevm.inner_mut_unchecked().ctx_inspector().1.outer_mut().outer_mut();
207            *limit = TimeLimit::new(self.deadline - std::time::Instant::now());
208
209            let tx_hash = tx.hash();
210
211            // Temporary rebinding of trevm within each loop iteration.
212            // The type of t is `EvmTransacted`, while the type of trevm is
213            // `EvmNeedsTx`.
214            let mut t = trevm
215                .run_tx(&tx)
216                .map_err(EvmErrored::err_into)
217                .inspect_err(|err| error!(err = %err.error(), "error while running transaction"))?;
218
219            // Check the result of the transaction.
220            let result = t.result();
221
222            let gas_used = result.gas_used();
223
224            // EVM Execution succeeded.
225            // We now check if the orders are valid with the bundle's fills. If
226            // not, and the tx is not marked as revertible by the bundle, we
227            // error our simulation.
228            if result.is_success() {
229                if self.check_fills(&mut t).is_err() {
230                    debug!("transaction dropped due to insufficient fills");
231                    if self.bundle.reverting_tx_hashes().contains(tx_hash) {
232                        trevm = t.reject();
233                        continue;
234                    } else {
235                        return Err(t.errored(BundleError::BundleReverted.into()));
236                    }
237                }
238
239                self.total_gas_used = self.total_gas_used.saturating_add(gas_used);
240            } else {
241                // If not success, we are in a revert or halt. If the tx is
242                // not marked as revertible by the bundle, we error our
243                // simulation.
244                if !self.bundle.reverting_tx_hashes().contains(tx_hash) {
245                    debug!("transaction reverted, not marked as revertible");
246                    return Err(t.errored(BundleError::BundleReverted.into()));
247                }
248                self.total_gas_used = self.total_gas_used.saturating_add(gas_used);
249            }
250
251            // If we did not shortcut return/continue, we accept the state
252            // changes from this transaction.
253            trevm = t.accept_state()
254        }
255
256        let beneficiary_balance =
257            trevm_try!(trevm.try_read_balance(beneficiary).map_err(EVMError::Database), trevm);
258
259        self.beneficiary_balance_increase =
260            beneficiary_balance.saturating_sub(inital_beneficiary_balance);
261
262        Ok(trevm)
263    }
264
265    fn post_bundle(
266        &mut self,
267        _trevm: &EvmNeedsTx<Db, SignetEthBundleInsp<Insp>>,
268    ) -> Result<(), Self::Error> {
269        Ok(())
270    }
271}