tempo_precompiles/stablecoin_exchange/
order.rs

1//! Limit order type for the stablecoin DEX.
2//!
3//! This module defines the core `Order` type used in the stablecoin DEX orderbook.
4//! Orders support price-time priority matching, partial fills, and flip orders that
5//! automatically place opposite-side orders when filled.
6
7use crate::{
8    error::TempoPrecompileError,
9    stablecoin_exchange::{IStablecoinExchange, error::OrderError},
10    storage::{Slot, StorageOps, slots::mapping_slot},
11};
12use alloy::primitives::{Address, B256};
13use tempo_precompiles_macros::Storable;
14
15/// Represents an order in the stablecoin DEX orderbook.
16///
17/// This struct matches the Solidity reference implementation in StablecoinExchange.sol.
18///
19/// # Order Types
20/// - **Regular orders**: Orders with `is_flip = false`
21/// - **Flip orders**: Orders with `is_flip = true` that automatically create
22///   a new order on the opposite side when fully filled
23///
24/// # Order Lifecycle
25/// 1. Order is placed via `place()` or `placeFlip()` and added to pending queue
26/// 2. At end of block, pending orders are inserted into the active orderbook
27/// 3. Orders can be filled (fully or partially) by taker orders
28/// 4. Flip orders automatically create a new order on the opposite side when fully filled
29/// 5. Orders can be cancelled, removing them from the book
30///
31/// # Price-Time Priority
32/// Orders are sorted by price (tick), then by insertion time.
33/// The doubly linked list maintains insertion order - orders are added at the tail,
34/// so traversing from head to tail gives price-time priority.
35///
36/// # Onchain Storage
37/// Orders are stored onchain in doubly linked lists organized by tick.
38/// Each tick maintains a FIFO queue of orders using `prev` and `next` pointers.
39#[derive(Debug, Clone, PartialEq, Eq, Storable)]
40pub struct Order {
41    /// Unique identifier for this order
42    pub order_id: u128,
43    /// Address of the user who placed this order
44    pub maker: Address,
45    /// Orderbook key (identifies the trading pair)
46    pub book_key: B256,
47    /// Whether this is a bid (true) or ask (false) order
48    pub is_bid: bool,
49    /// Price tick
50    pub tick: i16,
51    /// Original order amount
52    pub amount: u128,
53    /// Remaining amount to be filled
54    pub remaining: u128,
55    /// Previous order ID in the doubly linked list (0 if head)
56    pub prev: u128,
57    /// Next order ID in the doubly linked list (0 if tail)
58    pub next: u128,
59    /// Whether this is a flip order
60    pub is_flip: bool,
61    /// Tick to flip to when fully filled (for flip orders, 0 for regular orders)
62    /// For bid flips: flip_tick must be > tick
63    /// For ask flips: flip_tick must be < tick
64    pub flip_tick: i16,
65}
66
67// Helper type to easily interact with u128 fields (order_id, prev, next)
68type OrderId = Slot<u128>;
69// Helper type to easily interact with u128 fields (amount, remaining)
70type OrderAmount = Slot<u128>;
71
72impl Order {
73    /// Creates a new order with `prev` and `next` initialized to 0.
74    #[allow(clippy::too_many_arguments)]
75    pub fn new(
76        order_id: u128,
77        maker: Address,
78        book_key: B256,
79        amount: u128,
80        tick: i16,
81        is_bid: bool,
82        is_flip: bool,
83        flip_tick: i16,
84    ) -> Self {
85        Self {
86            order_id,
87            maker,
88            book_key,
89            is_bid,
90            tick,
91            amount,
92            remaining: amount,
93            prev: 0,
94            next: 0,
95            is_flip,
96            flip_tick,
97        }
98    }
99
100    /// Creates a new bid order
101    pub fn new_bid(
102        order_id: u128,
103        maker: Address,
104        book_key: B256,
105        amount: u128,
106        tick: i16,
107    ) -> Self {
108        Self::new(order_id, maker, book_key, amount, tick, true, false, 0)
109    }
110
111    /// Creates a new ask order
112    pub fn new_ask(
113        order_id: u128,
114        maker: Address,
115        book_key: B256,
116        amount: u128,
117        tick: i16,
118    ) -> Self {
119        Self::new(order_id, maker, book_key, amount, tick, false, false, 0)
120    }
121
122    /// Creates a new flip order.
123    ///
124    /// Note: `prev` and `next` are initialized to 0.
125    /// The orderbook will set these when inserting the order into the linked list.
126    ///
127    /// # Errors
128    /// Returns an error if flip_tick constraint is violated:
129    /// - For bids: flip_tick must be > tick
130    /// - For asks: flip_tick must be < tick
131    pub fn new_flip(
132        order_id: u128,
133        maker: Address,
134        book_key: B256,
135        amount: u128,
136        tick: i16,
137        is_bid: bool,
138        flip_tick: i16,
139    ) -> Result<Self, OrderError> {
140        // Validate flip tick constraint
141        if is_bid {
142            if flip_tick <= tick {
143                return Err(OrderError::InvalidBidFlipTick { tick, flip_tick });
144            }
145        } else if flip_tick >= tick {
146            return Err(OrderError::InvalidAskFlipTick { tick, flip_tick });
147        }
148
149        Ok(Self::new(
150            order_id, maker, book_key, amount, tick, is_bid, true, flip_tick,
151        ))
152    }
153
154    /// Update the order's remaining value in storage
155    pub fn update_remaining<S: StorageOps>(
156        storage: &mut S,
157        order_id: u128,
158        new_remaining: u128,
159    ) -> Result<(), TempoPrecompileError> {
160        let order_base_slot = mapping_slot(order_id.to_be_bytes(), super::slots::ORDERS);
161        OrderAmount::new_at_loc(order_base_slot, __packing_order::REMAINING_LOC)
162            .write(storage, new_remaining)?;
163        Ok(())
164    }
165
166    pub fn update_next_order<S: StorageOps>(
167        storage: &mut S,
168        order_id: u128,
169        new_next: u128,
170    ) -> Result<(), TempoPrecompileError> {
171        let order_base_slot = mapping_slot(order_id.to_be_bytes(), super::slots::ORDERS);
172        OrderId::new_at_loc(order_base_slot, __packing_order::NEXT_LOC).write(storage, new_next)?;
173        Ok(())
174    }
175
176    pub fn update_prev_order<S: StorageOps>(
177        storage: &mut S,
178        order_id: u128,
179        new_prev: u128,
180    ) -> Result<(), TempoPrecompileError> {
181        let order_base_slot = mapping_slot(order_id.to_be_bytes(), super::slots::ORDERS);
182        OrderId::new_at_loc(order_base_slot, __packing_order::PREV_LOC).write(storage, new_prev)?;
183        Ok(())
184    }
185
186    /// Returns the order ID.
187    pub fn order_id(&self) -> u128 {
188        self.order_id
189    }
190
191    /// Returns the maker address.
192    pub fn maker(&self) -> Address {
193        self.maker
194    }
195
196    /// Returns the orderbook key.
197    pub fn book_key(&self) -> B256 {
198        self.book_key
199    }
200
201    /// Returns whether this is a bid order.
202    pub fn is_bid(&self) -> bool {
203        self.is_bid
204    }
205
206    /// Returns the original amount.
207    pub fn amount(&self) -> u128 {
208        self.amount
209    }
210
211    /// Returns the remaining amount.
212    pub fn remaining(&self) -> u128 {
213        self.remaining
214    }
215
216    /// Returns a mutable reference to the remaining amount.
217    fn remaining_mut(&mut self) -> &mut u128 {
218        &mut self.remaining
219    }
220
221    /// Returns the tick price.
222    pub fn tick(&self) -> i16 {
223        self.tick
224    }
225
226    /// Returns true if this is an ask order (selling base token).
227    pub fn is_ask(&self) -> bool {
228        !self.is_bid
229    }
230
231    /// Returns true if this is a flip order.
232    pub fn is_flip(&self) -> bool {
233        self.is_flip
234    }
235
236    /// Returns the flip tick.
237    ///
238    /// For non-flip orders, this is always 0.
239    /// For flip orders, this can be any valid tick value including 0 (peg price).
240    pub fn flip_tick(&self) -> i16 {
241        self.flip_tick
242    }
243
244    /// Returns the previous order ID in the doubly linked list (0 if head).
245    pub fn prev(&self) -> u128 {
246        self.prev
247    }
248
249    /// Returns the next order ID in the doubly linked list (0 if tail).
250    pub fn next(&self) -> u128 {
251        self.next
252    }
253
254    /// Sets the previous order ID in the doubly linked list.
255    pub fn set_prev(&mut self, prev_id: u128) {
256        self.prev = prev_id;
257    }
258
259    /// Sets the next order ID in the doubly linked list.
260    pub fn set_next(&mut self, next_id: u128) {
261        self.next = next_id;
262    }
263
264    /// Returns true if the order is completely filled (no remaining amount).
265    pub fn is_fully_filled(&self) -> bool {
266        self.remaining == 0
267    }
268
269    /// Fills the order by the specified amount.
270    ///
271    /// # Errors
272    /// Returns an error if fill_amount exceeds remaining amount
273    pub fn fill(&mut self, fill_amount: u128) -> Result<(), OrderError> {
274        if fill_amount > self.remaining {
275            return Err(OrderError::FillAmountExceedsRemaining {
276                requested: fill_amount,
277                available: self.remaining,
278            });
279        }
280        *self.remaining_mut() = self.remaining.saturating_sub(fill_amount);
281        Ok(())
282    }
283
284    /// Creates a flipped order from a fully filled flip order.
285    ///
286    /// When a flip order is completely filled, it creates a new order on the opposite side:
287    /// - Sides are swapped (bid -> ask, ask -> bid)
288    /// - New price = original flip_tick
289    /// - New flip_tick = original tick
290    /// - Amount is the same as original
291    /// - Linked list pointers are reset to 0 (will be set by orderbook on insertion)
292    ///
293    /// # Errors
294    /// Returns an error if called on a non-flip order or if the order is not fully filled
295    pub fn create_flipped_order(&self, new_order_id: u128) -> Result<Self, OrderError> {
296        // Check if this is a flip order
297        if !self.is_flip {
298            return Err(OrderError::NotAFlipOrder);
299        }
300
301        // Check if fully filled
302        if self.remaining != 0 {
303            return Err(OrderError::OrderNotFullyFilled {
304                remaining: self.remaining,
305            });
306        }
307
308        // Create flipped order
309        Ok(Self {
310            order_id: new_order_id,
311            maker: self.maker,
312            book_key: self.book_key,
313            is_bid: !self.is_bid,   // Flip the side
314            tick: self.flip_tick,   // Old flip_tick becomes new tick
315            amount: self.amount,    // Same as original
316            remaining: self.amount, // Reset remaining to original amount
317            prev: 0,                // Reset linked list pointers
318            next: 0,
319            is_flip: true,        // Keep as flip order
320            flip_tick: self.tick, // Old tick becomes new flip_tick
321        })
322    }
323}
324
325impl From<Order> for IStablecoinExchange::Order {
326    fn from(value: Order) -> Self {
327        Self {
328            orderId: value.order_id,
329            maker: value.maker,
330            bookKey: value.book_key,
331            isBid: value.is_bid,
332            tick: value.tick,
333            amount: value.amount,
334            remaining: value.remaining,
335            prev: value.prev,
336            next: value.next,
337            isFlip: value.is_flip,
338            flipTick: value.flip_tick,
339        }
340    }
341}
342
343#[cfg(test)]
344mod tests {
345    use crate::{
346        stablecoin_exchange::StablecoinExchange, storage::hashmap::HashMapStorageProvider,
347    };
348
349    use super::*;
350    use alloy::primitives::{address, b256};
351
352    const TEST_MAKER: Address = address!("0x1111111111111111111111111111111111111111");
353    const TEST_BOOK_KEY: B256 =
354        b256!("0x0000000000000000000000000000000000000000000000000000000000000001");
355
356    #[test]
357    fn test_new_bid_order() {
358        let order = Order::new_bid(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 5);
359
360        assert_eq!(order.order_id(), 1);
361        assert_eq!(order.maker(), TEST_MAKER);
362        assert_eq!(order.book_key(), TEST_BOOK_KEY);
363        assert!(order.is_bid());
364        assert_eq!(order.amount(), 1000);
365        assert_eq!(order.remaining(), 1000);
366        assert!(order.is_bid());
367        assert!(!order.is_ask());
368        assert_eq!(order.tick(), 5);
369        assert!(!order.is_flip());
370        assert_eq!(order.flip_tick(), 0);
371    }
372
373    #[test]
374    fn test_new_ask_order() {
375        let order = Order::new_ask(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 5);
376
377        assert_eq!(order.order_id(), 1);
378        assert!(!order.is_bid());
379        assert!(!order.is_bid());
380        assert!(order.is_ask());
381        assert!(!order.is_flip());
382    }
383
384    #[test]
385    fn test_new_flip_order_bid() {
386        let order = Order::new_flip(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 5, true, 10).unwrap();
387
388        assert!(order.is_flip());
389        assert_eq!(order.flip_tick(), 10);
390        assert_eq!(order.tick(), 5);
391        assert!(order.is_bid());
392    }
393
394    #[test]
395    fn test_new_flip_order_ask() {
396        let order = Order::new_flip(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 5, false, 2).unwrap();
397
398        assert!(order.is_flip());
399        assert_eq!(order.flip_tick(), 2);
400        assert_eq!(order.tick(), 5);
401        assert!(!order.is_bid());
402        assert!(order.is_ask());
403    }
404
405    #[test]
406    fn test_new_flip_order_bid_invalid_flip_tick() {
407        let result = Order::new_flip(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 5, true, 3);
408
409        assert!(matches!(result, Err(OrderError::InvalidBidFlipTick { .. })));
410    }
411
412    #[test]
413    fn test_new_flip_order_ask_invalid_flip_tick() {
414        let result = Order::new_flip(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 5, false, 7);
415
416        assert!(matches!(result, Err(OrderError::InvalidAskFlipTick { .. })));
417    }
418
419    #[test]
420    fn test_fill_bid_order_partial() {
421        let mut order = Order::new_bid(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 5);
422
423        assert!(!order.is_fully_filled());
424
425        order.fill(400).unwrap();
426
427        assert_eq!(order.remaining(), 600);
428        assert_eq!(order.amount(), 1000);
429        assert!(!order.is_fully_filled());
430    }
431
432    #[test]
433    fn test_fill_ask_order_complete() {
434        let mut order = Order::new_ask(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 5);
435
436        order.fill(1000).unwrap();
437
438        assert_eq!(order.remaining(), 0);
439        assert_eq!(order.amount(), 1000);
440        assert!(order.is_fully_filled());
441    }
442
443    #[test]
444    fn test_fill_order_overfill() {
445        let mut order = Order::new_bid(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 5);
446
447        let result = order.fill(1001);
448        assert!(matches!(
449            result,
450            Err(OrderError::FillAmountExceedsRemaining { .. })
451        ));
452    }
453
454    #[test]
455    fn test_create_flipped_order_bid_to_ask() {
456        let mut order = Order::new_flip(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 5, true, 10).unwrap();
457
458        // Fully fill the order
459        order.fill(1000).unwrap();
460        assert!(order.is_fully_filled());
461
462        // Create flipped order
463        let flipped = order.create_flipped_order(2).unwrap();
464
465        assert_eq!(flipped.order_id(), 2);
466        assert_eq!(flipped.maker(), order.maker());
467        assert_eq!(flipped.book_key(), order.book_key());
468        assert_eq!(flipped.amount(), 1000); // Same as original
469        assert_eq!(flipped.remaining(), 1000); // Reset to full amount
470        assert!(!flipped.is_bid()); // Flipped from bid to ask
471        assert!(flipped.is_ask());
472        assert_eq!(flipped.tick(), 10); // Old flip_tick
473        assert_eq!(flipped.flip_tick(), 5); // Old tick
474        assert!(flipped.is_flip());
475    }
476
477    #[test]
478    fn test_create_flipped_order_ask_to_bid() {
479        let mut order = Order::new_flip(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 10, false, 5).unwrap();
480
481        order.fill(1000).unwrap();
482        let flipped = order.create_flipped_order(2).unwrap();
483
484        assert!(flipped.is_bid()); // Flipped from ask to bid
485        assert!(!flipped.is_ask());
486        assert_eq!(flipped.tick(), 5); // Old flip_tick
487        assert_eq!(flipped.flip_tick(), 10); // Old tick
488    }
489
490    #[test]
491    fn test_create_flipped_order_non_flip() {
492        let mut order = Order::new_bid(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 5);
493
494        order.fill(1000).unwrap();
495        let result = order.create_flipped_order(2);
496        assert!(matches!(result, Err(OrderError::NotAFlipOrder)));
497    }
498
499    #[test]
500    fn test_create_flipped_order_not_filled() {
501        let order = Order::new_flip(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 5, true, 10).unwrap();
502
503        let result = order.create_flipped_order(2);
504        assert!(matches!(
505            result,
506            Err(OrderError::OrderNotFullyFilled { .. })
507        ));
508    }
509
510    #[test]
511    fn test_multiple_fills() {
512        let mut order = Order::new_bid(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 5);
513
514        // Multiple partial fills
515        order.fill(300).unwrap();
516        assert_eq!(order.remaining(), 700);
517
518        order.fill(200).unwrap();
519        assert_eq!(order.remaining(), 500);
520
521        order.fill(500).unwrap();
522        assert_eq!(order.remaining(), 0);
523        assert!(order.is_fully_filled());
524    }
525
526    #[test]
527    fn test_multiple_flips() {
528        // Test that an order can flip multiple times
529        let mut order = Order::new_flip(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 5, true, 10).unwrap();
530
531        // First flip: bid -> ask
532        order.fill(1000).unwrap();
533        let mut flipped1 = order.create_flipped_order(2).unwrap();
534
535        assert!(!flipped1.is_bid());
536        assert!(flipped1.is_ask());
537        assert_eq!(flipped1.tick(), 10);
538        assert_eq!(flipped1.flip_tick(), 5);
539
540        // Second flip: ask -> bid
541        flipped1.fill(1000).unwrap();
542        let flipped2 = flipped1.create_flipped_order(3).unwrap();
543
544        assert!(flipped2.is_bid());
545        assert!(!flipped2.is_ask());
546        assert_eq!(flipped2.tick(), 5);
547        assert_eq!(flipped2.flip_tick(), 10);
548    }
549
550    #[test]
551    fn test_tick_price_encoding() {
552        // Tick represents price offset from peg
553
554        let order_above = Order::new_bid(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 2);
555        assert_eq!(order_above.tick(), 2);
556
557        let order_below = Order::new_ask(2, TEST_MAKER, TEST_BOOK_KEY, 1000, -2);
558        assert_eq!(order_below.tick(), -2);
559
560        let order_par = Order::new_bid(3, TEST_MAKER, TEST_BOOK_KEY, 1000, 0);
561        assert_eq!(order_par.tick(), 0);
562    }
563
564    #[test]
565    fn test_linked_list_pointers_initialization() {
566        let order = Order::new_bid(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 5);
567        // Linked list pointers should be initialized to 0
568        assert_eq!(order.prev(), 0);
569        assert_eq!(order.next(), 0);
570    }
571
572    #[test]
573    fn test_set_linked_list_pointers() {
574        let mut order = Order::new_bid(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 5);
575
576        // Set prev and next pointers
577        order.set_prev(42);
578        order.set_next(43);
579
580        assert_eq!(order.prev(), 42);
581        assert_eq!(order.next(), 43);
582    }
583
584    #[test]
585    fn test_flipped_order_resets_linked_list_pointers() {
586        let mut order = Order::new_flip(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 5, true, 10).unwrap();
587
588        // Set linked list pointers on original order
589        order.set_prev(100);
590        order.set_next(200);
591
592        // Fill the order
593        order.fill(1000).unwrap();
594
595        // Create flipped order
596        let flipped = order.create_flipped_order(2).unwrap();
597
598        // Flipped order should have reset pointers
599        assert_eq!(flipped.prev(), 0);
600        assert_eq!(flipped.next(), 0);
601    }
602
603    #[test]
604    fn test_store_order() -> eyre::Result<()> {
605        let mut storage = HashMapStorageProvider::new(1);
606        let mut exchange = StablecoinExchange::new(&mut storage);
607
608        let id = 42;
609        let order = Order::new_flip(id, TEST_MAKER, TEST_BOOK_KEY, 1000, 5, true, 10).unwrap();
610        exchange.sstore_orders(id, order)?;
611
612        let loaded_order = exchange.sload_orders(id)?;
613        assert_eq!(loaded_order.order_id(), 42);
614        assert_eq!(loaded_order.maker(), TEST_MAKER);
615        assert_eq!(loaded_order.book_key(), TEST_BOOK_KEY);
616        assert_eq!(loaded_order.amount(), 1000);
617        assert_eq!(loaded_order.remaining(), 1000);
618        assert_eq!(loaded_order.tick(), 5);
619        assert!(loaded_order.is_bid());
620        assert!(loaded_order.is_flip());
621        assert_eq!(loaded_order.flip_tick(), 10);
622        assert_eq!(loaded_order.prev(), 0);
623        assert_eq!(loaded_order.next(), 0);
624
625        Ok(())
626    }
627
628    #[test]
629    fn test_delete_order() -> eyre::Result<()> {
630        let mut storage = HashMapStorageProvider::new(1);
631        let mut exchange = StablecoinExchange::new(&mut storage);
632
633        let id = 42;
634        let order = Order::new_flip(id, TEST_MAKER, TEST_BOOK_KEY, 1000, 5, true, 10).unwrap();
635        exchange.sstore_orders(id, order)?;
636        exchange.clear_orders(id)?;
637
638        let deleted_order = exchange.sload_orders(id)?;
639        assert_eq!(deleted_order.order_id(), 0);
640        assert_eq!(deleted_order.maker(), Address::ZERO);
641        assert_eq!(deleted_order.book_key(), B256::ZERO);
642        assert_eq!(deleted_order.amount(), 0);
643        assert_eq!(deleted_order.remaining(), 0);
644        assert_eq!(deleted_order.tick(), 0);
645        assert!(!deleted_order.is_bid());
646        assert!(!deleted_order.is_flip());
647        assert_eq!(deleted_order.flip_tick(), 0);
648        assert_eq!(deleted_order.prev(), 0);
649        assert_eq!(deleted_order.next(), 0);
650
651        Ok(())
652    }
653}