signet_bundle/send/
driver.rs

1use crate::send::SignetEthBundle;
2use alloy::primitives::U256;
3use signet_evm::{DriveBundleResult, EvmNeedsTx, SignetLayered};
4use signet_types::SignedPermitError;
5use trevm::{
6    helpers::Ctx,
7    inspectors::{Layered, TimeLimit},
8    revm::{
9        context::result::{EVMError, ExecutionResult, HaltReason},
10        inspector::InspectorEvmTr,
11        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/// Errors while running a [`SignetEthBundle`] on the EVM.
21#[derive(thiserror::Error)]
22pub enum SignetEthBundleError<Db: Database> {
23    /// Bundle error.
24    #[error(transparent)]
25    BundleError(#[from] BundleError<Db>),
26
27    /// SignedPermitError.
28    #[error(transparent)]
29    SignedPermitError(#[from] SignedPermitError),
30
31    /// Contract error.
32    #[error(transparent)]
33    ContractError(#[from] alloy::contract::Error),
34}
35
36impl<Db: Database> core::fmt::Debug for SignetEthBundleError<Db> {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        match self {
39            SignetEthBundleError::BundleError(bundle_error) => {
40                f.debug_tuple("BundleError").field(bundle_error).finish()
41            }
42            SignetEthBundleError::SignedPermitError(signed_order_error) => {
43                f.debug_tuple("SignedPermitError").field(signed_order_error).finish()
44            }
45            SignetEthBundleError::ContractError(contract_error) => {
46                f.debug_tuple("ContractError").field(contract_error).finish()
47            }
48        }
49    }
50}
51
52impl<Db: Database> From<EVMError<Db::Error>> for SignetEthBundleError<Db> {
53    fn from(err: EVMError<Db::Error>) -> Self {
54        Self::BundleError(BundleError::from(err))
55    }
56}
57
58/// Driver for applying a Signet Ethereum bundle to an EVM.
59#[derive(Debug, Clone)]
60pub struct SignetEthBundleDriver<'a> {
61    bundle: &'a SignetEthBundle,
62    deadline: std::time::Instant,
63
64    total_gas_used: u64,
65    beneficiary_balance_increase: U256,
66}
67
68impl<'a> SignetEthBundleDriver<'a> {
69    /// Creates a new [`SignetEthBundleDriver`] with the given bundle and
70    /// response.
71    pub const fn new(bundle: &'a SignetEthBundle, deadline: std::time::Instant) -> Self {
72        Self { bundle, deadline, total_gas_used: 0, beneficiary_balance_increase: U256::ZERO }
73    }
74
75    /// Get a reference to the bundle.
76    pub const fn bundle(&self) -> &SignetEthBundle {
77        self.bundle
78    }
79
80    /// Get the deadline for this driver.
81    pub const fn deadline(&self) -> std::time::Instant {
82        self.deadline
83    }
84
85    /// Get the total gas used by this driver during tx execution.
86    pub const fn total_gas_used(&self) -> u64 {
87        self.total_gas_used
88    }
89
90    /// Get the beneficiary balance increase for this driver during tx execution.
91    pub const fn beneficiary_balance_increase(&self) -> U256 {
92        self.beneficiary_balance_increase
93    }
94}
95
96impl<Db, Insp> BundleDriver<Db, SignetLayered<Layered<TimeLimit, Insp>>>
97    for SignetEthBundleDriver<'_>
98where
99    Db: Database + DatabaseCommit,
100    Insp: Inspector<Ctx<Db>>,
101{
102    type Error = SignetEthBundleError<Db>;
103
104    fn run_bundle(
105        &mut self,
106        mut trevm: EvmNeedsTx<Db, SignetEthBundleInsp<Insp>>,
107    ) -> DriveBundleResult<Self, Db, SignetEthBundleInsp<Insp>> {
108        let bundle = &self.bundle.bundle;
109
110        // Reset the total gas used and beneficiary balance increase
111        // to 0 before running the bundle.
112        self.total_gas_used = 0;
113        self.beneficiary_balance_increase = U256::ZERO;
114
115        // Get the beneficiary address and its initial balance
116        let beneficiary = trevm.beneficiary();
117        let inital_beneficiary_balance =
118            trevm_try!(trevm.try_read_balance(beneficiary).map_err(EVMError::Database), trevm);
119
120        // Ensure that the bundle has transactions
121        trevm_ensure!(!bundle.txs.is_empty(), trevm, BundleError::BundleEmpty.into());
122
123        // Check if the block we're in is valid for this bundle. Both must match
124        trevm_ensure!(
125            trevm.block_number().to::<u64>() == bundle.block_number,
126            trevm,
127            BundleError::BlockNumberMismatch.into()
128        );
129
130        // Check if the state block number is valid (not 0, and not a tag)
131        let timestamp = trevm.block_timestamp();
132        trevm_ensure!(
133            self.bundle.is_valid_at_timestamp(timestamp.to()),
134            trevm,
135            BundleError::TimestampOutOfRange.into()
136        );
137
138        // Check that the `SignedFill` is valid at the timestamp.
139        if self.bundle().validate_fills_offchain(timestamp.to()).is_err() {
140            return Err(trevm.errored(BundleError::BundleReverted.into()));
141        }
142
143        // Decode and validate the transactions in the bundle
144        let txs = trevm_try!(self.bundle.decode_and_validate_txs(), trevm);
145
146        for tx in txs.into_iter() {
147            // Update the inner deadline.
148            let limit = trevm.inner_mut_unchecked().ctx_inspector().1.outer_mut().outer_mut();
149            *limit = TimeLimit::new(self.deadline - std::time::Instant::now());
150
151            let tx_hash = tx.hash();
152
153            trevm = match trevm.run_tx(&tx) {
154                Ok(trevm) => {
155                    // Check if the transaction was reverted or halted
156                    let result = trevm.result();
157
158                    match result {
159                        ExecutionResult::Success { gas_used, .. } => {
160                            self.total_gas_used = self.total_gas_used.saturating_add(*gas_used);
161                        }
162                        // `CallTooDeep` represents the timelimit being reached
163                        ExecutionResult::Halt { reason, .. }
164                            if *reason == HaltReason::CallTooDeep =>
165                        {
166                            return Err(trevm.errored(BundleError::BundleReverted.into()));
167                        }
168                        ExecutionResult::Halt { gas_used, .. }
169                        | ExecutionResult::Revert { gas_used, .. } => {
170                            if !self.bundle.reverting_tx_hashes().contains(tx_hash) {
171                                return Err(trevm.errored(BundleError::BundleReverted.into()));
172                            }
173                            self.total_gas_used = self.total_gas_used.saturating_add(*gas_used);
174                        }
175                    }
176                    trevm.accept_state()
177                }
178                Err(err) => return Err(err.err_into()),
179            };
180        }
181
182        let beneficiary_balance =
183            trevm_try!(trevm.try_read_balance(beneficiary).map_err(EVMError::Database), trevm);
184
185        self.beneficiary_balance_increase =
186            beneficiary_balance.saturating_sub(inital_beneficiary_balance);
187
188        Ok(trevm)
189    }
190
191    fn post_bundle(
192        &mut self,
193        _trevm: &EvmNeedsTx<Db, SignetEthBundleInsp<Insp>>,
194    ) -> Result<(), Self::Error> {
195        Ok(())
196    }
197}