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, Copy, 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(crate) fn create_flipped_order(&self, new_order_id: u128) -> Self {
262 debug_assert!(self.is_flip());
263
264 Self {
266 order_id: new_order_id,
267 maker: self.maker,
268 book_key: self.book_key,
269 is_bid: !self.is_bid, tick: self.flip_tick, amount: self.amount, remaining: self.amount, prev: 0, next: 0,
275 is_flip: true, flip_tick: self.tick, }
278 }
279}
280
281impl From<Order> for IStablecoinDEX::Order {
282 fn from(value: Order) -> Self {
283 Self {
284 orderId: value.order_id,
285 maker: value.maker,
286 bookKey: value.book_key,
287 isBid: value.is_bid,
288 tick: value.tick,
289 amount: value.amount,
290 remaining: value.remaining,
291 prev: value.prev,
292 next: value.next,
293 isFlip: value.is_flip,
294 flipTick: value.flip_tick,
295 }
296 }
297}
298
299#[cfg(test)]
300mod tests {
301 use crate::{
302 stablecoin_dex::StablecoinDEX,
303 storage::{Handler, StorageCtx, hashmap::HashMapStorageProvider},
304 };
305
306 use super::*;
307 use alloy::primitives::{address, b256};
308
309 const TEST_MAKER: Address = address!("0x1111111111111111111111111111111111111111");
310 const TEST_BOOK_KEY: B256 =
311 b256!("0x0000000000000000000000000000000000000000000000000000000000000001");
312
313 #[test]
314 fn test_new_bid_order() {
315 let order = Order::new_bid(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 5);
316
317 assert_eq!(order.order_id(), 1);
318 assert_eq!(order.maker(), TEST_MAKER);
319 assert_eq!(order.book_key(), TEST_BOOK_KEY);
320 assert!(order.is_bid());
321 assert_eq!(order.amount(), 1000);
322 assert_eq!(order.remaining(), 1000);
323 assert!(order.is_bid());
324 assert!(!order.is_ask());
325 assert_eq!(order.tick(), 5);
326 assert!(!order.is_flip());
327 assert_eq!(order.flip_tick(), 0);
328 }
329
330 #[test]
331 fn test_new_ask_order() {
332 let order = Order::new_ask(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 5);
333
334 assert_eq!(order.order_id(), 1);
335 assert!(!order.is_bid());
336 assert!(!order.is_bid());
337 assert!(order.is_ask());
338 assert!(!order.is_flip());
339 }
340
341 #[test]
342 fn test_new_flip_order_bid() {
343 let order = Order::new_flip(
344 1,
345 TEST_MAKER,
346 TEST_BOOK_KEY,
347 1000,
348 5,
349 true,
350 10,
351 TempoHardfork::T4,
352 )
353 .unwrap();
354
355 assert!(order.is_flip());
356 assert_eq!(order.flip_tick(), 10);
357 assert_eq!(order.tick(), 5);
358 assert!(order.is_bid());
359 }
360
361 #[test]
362 fn test_new_flip_order_ask() {
363 let order = Order::new_flip(
364 1,
365 TEST_MAKER,
366 TEST_BOOK_KEY,
367 1000,
368 5,
369 false,
370 2,
371 TempoHardfork::T4,
372 )
373 .unwrap();
374
375 assert!(order.is_flip());
376 assert_eq!(order.flip_tick(), 2);
377 assert_eq!(order.tick(), 5);
378 assert!(!order.is_bid());
379 assert!(order.is_ask());
380 }
381
382 #[test]
383 fn test_new_flip_order_bid_invalid_flip_tick() {
384 let result = Order::new_flip(
385 1,
386 TEST_MAKER,
387 TEST_BOOK_KEY,
388 1000,
389 5,
390 true,
391 3,
392 TempoHardfork::T4,
393 );
394
395 assert!(matches!(result, Err(OrderError::InvalidBidFlipTick { .. })));
396 }
397
398 #[test]
399 fn test_new_flip_order_ask_invalid_flip_tick() {
400 let result = Order::new_flip(
401 1,
402 TEST_MAKER,
403 TEST_BOOK_KEY,
404 1000,
405 5,
406 false,
407 7,
408 TempoHardfork::T4,
409 );
410
411 assert!(matches!(result, Err(OrderError::InvalidAskFlipTick { .. })));
412 }
413
414 #[test]
415 fn test_new_flip_order_bid_same_tick_rejected() {
416 let result = Order::new_flip(
418 1,
419 TEST_MAKER,
420 TEST_BOOK_KEY,
421 1000,
422 5,
423 true,
424 5,
425 TempoHardfork::T4,
426 );
427 assert!(matches!(result, Err(OrderError::InvalidBidFlipTick { .. })));
428 }
429
430 #[test]
431 fn test_new_flip_order_ask_same_tick_rejected() {
432 let result = Order::new_flip(
434 1,
435 TEST_MAKER,
436 TEST_BOOK_KEY,
437 1000,
438 5,
439 false,
440 5,
441 TempoHardfork::T4,
442 );
443 assert!(matches!(result, Err(OrderError::InvalidAskFlipTick { .. })));
444 }
445
446 #[test]
447 fn test_new_flip_order_bid_same_tick_accepted() {
448 let order = Order::new_flip(
450 1,
451 TEST_MAKER,
452 TEST_BOOK_KEY,
453 1000,
454 5,
455 true,
456 5,
457 TempoHardfork::T5,
458 )
459 .unwrap();
460 assert!(order.is_flip());
461 assert_eq!(order.tick(), 5);
462 assert_eq!(order.flip_tick(), 5);
463 assert!(order.is_bid());
464 }
465
466 #[test]
467 fn test_new_flip_t5_still_rejects_wrong_side() {
468 let result = Order::new_flip(
470 1,
471 TEST_MAKER,
472 TEST_BOOK_KEY,
473 1000,
474 5,
475 true,
476 3,
477 TempoHardfork::T5,
478 );
479 assert!(matches!(result, Err(OrderError::InvalidBidFlipTick { .. })));
480
481 let result = Order::new_flip(
483 1,
484 TEST_MAKER,
485 TEST_BOOK_KEY,
486 1000,
487 5,
488 false,
489 7,
490 TempoHardfork::T5,
491 );
492 assert!(matches!(result, Err(OrderError::InvalidAskFlipTick { .. })));
493 }
494
495 #[test]
496 fn test_new_flip_order_ask_same_tick_accepted() {
497 let order = Order::new_flip(
499 1,
500 TEST_MAKER,
501 TEST_BOOK_KEY,
502 1000,
503 5,
504 false,
505 5,
506 TempoHardfork::T5,
507 )
508 .unwrap();
509 assert!(order.is_flip());
510 assert_eq!(order.tick(), 5);
511 assert_eq!(order.flip_tick(), 5);
512 assert!(order.is_ask());
513 }
514
515 #[test]
516 fn test_fill_bid_order_partial() {
517 let mut order = Order::new_bid(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 5);
518
519 assert!(!order.is_fully_filled());
520
521 order.fill(400).unwrap();
522
523 assert_eq!(order.remaining(), 600);
524 assert_eq!(order.amount(), 1000);
525 assert!(!order.is_fully_filled());
526 }
527
528 #[test]
529 fn test_fill_ask_order_complete() {
530 let mut order = Order::new_ask(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 5);
531
532 order.fill(1000).unwrap();
533
534 assert_eq!(order.remaining(), 0);
535 assert_eq!(order.amount(), 1000);
536 assert!(order.is_fully_filled());
537 }
538
539 #[test]
540 fn test_fill_order_overfill() {
541 let mut order = Order::new_bid(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 5);
542
543 let result = order.fill(1001);
544 assert!(matches!(
545 result,
546 Err(OrderError::FillAmountExceedsRemaining { .. })
547 ));
548 }
549
550 #[test]
551 fn test_create_flipped_order_bid_to_ask() {
552 let mut order = Order::new_flip(
553 1,
554 TEST_MAKER,
555 TEST_BOOK_KEY,
556 1000,
557 5,
558 true,
559 10,
560 TempoHardfork::T4,
561 )
562 .unwrap();
563
564 order.fill(1000).unwrap();
566 assert!(order.is_fully_filled());
567
568 let flipped = order.create_flipped_order(2);
570
571 assert_eq!(flipped.order_id(), 2);
572 assert_eq!(flipped.maker(), order.maker());
573 assert_eq!(flipped.book_key(), order.book_key());
574 assert_eq!(flipped.amount(), 1000); assert_eq!(flipped.remaining(), 1000); assert!(!flipped.is_bid()); assert!(flipped.is_ask());
578 assert_eq!(flipped.tick(), 10); assert_eq!(flipped.flip_tick(), 5); assert!(flipped.is_flip());
581 }
582
583 #[test]
584 fn test_create_flipped_order_ask_to_bid() {
585 let mut order = Order::new_flip(
586 1,
587 TEST_MAKER,
588 TEST_BOOK_KEY,
589 1000,
590 10,
591 false,
592 5,
593 TempoHardfork::T4,
594 )
595 .unwrap();
596
597 order.fill(1000).unwrap();
598 let flipped = order.create_flipped_order(2);
599
600 assert!(flipped.is_bid()); assert!(!flipped.is_ask());
602 assert_eq!(flipped.tick(), 5); assert_eq!(flipped.flip_tick(), 10); }
605
606 #[test]
607 fn test_multiple_fills() {
608 let mut order = Order::new_bid(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 5);
609
610 order.fill(300).unwrap();
612 assert_eq!(order.remaining(), 700);
613
614 order.fill(200).unwrap();
615 assert_eq!(order.remaining(), 500);
616
617 order.fill(500).unwrap();
618 assert_eq!(order.remaining(), 0);
619 assert!(order.is_fully_filled());
620 }
621
622 #[test]
623 fn test_multiple_flips() {
624 let mut order = Order::new_flip(
626 1,
627 TEST_MAKER,
628 TEST_BOOK_KEY,
629 1000,
630 5,
631 true,
632 10,
633 TempoHardfork::T4,
634 )
635 .unwrap();
636
637 order.fill(1000).unwrap();
639 let mut flipped1 = order.create_flipped_order(2);
640
641 assert!(!flipped1.is_bid());
642 assert!(flipped1.is_ask());
643 assert_eq!(flipped1.tick(), 10);
644 assert_eq!(flipped1.flip_tick(), 5);
645
646 flipped1.fill(1000).unwrap();
648 let flipped2 = flipped1.create_flipped_order(3);
649
650 assert!(flipped2.is_bid());
651 assert!(!flipped2.is_ask());
652 assert_eq!(flipped2.tick(), 5);
653 assert_eq!(flipped2.flip_tick(), 10);
654 }
655
656 #[test]
657 fn test_tick_price_encoding() {
658 let order_above = Order::new_bid(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 2);
661 assert_eq!(order_above.tick(), 2);
662
663 let order_below = Order::new_ask(2, TEST_MAKER, TEST_BOOK_KEY, 1000, -2);
664 assert_eq!(order_below.tick(), -2);
665
666 let order_par = Order::new_bid(3, TEST_MAKER, TEST_BOOK_KEY, 1000, 0);
667 assert_eq!(order_par.tick(), 0);
668 }
669
670 #[test]
671 fn test_linked_list_pointers_initialization() {
672 let order = Order::new_bid(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 5);
673 assert_eq!(order.prev(), 0);
675 assert_eq!(order.next(), 0);
676 }
677
678 #[test]
679 fn test_set_linked_list_pointers() {
680 let mut order = Order::new_bid(1, TEST_MAKER, TEST_BOOK_KEY, 1000, 5);
681
682 order.set_prev(42);
684 order.set_next(43);
685
686 assert_eq!(order.prev(), 42);
687 assert_eq!(order.next(), 43);
688 }
689
690 #[test]
691 fn test_flipped_order_resets_linked_list_pointers() {
692 let mut order = Order::new_flip(
693 1,
694 TEST_MAKER,
695 TEST_BOOK_KEY,
696 1000,
697 5,
698 true,
699 10,
700 TempoHardfork::T4,
701 )
702 .unwrap();
703
704 order.set_prev(100);
706 order.set_next(200);
707
708 order.fill(1000).unwrap();
710
711 let flipped = order.create_flipped_order(2);
713
714 assert_eq!(flipped.prev(), 0);
716 assert_eq!(flipped.next(), 0);
717 }
718
719 #[test]
720 fn test_store_order() -> eyre::Result<()> {
721 let mut storage = HashMapStorageProvider::new(1);
722 StorageCtx::enter(&mut storage, || {
723 let mut exchange = StablecoinDEX::new();
724
725 let id = 42;
726 let order = Order::new_flip(
727 id,
728 TEST_MAKER,
729 TEST_BOOK_KEY,
730 1000,
731 5,
732 true,
733 10,
734 TempoHardfork::T4,
735 )
736 .unwrap();
737 exchange.orders[id].write(order)?;
738
739 let loaded_order = exchange.orders[id].read()?;
740 assert_eq!(loaded_order.order_id(), 42);
741 assert_eq!(loaded_order.maker(), TEST_MAKER);
742 assert_eq!(loaded_order.book_key(), TEST_BOOK_KEY);
743 assert_eq!(loaded_order.amount(), 1000);
744 assert_eq!(loaded_order.remaining(), 1000);
745 assert_eq!(loaded_order.tick(), 5);
746 assert!(loaded_order.is_bid());
747 assert!(loaded_order.is_flip());
748 assert_eq!(loaded_order.flip_tick(), 10);
749 assert_eq!(loaded_order.prev(), 0);
750 assert_eq!(loaded_order.next(), 0);
751
752 Ok(())
753 })
754 }
755
756 #[test]
757 fn test_delete_order() -> eyre::Result<()> {
758 let mut storage = HashMapStorageProvider::new(1);
759 StorageCtx::enter(&mut storage, || {
760 let mut exchange = StablecoinDEX::new();
761
762 let id = 42;
763 let order = Order::new_flip(
764 id,
765 TEST_MAKER,
766 TEST_BOOK_KEY,
767 1000,
768 5,
769 true,
770 10,
771 TempoHardfork::T4,
772 )
773 .unwrap();
774 exchange.orders[id].write(order)?;
775 exchange.orders[id].delete()?;
776
777 let deleted_order = exchange.orders[id].read()?;
778 assert_eq!(deleted_order.order_id(), 0);
779 assert_eq!(deleted_order.maker(), Address::ZERO);
780 assert_eq!(deleted_order.book_key(), B256::ZERO);
781 assert_eq!(deleted_order.amount(), 0);
782 assert_eq!(deleted_order.remaining(), 0);
783 assert_eq!(deleted_order.tick(), 0);
784 assert!(!deleted_order.is_bid());
785 assert!(!deleted_order.is_flip());
786 assert_eq!(deleted_order.flip_tick(), 0);
787 assert_eq!(deleted_order.prev(), 0);
788 assert_eq!(deleted_order.next(), 0);
789
790 Ok(())
791 })
792 }
793}