Skip to main content

signet_test_utils/
orders.rs

1//! Mock implementations and test helpers for signet-orders traits.
2use crate::users::TEST_SIGNERS;
3use alloy::{
4    network::{Ethereum, EthereumWallet, TransactionBuilder},
5    primitives::{Address, U256},
6    providers::{fillers::FillerControlFlow, Provider, ProviderBuilder, RootProvider, SendableTx},
7    signers::{local::PrivateKeySigner, Signer},
8    transports::{mock::Asserter, TransportResult},
9};
10use core::convert::Infallible;
11use futures_util::{stream, Stream};
12use signet_bundle::SignetEthBundle;
13use signet_constants::{test_utils::TEST_SYS, SignetSystemConstants};
14use signet_orders::{
15    BundleSubmitter, FillSubmitter, OrderSource, OrderSubmitter, OrdersAndFills, TxBuilder,
16};
17use signet_types::{SignedOrder, UnsignedOrder};
18use signet_zenith::RollupOrders::Output;
19use std::sync::{
20    atomic::{AtomicU64, Ordering},
21    Arc, Mutex,
22};
23
24/// A mock [`OrderSubmitter`] that captures submitted orders.
25#[derive(Debug, Clone, Default)]
26pub struct MockOrderSubmitter {
27    orders: Arc<Mutex<Vec<SignedOrder>>>,
28}
29
30impl MockOrderSubmitter {
31    /// Create a new mock order submitter.
32    pub fn new() -> Self {
33        Self::default()
34    }
35
36    /// Get all submitted orders.
37    pub fn submitted_orders(&self) -> Vec<SignedOrder> {
38        self.orders.lock().unwrap().clone()
39    }
40}
41
42impl OrderSubmitter for MockOrderSubmitter {
43    type Error = Infallible;
44
45    async fn submit_order(&self, order: SignedOrder) -> Result<(), Self::Error> {
46        self.orders.lock().unwrap().push(order);
47        Ok(())
48    }
49}
50
51/// A mock [`OrderSource`] that returns a predefined list of orders.
52#[derive(Debug, Clone)]
53pub struct MockOrderSource {
54    orders: Vec<SignedOrder>,
55}
56
57impl MockOrderSource {
58    /// Create a new mock order source with the given orders.
59    pub fn new(orders: Vec<SignedOrder>) -> Self {
60        Self { orders }
61    }
62
63    /// Create an empty mock order source.
64    pub fn empty() -> Self {
65        Self { orders: vec![] }
66    }
67}
68
69impl OrderSource for MockOrderSource {
70    type Error = Infallible;
71
72    fn get_orders(&self) -> impl Stream<Item = Result<SignedOrder, Self::Error>> + Send {
73        stream::iter(self.orders.clone().into_iter().map(Ok))
74    }
75}
76
77/// A mock [`BundleSubmitter`] that captures submitted bundles.
78#[derive(Debug, Clone, Default)]
79pub struct MockBundleSubmitter {
80    bundles: Arc<Mutex<Vec<SignetEthBundle>>>,
81}
82
83impl MockBundleSubmitter {
84    /// Create a new mock bundle submitter.
85    pub fn new() -> Self {
86        Self::default()
87    }
88
89    /// Get all submitted bundles.
90    pub fn submitted_bundles(&self) -> Vec<SignetEthBundle> {
91        self.bundles.lock().unwrap().clone()
92    }
93}
94
95impl BundleSubmitter for MockBundleSubmitter {
96    type Response = ();
97    type Error = Infallible;
98
99    async fn submit_bundle(&self, bundle: SignetEthBundle) -> Result<(), Self::Error> {
100        self.bundles.lock().unwrap().push(bundle);
101        Ok(())
102    }
103}
104
105/// A mock [`TxBuilder`] that pre-fills transactions with gas and nonce values.
106///
107/// This avoids the complexity of concurrent filler RPC calls by setting values locally:
108/// - Nonce: incremented locally for each transaction
109/// - Gas limit: fixed at 100,000
110/// - Max fee per gas: fixed at 1 gwei
111/// - Max priority fee per gas: fixed at 1 gwei
112///
113/// The inner provider is only used for signing and `get_block_number` calls.
114#[derive(Clone)]
115pub struct MockTxBuilder<P> {
116    inner: P,
117    asserter: Asserter,
118    nonce: Arc<AtomicU64>,
119}
120
121impl<P> MockTxBuilder<P> {
122    /// Create a new mock transaction builder wrapping the given provider.
123    fn new(inner: P, asserter: Asserter) -> Self {
124        Self { inner, asserter, nonce: Arc::new(AtomicU64::new(0)) }
125    }
126
127    /// Get a reference to the asserter for pushing mock responses.
128    pub fn asserter(&self) -> &Asserter {
129        &self.asserter
130    }
131}
132
133impl<P: Provider<Ethereum>> Provider<Ethereum> for MockTxBuilder<P> {
134    fn root(&self) -> &RootProvider<Ethereum> {
135        self.inner.root()
136    }
137}
138
139impl<P> TxBuilder<Ethereum> for MockTxBuilder<P>
140where
141    P: TxBuilder<Ethereum>,
142{
143    async fn fill(
144        &self,
145        mut tx: <Ethereum as alloy::network::Network>::TransactionRequest,
146    ) -> TransportResult<SendableTx<Ethereum>> {
147        // Pre-fill gas and nonce if they're not already set, so fillers don't need to make RPC
148        // calls.
149        if tx.nonce.is_none() {
150            let nonce = self.nonce.fetch_add(1, Ordering::SeqCst);
151            tx = tx.with_nonce(nonce);
152        }
153        if tx.gas.is_none() {
154            tx = tx.with_gas_limit(100_000);
155        }
156        if tx.max_fee_per_gas.is_none() {
157            tx = tx.with_max_fee_per_gas(1_000_000_000); // 1 gwei
158        }
159        if tx.max_priority_fee_per_gas.is_none() {
160            tx = tx.with_max_priority_fee_per_gas(1_000_000_000); // 1 gwei
161        }
162        self.inner.fill(tx).await
163    }
164
165    fn status(
166        &self,
167        tx: &<Ethereum as alloy::network::Network>::TransactionRequest,
168    ) -> FillerControlFlow {
169        self.inner.status(tx)
170    }
171}
172
173/// Create a mock [`TxBuilder`] for testing transaction building without a real network.
174///
175/// Pre-fills transactions with gas and nonce values locally, so the only RPC call needed
176/// is `get_block_number`.
177pub fn mock_tx_builder(
178    wallet: PrivateKeySigner,
179    chain_id: u64,
180) -> MockTxBuilder<impl TxBuilder<Ethereum>> {
181    let asserter = Asserter::new();
182    let inner = ProviderBuilder::new()
183        .with_chain_id(chain_id)
184        .wallet(EthereumWallet::new(wallet))
185        .connect_mocked_client(asserter.clone());
186    MockTxBuilder::new(inner, asserter)
187}
188
189/// A mock [`FillSubmitter`] that captures submitted fills.
190#[derive(Debug, Clone, Default)]
191pub struct MockFillSubmitter {
192    submissions: Arc<Mutex<Vec<OrdersAndFills>>>,
193}
194
195impl MockFillSubmitter {
196    /// Create a new mock fill submitter.
197    pub fn new() -> Self {
198        Self::default()
199    }
200
201    /// Get all submitted fills.
202    pub fn submissions(&self) -> Vec<OrdersAndFills> {
203        self.submissions.lock().unwrap().clone()
204    }
205}
206
207impl FillSubmitter for MockFillSubmitter {
208    type Response = ();
209    type Error = Infallible;
210
211    async fn submit_fills(
212        &self,
213        orders_and_fills: OrdersAndFills,
214        _target_block_count: u8,
215    ) -> Result<(), Self::Error> {
216        self.submissions.lock().unwrap().push(orders_and_fills);
217        Ok(())
218    }
219}
220
221/// Builder for creating test [`SignedOrder`] instances.
222#[derive(Debug, Clone)]
223pub struct TestOrderBuilder {
224    constants: SignetSystemConstants,
225    inputs: Vec<(Address, U256)>,
226    outputs: Vec<Output>,
227    nonce: Option<u64>,
228}
229
230impl Default for TestOrderBuilder {
231    fn default() -> Self {
232        Self::new()
233    }
234}
235
236impl TestOrderBuilder {
237    /// Create a new test order builder using [`TEST_SYS`] system constants.
238    pub fn new() -> Self {
239        Self { constants: TEST_SYS, inputs: vec![], outputs: vec![], nonce: None }
240    }
241
242    /// Use the provided system constants.
243    pub fn with_constants(mut self, constants: SignetSystemConstants) -> Self {
244        self.constants = constants;
245        self
246    }
247
248    /// Append a new input to the collection of inputs.
249    pub fn with_input(mut self, token: Address, amount: U256) -> Self {
250        self.inputs.push((token, amount));
251        self
252    }
253
254    /// Append a new output to the collection of outputs.
255    pub fn with_output(
256        mut self,
257        token: Address,
258        amount: U256,
259        recipient: Address,
260        chain_id: u64,
261    ) -> Self {
262        self.outputs.push(Output { token, amount, recipient, chainId: chain_id as u32 });
263        self
264    }
265
266    /// Set the nonce.
267    pub fn with_nonce(mut self, nonce: u64) -> Self {
268        self.nonce = Some(nonce);
269        self
270    }
271
272    /// Sign and build the order.
273    pub async fn sign<S: Signer>(self, signer: &S) -> SignedOrder {
274        let mut unsigned = UnsignedOrder::new();
275
276        for (token, amount) in self.inputs {
277            unsigned = unsigned.with_input(token, amount);
278        }
279
280        for output in self.outputs {
281            unsigned = unsigned.with_raw_output(output);
282        }
283
284        if let Some(nonce) = self.nonce {
285            unsigned = unsigned.with_nonce(nonce);
286        }
287
288        unsigned = unsigned.with_chain(&self.constants);
289
290        unsigned.sign(signer).await.expect("signing should succeed with test signer")
291    }
292}
293
294/// Create dummy orders for testing: one with host chain output, one with rollup chain output.
295pub async fn default_test_orders() -> Vec<SignedOrder> {
296    let signer = &TEST_SIGNERS[0];
297
298    let host_order = TestOrderBuilder::new()
299        .with_input(Address::repeat_byte(0x11), U256::from(1000))
300        .with_output(
301            Address::repeat_byte(0x22),
302            U256::from(500),
303            signer.address(),
304            TEST_SYS.host_chain_id(),
305        )
306        .with_nonce(1)
307        .sign(signer)
308        .await;
309
310    let rollup_order = TestOrderBuilder::new()
311        .with_input(Address::repeat_byte(0x11), U256::from(2000))
312        .with_output(
313            Address::repeat_byte(0x33),
314            U256::from(1000),
315            signer.address(),
316            TEST_SYS.ru_chain_id(),
317        )
318        .with_nonce(2)
319        .sign(signer)
320        .await;
321
322    vec![host_order, rollup_order]
323}