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() == 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            timestamp >= bundle.min_timestamp.unwrap_or_default()
134                && timestamp <= bundle.max_timestamp.unwrap_or(u64::MAX),
135            trevm,
136            BundleError::TimestampOutOfRange.into()
137        );
138
139        // Check that the `SignedFill` is valid at the timestamp.
140        if self.bundle().validate_fills_offchain(timestamp).is_err() {
141            return Err(trevm.errored(BundleError::BundleReverted.into()));
142        }
143
144        // Decode and validate the transactions in the bundle
145        let txs = trevm_try!(self.bundle.decode_and_validate_txs(), trevm);
146
147        for tx in txs.into_iter() {
148            // Update the inner deadline.
149            let limit = trevm.inner_mut_unchecked().ctx_inspector().1.outer_mut().outer_mut();
150            *limit = TimeLimit::new(self.deadline - std::time::Instant::now());
151
152            let tx_hash = tx.hash();
153
154            trevm = match trevm.run_tx(&tx) {
155                Ok(trevm) => {
156                    // Check if the transaction was reverted or halted
157                    let result = trevm.result();
158
159                    match result {
160                        ExecutionResult::Success { gas_used, .. } => {
161                            self.total_gas_used = self.total_gas_used.saturating_add(*gas_used);
162                        }
163                        // `CallTooDeep` represents the timelimit being reached
164                        ExecutionResult::Halt { reason, .. }
165                            if *reason == HaltReason::CallTooDeep =>
166                        {
167                            return Err(trevm.errored(BundleError::BundleReverted.into()));
168                        }
169                        ExecutionResult::Halt { gas_used, .. }
170                        | ExecutionResult::Revert { gas_used, .. } => {
171                            if !self.bundle.reverting_tx_hashes().contains(tx_hash) {
172                                return Err(trevm.errored(BundleError::BundleReverted.into()));
173                            }
174                            self.total_gas_used = self.total_gas_used.saturating_add(*gas_used);
175                        }
176                    }
177                    trevm.accept_state()
178                }
179                Err(err) => return Err(err.err_into()),
180            };
181        }
182
183        let beneficiary_balance =
184            trevm_try!(trevm.try_read_balance(beneficiary).map_err(EVMError::Database), trevm);
185
186        self.beneficiary_balance_increase =
187            beneficiary_balance.saturating_sub(inital_beneficiary_balance);
188
189        Ok(trevm)
190    }
191
192    fn post_bundle(
193        &mut self,
194        _trevm: &EvmNeedsTx<Db, SignetEthBundleInsp<Insp>>,
195    ) -> Result<(), Self::Error> {
196        Ok(())
197    }
198}