1use crate::stablecoin_dex::{IStablecoinDEX, error::OrderError};
8use alloy::primitives::{Address, B256};
9use tempo_chainspec::hardfork::TempoHardfork;
10use tempo_precompiles_macros::Storable;
11
12#[derive(Debug, Clone, PartialEq, Eq, Storable)]
36pub struct Order {
37 pub order_id: u128,
39 pub maker: Address,
41 pub book_key: B256,
43 pub is_bid: bool,
45 pub tick: i16,
47 pub amount: u128,
49 pub remaining: u128,
51 pub prev: u128,
53 pub next: u128,
55 pub is_flip: bool,
57 pub flip_tick: i16,
61}
62
63impl Order {
64 #[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 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 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 #[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 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 pub fn order_id(&self) -> u128 {
157 self.order_id
158 }
159
160 pub fn maker(&self) -> Address {
162 self.maker
163 }
164
165 pub fn book_key(&self) -> B256 {
167 self.book_key
168 }
169
170 pub fn is_bid(&self) -> bool {
172 self.is_bid
173 }
174
175 pub fn amount(&self) -> u128 {
177 self.amount
178 }
179
180 pub fn remaining(&self) -> u128 {
182 self.remaining
183 }
184
185 fn remaining_mut(&mut self) -> &mut u128 {
187 &mut self.remaining
188 }
189
190 pub fn tick(&self) -> i16 {
192 self.tick
193 }
194
195 pub fn is_ask(&self) -> bool {
197 !self.is_bid
198 }
199
200 pub fn is_flip(&self) -> bool {
202 self.is_flip
203 }
204
205 pub fn flip_tick(&self) -> i16 {
210 self.flip_tick
211 }
212
213 pub fn prev(&self) -> u128 {
215 self.prev
216 }
217
218 pub fn next(&self) -> u128 {
220 self.next
221 }
222
223 pub fn set_prev(&mut self, prev_id: u128) {
225 self.prev = prev_id;
226 }
227
228 pub fn set_next(&mut self, next_id: u128) {
230 self.next = next_id;
231 }
232
233 pub fn is_fully_filled(&self) -> bool {
235 self.remaining == 0
236 }
237
238 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 pub fn create_flipped_order(&self, new_order_id: u128) -> Result<Self, OrderError> {
266 if !self.is_flip {
268 return Err(OrderError::NotAFlipOrder);
269 }
270
271 if self.remaining != 0 {
273 return Err(OrderError::OrderNotFullyFilled {
274 remaining: self.remaining,
275 });
276 }
277
278 Ok(Self {
280 order_id: new_order_id,
281 maker: self.maker,
282 book_key: self.book_key,
283 is_bid: !self.is_bid, tick: self.flip_tick, amount: self.amount, remaining: self.amount, prev: 0, next: 0,
289 is_flip: true, flip_tick: self.tick, })
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 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 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 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 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 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 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 order.fill(1000).unwrap();
580 assert!(order.is_fully_filled());
581
582 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); assert_eq!(flipped.remaining(), 1000); assert!(!flipped.is_bid()); assert!(flipped.is_ask());
592 assert_eq!(flipped.tick(), 10); assert_eq!(flipped.flip_tick(), 5); 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()); assert!(!flipped.is_ask());
616 assert_eq!(flipped.tick(), 5); assert_eq!(flipped.flip_tick(), 10); }
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 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 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 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 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 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 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 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 order.set_prev(100);
750 order.set_next(200);
751
752 order.fill(1000).unwrap();
754
755 let flipped = order.create_flipped_order(2).unwrap();
757
758 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}