Skip to main content

tempo_precompiles/stablecoin_dex/
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::stablecoin_dex::{IStablecoinDEX, error::OrderError};
8use alloy::primitives::{Address, B256};
9use tempo_chainspec::hardfork::TempoHardfork;
10use tempo_precompiles_macros::Storable;
11
12/// Represents an order in the stablecoin DEX orderbook.
13///
14/// This struct matches the Solidity reference implementation in StablecoinDEX.sol.
15///
16/// # Order Types
17/// - **Regular orders**: Orders with `is_flip = false`
18/// - **Flip orders**: Orders with `is_flip = true` that automatically create
19///   a new order on the opposite side when fully filled
20///
21/// # Order Lifecycle
22/// 1. Order is placed via `place()` or `placeFlip()` and immediately added to the orderbook
23/// 2. Orders can be filled (fully or partially) by swaps
24/// 3. Flip orders automatically create a new order on the opposite side when fully filled
25/// 4. Orders can be cancelled, removing them from the book and refunding escrow
26///
27/// # Price-Time Priority
28/// Orders are sorted by price (tick), then by insertion time.
29/// The doubly linked list maintains insertion order - orders are added at the tail,
30/// so traversing from head to tail gives price-time priority.
31///
32/// # Onchain Storage
33/// Orders are stored onchain in doubly linked lists organized by tick.
34/// Each tick maintains a FIFO queue of orders using `prev` and `next` pointers.
35#[derive(Debug, Clone, PartialEq, Eq, Storable)]
36pub struct Order {
37    /// Unique identifier for this order
38    pub order_id: u128,
39    /// Address of the user who placed this order
40    pub maker: Address,
41    /// Orderbook key (identifies the trading pair)
42    pub book_key: B256,
43    /// Whether this is a bid (true) or ask (false) order
44    pub is_bid: bool,
45    /// Price tick
46    pub tick: i16,
47    /// Original order amount
48    pub amount: u128,
49    /// Remaining amount to be filled
50    pub remaining: u128,
51    /// Previous order ID in the doubly linked list (0 if head)
52    pub prev: u128,
53    /// Next order ID in the doubly linked list (0 if tail)
54    pub next: u128,
55    /// Whether this is a flip order
56    pub is_flip: bool,
57    /// Tick to flip to when fully filled (for flip orders, 0 for regular orders).
58    /// Pre-T5: for bid flips `flip_tick > tick`; for ask flips `flip_tick < tick`.
59    /// T5+ (TIP-1030): for bid flips `flip_tick >= tick`; for ask flips `flip_tick <= tick`.
60    pub flip_tick: i16,
61}
62
63impl Order {
64    /// Creates a new [`Order`] with `prev` and `next` initialized to 0.
65    #[allow(clippy::too_many_arguments)]
66    pub fn new(
67        order_id: u128,
68        maker: Address,
69        book_key: B256,
70        amount: u128,
71        tick: i16,
72        is_bid: bool,
73        is_flip: bool,
74        flip_tick: i16,
75    ) -> Self {
76        Self {
77            order_id,
78            maker,
79            book_key,
80            is_bid,
81            tick,
82            amount,
83            remaining: amount,
84            prev: 0,
85            next: 0,
86            is_flip,
87            flip_tick,
88        }
89    }
90
91    /// Creates a new bid order
92    pub fn new_bid(
93        order_id: u128,
94        maker: Address,
95        book_key: B256,
96        amount: u128,
97        tick: i16,
98    ) -> Self {
99        Self::new(order_id, maker, book_key, amount, tick, true, false, 0)
100    }
101
102    /// Creates a new ask order
103    pub fn new_ask(
104        order_id: u128,
105        maker: Address,
106        book_key: B256,
107        amount: u128,
108        tick: i16,
109    ) -> Self {
110        Self::new(order_id, maker, book_key, amount, tick, false, false, 0)
111    }
112
113    /// Creates a new flip order with `prev` and `next` initialized to 0.
114    /// The orderbook sets linked-list pointers when inserting.
115    ///
116    /// The `hardfork` parameter controls flip-tick validation:
117    /// - Pre-T5: for bid flips `flip_tick > tick`; for ask flips `flip_tick < tick`.
118    /// - T5+ (TIP-1030): for bid flips `flip_tick >= tick`; for ask flips `flip_tick <= tick`.
119    ///
120    /// # Errors
121    /// - `InvalidBidFlipTick` - `is_bid` is true and `flip_tick < tick`
122    /// - `InvalidAskFlipTick` - `is_bid` is false and `flip_tick > tick`
123    #[allow(clippy::too_many_arguments)]
124    pub fn new_flip(
125        order_id: u128,
126        maker: Address,
127        book_key: B256,
128        amount: u128,
129        tick: i16,
130        is_bid: bool,
131        flip_tick: i16,
132        hardfork: TempoHardfork,
133    ) -> Result<Self, OrderError> {
134        // TIP-1030 (T5+) relaxes the constraint to allow `flip_tick == tick`.
135        let t5_active = hardfork.is_t5();
136        let invalid = if is_bid {
137            flip_tick < tick || (!t5_active && flip_tick == tick)
138        } else {
139            flip_tick > tick || (!t5_active && flip_tick == tick)
140        };
141
142        if invalid {
143            return Err(if is_bid {
144                OrderError::InvalidBidFlipTick { tick, flip_tick }
145            } else {
146                OrderError::InvalidAskFlipTick { tick, flip_tick }
147            });
148        }
149
150        Ok(Self::new(
151            order_id, maker, book_key, amount, tick, is_bid, true, flip_tick,
152        ))
153    }
154
155    /// Returns the order ID.
156    pub fn order_id(&self) -> u128 {
157        self.order_id
158    }
159
160    /// Returns the maker address.
161    pub fn maker(&self) -> Address {
162        self.maker
163    }
164
165    /// Returns the orderbook key.
166    pub fn book_key(&self) -> B256 {
167        self.book_key
168    }
169
170    /// Returns whether this is a bid order.
171    pub fn is_bid(&self) -> bool {
172        self.is_bid
173    }
174
175    /// Returns the original amount.
176    pub fn amount(&self) -> u128 {
177        self.amount
178    }
179
180    /// Returns the remaining amount.
181    pub fn remaining(&self) -> u128 {
182        self.remaining
183    }
184
185    /// Returns a mutable reference to the remaining amount.
186    fn remaining_mut(&mut self) -> &mut u128 {
187        &mut self.remaining
188    }
189
190    /// Returns the tick price.
191    pub fn tick(&self) -> i16 {
192        self.tick
193    }
194
195    /// Returns true if this is an ask order (selling base token).
196    pub fn is_ask(&self) -> bool {
197        !self.is_bid
198    }
199
200    /// Returns true if this is a flip order.
201    pub fn is_flip(&self) -> bool {
202        self.is_flip
203    }
204
205    /// Returns the flip tick.
206    ///
207    /// For non-flip orders, this is always 0.
208    /// For flip orders, this can be any valid tick value including 0 (peg price).
209    pub fn flip_tick(&self) -> i16 {
210        self.flip_tick
211    }
212
213    /// Returns the previous order ID in the doubly linked list (0 if head).
214    pub fn prev(&self) -> u128 {
215        self.prev
216    }
217
218    /// Returns the next order ID in the doubly linked list (0 if tail).
219    pub fn next(&self) -> u128 {
220        self.next
221    }
222
223    /// Sets the previous order ID in the doubly linked list.
224    pub fn set_prev(&mut self, prev_id: u128) {
225        self.prev = prev_id;
226    }
227
228    /// Sets the next order ID in the doubly linked list.
229    pub fn set_next(&mut self, next_id: u128) {
230        self.next = next_id;
231    }
232
233    /// Returns true if the order is completely filled (no remaining amount).
234    pub fn is_fully_filled(&self) -> bool {
235        self.remaining == 0
236    }
237
238    /// Fills the order by the specified amount, reducing `remaining` accordingly.
239    ///
240    /// # Errors
241    /// - `FillAmountExceedsRemaining` — `fill_amount` is greater than `remaining`
242    pub fn fill(&mut self, fill_amount: u128) -> Result<(), OrderError> {
243        if fill_amount > self.remaining {
244            return Err(OrderError::FillAmountExceedsRemaining {
245                requested: fill_amount,
246                available: self.remaining,
247            });
248        }
249        *self.remaining_mut() = self.remaining.saturating_sub(fill_amount);
250        Ok(())
251    }
252
253    /// Creates a flipped order from a fully filled flip order.
254    ///
255    /// When a flip order is completely filled, it creates a new order on the opposite side:
256    /// - Sides are swapped (bid -> ask, ask -> bid)
257    /// - New price = original flip_tick
258    /// - New flip_tick = original tick
259    /// - Amount is the same as original
260    /// - Linked list pointers are reset to 0 (will be set by orderbook on insertion)
261    ///
262    /// # Errors
263    /// - `NotAFlipOrder` — called on a non-flip order
264    /// - `OrderNotFullyFilled` — `remaining` is not zero
265    pub fn create_flipped_order(&self, new_order_id: u128) -> Result<Self, OrderError> {
266        // Check if this is a flip order
267        if !self.is_flip {
268            return Err(OrderError::NotAFlipOrder);
269        }
270
271        // Check if fully filled
272        if self.remaining != 0 {
273            return Err(OrderError::OrderNotFullyFilled {
274                remaining: self.remaining,
275            });
276        }
277
278        // Create flipped order
279        Ok(Self {
280            order_id: new_order_id,
281            maker: self.maker,
282            book_key: self.book_key,
283            is_bid: !self.is_bid,   // Flip the side
284            tick: self.flip_tick,   // Old flip_tick becomes new tick
285            amount: self.amount,    // Same as original
286            remaining: self.amount, // Reset remaining to original amount
287            prev: 0,                // Reset linked list pointers
288            next: 0,
289            is_flip: true,        // Keep as flip order
290            flip_tick: self.tick, // Old tick becomes new flip_tick
291        })
292    }
293}
294
295impl From<Order> for IStablecoinDEX::Order {
296    fn from(value: Order) -> Self {
297        Self {
298            orderId: value.order_id,
299            maker: value.maker,
300            bookKey: value.book_key,
301            isBid: value.is_bid,
302            tick: value.tick,
303            amount: value.amount,
304            remaining: value.remaining,
305            prev: value.prev,
306            next: value.next,
307            isFlip: value.is_flip,
308            flipTick: value.flip_tick,
309        }
310    }
311}
312
313#[cfg(test)]
314mod tests {
315    use crate::{
316        stablecoin_dex::StablecoinDEX,
317        storage::{Handler, StorageCtx, hashmap::HashMapStorageProvider},
318    };
319
320    use super::*;
321    use alloy::primitives::{address, b256};
322
323    const TEST_MAKER: Address = address!("0x1111111111111111111111111111111111111111");
324    const TEST_BOOK_KEY: B256 =
325        b256!("0x0000000000000000000000000000000000000000000000000000000000000001");
326
327    #[test]
328    fn test_new_bid_order() {
329        let order = Order::new_bid(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 5);
330
331        assert_eq!(order.order_id(), 1);
332        assert_eq!(order.maker(), TEST_MAKER);
333        assert_eq!(order.book_key(), TEST_BOOK_KEY);
334        assert!(order.is_bid());
335        assert_eq!(order.amount(), 1000);
336        assert_eq!(order.remaining(), 1000);
337        assert!(order.is_bid());
338        assert!(!order.is_ask());
339        assert_eq!(order.tick(), 5);
340        assert!(!order.is_flip());
341        assert_eq!(order.flip_tick(), 0);
342    }
343
344    #[test]
345    fn test_new_ask_order() {
346        let order = Order::new_ask(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 5);
347
348        assert_eq!(order.order_id(), 1);
349        assert!(!order.is_bid());
350        assert!(!order.is_bid());
351        assert!(order.is_ask());
352        assert!(!order.is_flip());
353    }
354
355    #[test]
356    fn test_new_flip_order_bid() {
357        let order = Order::new_flip(
358            1,
359            TEST_MAKER,
360            TEST_BOOK_KEY,
361            1000,
362            5,
363            true,
364            10,
365            TempoHardfork::T4,
366        )
367        .unwrap();
368
369        assert!(order.is_flip());
370        assert_eq!(order.flip_tick(), 10);
371        assert_eq!(order.tick(), 5);
372        assert!(order.is_bid());
373    }
374
375    #[test]
376    fn test_new_flip_order_ask() {
377        let order = Order::new_flip(
378            1,
379            TEST_MAKER,
380            TEST_BOOK_KEY,
381            1000,
382            5,
383            false,
384            2,
385            TempoHardfork::T4,
386        )
387        .unwrap();
388
389        assert!(order.is_flip());
390        assert_eq!(order.flip_tick(), 2);
391        assert_eq!(order.tick(), 5);
392        assert!(!order.is_bid());
393        assert!(order.is_ask());
394    }
395
396    #[test]
397    fn test_new_flip_order_bid_invalid_flip_tick() {
398        let result = Order::new_flip(
399            1,
400            TEST_MAKER,
401            TEST_BOOK_KEY,
402            1000,
403            5,
404            true,
405            3,
406            TempoHardfork::T4,
407        );
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(
415            1,
416            TEST_MAKER,
417            TEST_BOOK_KEY,
418            1000,
419            5,
420            false,
421            7,
422            TempoHardfork::T4,
423        );
424
425        assert!(matches!(result, Err(OrderError::InvalidAskFlipTick { .. })));
426    }
427
428    #[test]
429    fn test_new_flip_order_bid_same_tick_rejected() {
430        // Pre-T5: same-tick bid flip is rejected
431        let result = Order::new_flip(
432            1,
433            TEST_MAKER,
434            TEST_BOOK_KEY,
435            1000,
436            5,
437            true,
438            5,
439            TempoHardfork::T4,
440        );
441        assert!(matches!(result, Err(OrderError::InvalidBidFlipTick { .. })));
442    }
443
444    #[test]
445    fn test_new_flip_order_ask_same_tick_rejected() {
446        // Pre-T5: same-tick ask flip is rejected
447        let result = Order::new_flip(
448            1,
449            TEST_MAKER,
450            TEST_BOOK_KEY,
451            1000,
452            5,
453            false,
454            5,
455            TempoHardfork::T4,
456        );
457        assert!(matches!(result, Err(OrderError::InvalidAskFlipTick { .. })));
458    }
459
460    #[test]
461    fn test_new_flip_order_bid_same_tick_accepted() {
462        // TIP-1030 (T5+): same-tick bid flip is accepted
463        let order = Order::new_flip(
464            1,
465            TEST_MAKER,
466            TEST_BOOK_KEY,
467            1000,
468            5,
469            true,
470            5,
471            TempoHardfork::T5,
472        )
473        .unwrap();
474        assert!(order.is_flip());
475        assert_eq!(order.tick(), 5);
476        assert_eq!(order.flip_tick(), 5);
477        assert!(order.is_bid());
478    }
479
480    #[test]
481    fn test_new_flip_t5_still_rejects_wrong_side() {
482        // TIP-1030 (T5+): flip_tick < tick still rejected for bids
483        let result = Order::new_flip(
484            1,
485            TEST_MAKER,
486            TEST_BOOK_KEY,
487            1000,
488            5,
489            true,
490            3,
491            TempoHardfork::T5,
492        );
493        assert!(matches!(result, Err(OrderError::InvalidBidFlipTick { .. })));
494
495        // TIP-1030 (T5+): flip_tick > tick still rejected for asks
496        let result = Order::new_flip(
497            1,
498            TEST_MAKER,
499            TEST_BOOK_KEY,
500            1000,
501            5,
502            false,
503            7,
504            TempoHardfork::T5,
505        );
506        assert!(matches!(result, Err(OrderError::InvalidAskFlipTick { .. })));
507    }
508
509    #[test]
510    fn test_new_flip_order_ask_same_tick_accepted() {
511        // TIP-1030 (T5+): same-tick ask flip is accepted
512        let order = Order::new_flip(
513            1,
514            TEST_MAKER,
515            TEST_BOOK_KEY,
516            1000,
517            5,
518            false,
519            5,
520            TempoHardfork::T5,
521        )
522        .unwrap();
523        assert!(order.is_flip());
524        assert_eq!(order.tick(), 5);
525        assert_eq!(order.flip_tick(), 5);
526        assert!(order.is_ask());
527    }
528
529    #[test]
530    fn test_fill_bid_order_partial() {
531        let mut order = Order::new_bid(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 5);
532
533        assert!(!order.is_fully_filled());
534
535        order.fill(400).unwrap();
536
537        assert_eq!(order.remaining(), 600);
538        assert_eq!(order.amount(), 1000);
539        assert!(!order.is_fully_filled());
540    }
541
542    #[test]
543    fn test_fill_ask_order_complete() {
544        let mut order = Order::new_ask(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 5);
545
546        order.fill(1000).unwrap();
547
548        assert_eq!(order.remaining(), 0);
549        assert_eq!(order.amount(), 1000);
550        assert!(order.is_fully_filled());
551    }
552
553    #[test]
554    fn test_fill_order_overfill() {
555        let mut order = Order::new_bid(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 5);
556
557        let result = order.fill(1001);
558        assert!(matches!(
559            result,
560            Err(OrderError::FillAmountExceedsRemaining { .. })
561        ));
562    }
563
564    #[test]
565    fn test_create_flipped_order_bid_to_ask() {
566        let mut order = Order::new_flip(
567            1,
568            TEST_MAKER,
569            TEST_BOOK_KEY,
570            1000,
571            5,
572            true,
573            10,
574            TempoHardfork::T4,
575        )
576        .unwrap();
577
578        // Fully fill the order
579        order.fill(1000).unwrap();
580        assert!(order.is_fully_filled());
581
582        // Create flipped order
583        let flipped = order.create_flipped_order(2).unwrap();
584
585        assert_eq!(flipped.order_id(), 2);
586        assert_eq!(flipped.maker(), order.maker());
587        assert_eq!(flipped.book_key(), order.book_key());
588        assert_eq!(flipped.amount(), 1000); // Same as original
589        assert_eq!(flipped.remaining(), 1000); // Reset to full amount
590        assert!(!flipped.is_bid()); // Flipped from bid to ask
591        assert!(flipped.is_ask());
592        assert_eq!(flipped.tick(), 10); // Old flip_tick
593        assert_eq!(flipped.flip_tick(), 5); // Old tick
594        assert!(flipped.is_flip());
595    }
596
597    #[test]
598    fn test_create_flipped_order_ask_to_bid() {
599        let mut order = Order::new_flip(
600            1,
601            TEST_MAKER,
602            TEST_BOOK_KEY,
603            1000,
604            10,
605            false,
606            5,
607            TempoHardfork::T4,
608        )
609        .unwrap();
610
611        order.fill(1000).unwrap();
612        let flipped = order.create_flipped_order(2).unwrap();
613
614        assert!(flipped.is_bid()); // Flipped from ask to bid
615        assert!(!flipped.is_ask());
616        assert_eq!(flipped.tick(), 5); // Old flip_tick
617        assert_eq!(flipped.flip_tick(), 10); // Old tick
618    }
619
620    #[test]
621    fn test_create_flipped_order_non_flip() {
622        let mut order = Order::new_bid(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 5);
623
624        order.fill(1000).unwrap();
625        let result = order.create_flipped_order(2);
626        assert!(matches!(result, Err(OrderError::NotAFlipOrder)));
627    }
628
629    #[test]
630    fn test_create_flipped_order_not_filled() {
631        let order = Order::new_flip(
632            1,
633            TEST_MAKER,
634            TEST_BOOK_KEY,
635            1000,
636            5,
637            true,
638            10,
639            TempoHardfork::T4,
640        )
641        .unwrap();
642
643        let result = order.create_flipped_order(2);
644        assert!(matches!(
645            result,
646            Err(OrderError::OrderNotFullyFilled { .. })
647        ));
648    }
649
650    #[test]
651    fn test_multiple_fills() {
652        let mut order = Order::new_bid(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 5);
653
654        // Multiple partial fills
655        order.fill(300).unwrap();
656        assert_eq!(order.remaining(), 700);
657
658        order.fill(200).unwrap();
659        assert_eq!(order.remaining(), 500);
660
661        order.fill(500).unwrap();
662        assert_eq!(order.remaining(), 0);
663        assert!(order.is_fully_filled());
664    }
665
666    #[test]
667    fn test_multiple_flips() {
668        // Test that an order can flip multiple times
669        let mut order = Order::new_flip(
670            1,
671            TEST_MAKER,
672            TEST_BOOK_KEY,
673            1000,
674            5,
675            true,
676            10,
677            TempoHardfork::T4,
678        )
679        .unwrap();
680
681        // First flip: bid -> ask
682        order.fill(1000).unwrap();
683        let mut flipped1 = order.create_flipped_order(2).unwrap();
684
685        assert!(!flipped1.is_bid());
686        assert!(flipped1.is_ask());
687        assert_eq!(flipped1.tick(), 10);
688        assert_eq!(flipped1.flip_tick(), 5);
689
690        // Second flip: ask -> bid
691        flipped1.fill(1000).unwrap();
692        let flipped2 = flipped1.create_flipped_order(3).unwrap();
693
694        assert!(flipped2.is_bid());
695        assert!(!flipped2.is_ask());
696        assert_eq!(flipped2.tick(), 5);
697        assert_eq!(flipped2.flip_tick(), 10);
698    }
699
700    #[test]
701    fn test_tick_price_encoding() {
702        // Tick represents price offset from peg
703
704        let order_above = Order::new_bid(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 2);
705        assert_eq!(order_above.tick(), 2);
706
707        let order_below = Order::new_ask(2, TEST_MAKER, TEST_BOOK_KEY, 1000, -2);
708        assert_eq!(order_below.tick(), -2);
709
710        let order_par = Order::new_bid(3, TEST_MAKER, TEST_BOOK_KEY, 1000, 0);
711        assert_eq!(order_par.tick(), 0);
712    }
713
714    #[test]
715    fn test_linked_list_pointers_initialization() {
716        let order = Order::new_bid(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 5);
717        // Linked list pointers should be initialized to 0
718        assert_eq!(order.prev(), 0);
719        assert_eq!(order.next(), 0);
720    }
721
722    #[test]
723    fn test_set_linked_list_pointers() {
724        let mut order = Order::new_bid(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 5);
725
726        // Set prev and next pointers
727        order.set_prev(42);
728        order.set_next(43);
729
730        assert_eq!(order.prev(), 42);
731        assert_eq!(order.next(), 43);
732    }
733
734    #[test]
735    fn test_flipped_order_resets_linked_list_pointers() {
736        let mut order = Order::new_flip(
737            1,
738            TEST_MAKER,
739            TEST_BOOK_KEY,
740            1000,
741            5,
742            true,
743            10,
744            TempoHardfork::T4,
745        )
746        .unwrap();
747
748        // Set linked list pointers on original order
749        order.set_prev(100);
750        order.set_next(200);
751
752        // Fill the order
753        order.fill(1000).unwrap();
754
755        // Create flipped order
756        let flipped = order.create_flipped_order(2).unwrap();
757
758        // Flipped order should have reset pointers
759        assert_eq!(flipped.prev(), 0);
760        assert_eq!(flipped.next(), 0);
761    }
762
763    #[test]
764    fn test_store_order() -> eyre::Result<()> {
765        let mut storage = HashMapStorageProvider::new(1);
766        StorageCtx::enter(&mut storage, || {
767            let mut exchange = StablecoinDEX::new();
768
769            let id = 42;
770            let order = Order::new_flip(
771                id,
772                TEST_MAKER,
773                TEST_BOOK_KEY,
774                1000,
775                5,
776                true,
777                10,
778                TempoHardfork::T4,
779            )
780            .unwrap();
781            exchange.orders[id].write(order)?;
782
783            let loaded_order = exchange.orders[id].read()?;
784            assert_eq!(loaded_order.order_id(), 42);
785            assert_eq!(loaded_order.maker(), TEST_MAKER);
786            assert_eq!(loaded_order.book_key(), TEST_BOOK_KEY);
787            assert_eq!(loaded_order.amount(), 1000);
788            assert_eq!(loaded_order.remaining(), 1000);
789            assert_eq!(loaded_order.tick(), 5);
790            assert!(loaded_order.is_bid());
791            assert!(loaded_order.is_flip());
792            assert_eq!(loaded_order.flip_tick(), 10);
793            assert_eq!(loaded_order.prev(), 0);
794            assert_eq!(loaded_order.next(), 0);
795
796            Ok(())
797        })
798    }
799
800    #[test]
801    fn test_delete_order() -> eyre::Result<()> {
802        let mut storage = HashMapStorageProvider::new(1);
803        StorageCtx::enter(&mut storage, || {
804            let mut exchange = StablecoinDEX::new();
805
806            let id = 42;
807            let order = Order::new_flip(
808                id,
809                TEST_MAKER,
810                TEST_BOOK_KEY,
811                1000,
812                5,
813                true,
814                10,
815                TempoHardfork::T4,
816            )
817            .unwrap();
818            exchange.orders[id].write(order)?;
819            exchange.orders[id].delete()?;
820
821            let deleted_order = exchange.orders[id].read()?;
822            assert_eq!(deleted_order.order_id(), 0);
823            assert_eq!(deleted_order.maker(), Address::ZERO);
824            assert_eq!(deleted_order.book_key(), B256::ZERO);
825            assert_eq!(deleted_order.amount(), 0);
826            assert_eq!(deleted_order.remaining(), 0);
827            assert_eq!(deleted_order.tick(), 0);
828            assert!(!deleted_order.is_bid());
829            assert!(!deleted_order.is_flip());
830            assert_eq!(deleted_order.flip_tick(), 0);
831            assert_eq!(deleted_order.prev(), 0);
832            assert_eq!(deleted_order.next(), 0);
833
834            Ok(())
835        })
836    }
837}