1use sha3::{Digest, Keccak256};
7use solana_pubkey::Pubkey;
8use solana_signature::Signature;
9
10#[cfg(feature = "client")]
11use solana_keypair::Keypair;
12#[cfg(feature = "client")]
13use solana_signer::Signer;
14
15use crate::program::constants::{COMPACT_ORDER_SIZE, FULL_ORDER_SIZE};
16use crate::program::error::{SdkError, SdkResult};
17use crate::program::types::{AskOrderParams, BidOrderParams, OrderSide};
18use crate::shared::SubmitOrderRequest;
19
20#[derive(Debug, Clone)]
38pub struct FullOrder {
39 pub nonce: u64,
41 pub maker: Pubkey,
43 pub market: Pubkey,
45 pub base_mint: Pubkey,
47 pub quote_mint: Pubkey,
49 pub side: OrderSide,
51 pub maker_amount: u64,
53 pub taker_amount: u64,
55 pub expiration: i64,
57 pub signature: [u8; 64],
59}
60
61impl FullOrder {
62 pub const LEN: usize = FULL_ORDER_SIZE;
64
65 pub fn new_bid(params: BidOrderParams) -> Self {
67 Self {
68 nonce: params.nonce,
69 maker: params.maker,
70 market: params.market,
71 base_mint: params.base_mint,
72 quote_mint: params.quote_mint,
73 side: OrderSide::Bid,
74 maker_amount: params.maker_amount,
75 taker_amount: params.taker_amount,
76 expiration: params.expiration,
77 signature: [0u8; 64],
78 }
79 }
80
81 pub fn new_ask(params: AskOrderParams) -> Self {
83 Self {
84 nonce: params.nonce,
85 maker: params.maker,
86 market: params.market,
87 base_mint: params.base_mint,
88 quote_mint: params.quote_mint,
89 side: OrderSide::Ask,
90 maker_amount: params.maker_amount,
91 taker_amount: params.taker_amount,
92 expiration: params.expiration,
93 signature: [0u8; 64],
94 }
95 }
96
97 pub fn hash(&self) -> [u8; 32] {
110 let mut hasher = Keccak256::new();
111
112 hasher.update(self.nonce.to_le_bytes());
113 hasher.update(self.maker.as_ref());
114 hasher.update(self.market.as_ref());
115 hasher.update(self.base_mint.as_ref());
116 hasher.update(self.quote_mint.as_ref());
117 hasher.update([self.side as u8]);
118 hasher.update(self.maker_amount.to_le_bytes());
119 hasher.update(self.taker_amount.to_le_bytes());
120 hasher.update(self.expiration.to_le_bytes());
121
122 hasher.finalize().into()
123 }
124
125 #[cfg(feature = "client")]
127 pub fn sign(&mut self, keypair: &Keypair) {
128 let hash = self.hash();
129 let sig = keypair.sign_message(&hash);
130 self.signature.copy_from_slice(sig.as_ref());
131 }
132
133 #[cfg(feature = "client")]
135 pub fn new_bid_signed(params: BidOrderParams, keypair: &Keypair) -> Self {
136 let mut order = Self::new_bid(params);
137 order.sign(keypair);
138 order
139 }
140
141 #[cfg(feature = "client")]
143 pub fn new_ask_signed(params: AskOrderParams, keypair: &Keypair) -> Self {
144 let mut order = Self::new_ask(params);
145 order.sign(keypair);
146 order
147 }
148
149 pub fn verify_signature(&self) -> SdkResult<()> {
151 let hash: [u8; 32] = self.hash();
152
153 let sig = Signature::try_from(self.signature.as_slice())
154 .map_err(|_| SdkError::InvalidSignature)?;
155
156 if !sig.verify(self.maker.as_ref(), &hash) {
157 return Err(SdkError::SignatureVerificationFailed);
158 }
159 Ok(())
160 }
161
162 pub fn apply_signature(&mut self, signature: &[u8]) -> SdkResult<()> {
164 self.signature = signature
165 .try_into()
166 .map_err(|_| SdkError::InvalidSignature)?;
167 Ok(())
168 }
169
170 pub fn serialize(&self) -> [u8; FULL_ORDER_SIZE] {
172 let mut data = [0u8; FULL_ORDER_SIZE];
173
174 data[0..8].copy_from_slice(&self.nonce.to_le_bytes());
175 data[8..40].copy_from_slice(self.maker.as_ref());
176 data[40..72].copy_from_slice(self.market.as_ref());
177 data[72..104].copy_from_slice(self.base_mint.as_ref());
178 data[104..136].copy_from_slice(self.quote_mint.as_ref());
179 data[136] = self.side as u8;
180 data[137..145].copy_from_slice(&self.maker_amount.to_le_bytes());
181 data[145..153].copy_from_slice(&self.taker_amount.to_le_bytes());
182 data[153..161].copy_from_slice(&self.expiration.to_le_bytes());
183 data[161..225].copy_from_slice(&self.signature);
184
185 data
186 }
187
188 pub fn deserialize(data: &[u8]) -> SdkResult<Self> {
190 if data.len() < FULL_ORDER_SIZE {
191 return Err(SdkError::InvalidDataLength {
192 expected: FULL_ORDER_SIZE,
193 actual: data.len(),
194 });
195 }
196
197 let mut nonce_bytes = [0u8; 8];
198 nonce_bytes.copy_from_slice(&data[0..8]);
199
200 let mut maker_bytes = [0u8; 32];
201 maker_bytes.copy_from_slice(&data[8..40]);
202
203 let mut market_bytes = [0u8; 32];
204 market_bytes.copy_from_slice(&data[40..72]);
205
206 let mut base_mint_bytes = [0u8; 32];
207 base_mint_bytes.copy_from_slice(&data[72..104]);
208
209 let mut quote_mint_bytes = [0u8; 32];
210 quote_mint_bytes.copy_from_slice(&data[104..136]);
211
212 let mut maker_amount_bytes = [0u8; 8];
213 maker_amount_bytes.copy_from_slice(&data[137..145]);
214
215 let mut taker_amount_bytes = [0u8; 8];
216 taker_amount_bytes.copy_from_slice(&data[145..153]);
217
218 let mut expiration_bytes = [0u8; 8];
219 expiration_bytes.copy_from_slice(&data[153..161]);
220
221 let mut signature = [0u8; 64];
222 signature.copy_from_slice(&data[161..225]);
223
224 Ok(Self {
225 nonce: u64::from_le_bytes(nonce_bytes),
226 maker: Pubkey::new_from_array(maker_bytes),
227 market: Pubkey::new_from_array(market_bytes),
228 base_mint: Pubkey::new_from_array(base_mint_bytes),
229 quote_mint: Pubkey::new_from_array(quote_mint_bytes),
230 side: OrderSide::try_from(data[136])?,
231 maker_amount: u64::from_le_bytes(maker_amount_bytes),
232 taker_amount: u64::from_le_bytes(taker_amount_bytes),
233 expiration: i64::from_le_bytes(expiration_bytes),
234 signature,
235 })
236 }
237
238 pub fn to_compact(&self) -> CompactOrder {
240 CompactOrder {
241 nonce: self.nonce,
242 maker: self.maker,
243 side: self.side,
244 maker_amount: self.maker_amount,
245 taker_amount: self.taker_amount,
246 expiration: self.expiration,
247 }
248 }
249
250 pub fn signature_hex(&self) -> String {
252 hex::encode(self.signature)
253 }
254
255 pub fn hash_hex(&self) -> String {
257 hex::encode(self.hash())
258 }
259
260 pub fn is_signed(&self) -> bool {
262 self.signature != [0u8; 64]
263 }
264
265 pub fn to_submit_request(&self, orderbook_id: impl Into<String>) -> SubmitOrderRequest {
291 assert!(
292 self.signature != [0u8; 64],
293 "Order must be signed before converting to submit request"
294 );
295
296 SubmitOrderRequest {
297 maker: self.maker.to_string(),
298 nonce: self.nonce,
299 market_pubkey: self.market.to_string(),
300 base_token: self.base_mint.to_string(),
301 quote_token: self.quote_mint.to_string(),
302 side: self.side as u32,
303 maker_amount: self.maker_amount,
304 taker_amount: self.taker_amount,
305 expiration: self.expiration,
306 signature: hex::encode(self.signature),
307 orderbook_id: orderbook_id.into(),
308 }
309 }
310
311 pub fn derive_orderbook_id(&self) -> String {
315 crate::shared::derive_orderbook_id(
316 &self.base_mint.to_string(),
317 &self.quote_mint.to_string(),
318 )
319 }
320}
321
322#[derive(Debug, Clone)]
338pub struct CompactOrder {
339 pub nonce: u64,
341 pub maker: Pubkey,
343 pub side: OrderSide,
345 pub maker_amount: u64,
347 pub taker_amount: u64,
349 pub expiration: i64,
351}
352
353impl CompactOrder {
354 pub const LEN: usize = COMPACT_ORDER_SIZE;
356
357 pub fn serialize(&self) -> [u8; COMPACT_ORDER_SIZE] {
359 let mut data = [0u8; COMPACT_ORDER_SIZE];
360
361 data[0..8].copy_from_slice(&self.nonce.to_le_bytes());
362 data[8..40].copy_from_slice(self.maker.as_ref());
363 data[40] = self.side as u8;
364 data[41..49].copy_from_slice(&self.maker_amount.to_le_bytes());
365 data[49..57].copy_from_slice(&self.taker_amount.to_le_bytes());
366 data[57..65].copy_from_slice(&self.expiration.to_le_bytes());
367
368 data
369 }
370
371 pub fn deserialize(data: &[u8]) -> SdkResult<Self> {
373 if data.len() < COMPACT_ORDER_SIZE {
374 return Err(SdkError::InvalidDataLength {
375 expected: COMPACT_ORDER_SIZE,
376 actual: data.len(),
377 });
378 }
379
380 let mut nonce_bytes = [0u8; 8];
381 nonce_bytes.copy_from_slice(&data[0..8]);
382
383 let mut maker_bytes = [0u8; 32];
384 maker_bytes.copy_from_slice(&data[8..40]);
385
386 let mut maker_amount_bytes = [0u8; 8];
387 maker_amount_bytes.copy_from_slice(&data[41..49]);
388
389 let mut taker_amount_bytes = [0u8; 8];
390 taker_amount_bytes.copy_from_slice(&data[49..57]);
391
392 let mut expiration_bytes = [0u8; 8];
393 expiration_bytes.copy_from_slice(&data[57..65]);
394
395 Ok(Self {
396 nonce: u64::from_le_bytes(nonce_bytes),
397 maker: Pubkey::new_from_array(maker_bytes),
398 side: OrderSide::try_from(data[40])?,
399 maker_amount: u64::from_le_bytes(maker_amount_bytes),
400 taker_amount: u64::from_le_bytes(taker_amount_bytes),
401 expiration: i64::from_le_bytes(expiration_bytes),
402 })
403 }
404
405 pub fn to_full_order(
407 &self,
408 market: Pubkey,
409 base_mint: Pubkey,
410 quote_mint: Pubkey,
411 signature: [u8; 64],
412 ) -> FullOrder {
413 FullOrder {
414 nonce: self.nonce,
415 maker: self.maker,
416 market,
417 base_mint,
418 quote_mint,
419 side: self.side,
420 maker_amount: self.maker_amount,
421 taker_amount: self.taker_amount,
422 expiration: self.expiration,
423 signature,
424 }
425 }
426}
427
428pub fn is_order_expired(order: &FullOrder, current_time: i64) -> bool {
434 order.expiration != 0 && current_time >= order.expiration
435}
436
437pub fn orders_can_cross(buy_order: &FullOrder, sell_order: &FullOrder) -> bool {
441 if buy_order.side != OrderSide::Bid || sell_order.side != OrderSide::Ask {
442 return false;
443 }
444
445 if buy_order.maker_amount == 0
446 || buy_order.taker_amount == 0
447 || sell_order.maker_amount == 0
448 || sell_order.taker_amount == 0
449 {
450 return false;
451 }
452
453 let buyer_cross = (buy_order.maker_amount as u128) * (sell_order.maker_amount as u128);
462 let seller_cross = (buy_order.taker_amount as u128) * (sell_order.taker_amount as u128);
463
464 buyer_cross >= seller_cross
465}
466
467pub fn calculate_taker_fill(maker_order: &FullOrder, maker_fill_amount: u64) -> SdkResult<u64> {
469 if maker_order.maker_amount == 0 {
470 return Err(SdkError::Overflow);
471 }
472
473 let result = (maker_fill_amount as u128)
474 .checked_mul(maker_order.taker_amount as u128)
475 .ok_or(SdkError::Overflow)?
476 .checked_div(maker_order.maker_amount as u128)
477 .ok_or(SdkError::Overflow)?;
478
479 if result > u64::MAX as u128 {
480 return Err(SdkError::Overflow);
481 }
482
483 Ok(result as u64)
484}
485
486pub fn derive_condition_id(oracle: &Pubkey, question_id: &[u8; 32], num_outcomes: u8) -> [u8; 32] {
488 let mut hasher = Keccak256::new();
489 hasher.update(oracle.as_ref());
490 hasher.update(question_id);
491 hasher.update([num_outcomes]);
492 hasher.finalize().into()
493}
494
495#[cfg(test)]
496mod tests {
497 use super::*;
498
499 #[test]
500 fn test_order_serialization_roundtrip() {
501 let order = FullOrder {
502 nonce: 12345,
503 maker: Pubkey::new_unique(),
504 market: Pubkey::new_unique(),
505 base_mint: Pubkey::new_unique(),
506 quote_mint: Pubkey::new_unique(),
507 side: OrderSide::Bid,
508 maker_amount: 1000000,
509 taker_amount: 500000,
510 expiration: 1234567890,
511 signature: [0u8; 64],
512 };
513
514 let serialized = order.serialize();
515 let deserialized = FullOrder::deserialize(&serialized).unwrap();
516
517 assert_eq!(order.nonce, deserialized.nonce);
518 assert_eq!(order.maker, deserialized.maker);
519 assert_eq!(order.market, deserialized.market);
520 assert_eq!(order.base_mint, deserialized.base_mint);
521 assert_eq!(order.quote_mint, deserialized.quote_mint);
522 assert_eq!(order.side, deserialized.side);
523 assert_eq!(order.maker_amount, deserialized.maker_amount);
524 assert_eq!(order.taker_amount, deserialized.taker_amount);
525 assert_eq!(order.expiration, deserialized.expiration);
526 }
527
528 #[test]
529 fn test_compact_order_serialization_roundtrip() {
530 let order = CompactOrder {
531 nonce: 12345,
532 maker: Pubkey::new_unique(),
533 side: OrderSide::Ask,
534 maker_amount: 1000000,
535 taker_amount: 500000,
536 expiration: 1234567890,
537 };
538
539 let serialized = order.serialize();
540 let deserialized = CompactOrder::deserialize(&serialized).unwrap();
541
542 assert_eq!(order.nonce, deserialized.nonce);
543 assert_eq!(order.maker, deserialized.maker);
544 assert_eq!(order.side, deserialized.side);
545 assert_eq!(order.maker_amount, deserialized.maker_amount);
546 assert_eq!(order.taker_amount, deserialized.taker_amount);
547 assert_eq!(order.expiration, deserialized.expiration);
548 }
549
550 #[test]
551 fn test_order_hash_consistency() {
552 let order = FullOrder {
553 nonce: 1,
554 maker: Pubkey::new_from_array([1u8; 32]),
555 market: Pubkey::new_from_array([2u8; 32]),
556 base_mint: Pubkey::new_from_array([3u8; 32]),
557 quote_mint: Pubkey::new_from_array([4u8; 32]),
558 side: OrderSide::Bid,
559 maker_amount: 100,
560 taker_amount: 50,
561 expiration: 0,
562 signature: [0u8; 64],
563 };
564
565 let hash1 = order.hash();
566 let hash2 = order.hash();
567 assert_eq!(hash1, hash2);
568 }
569
570 #[test]
571 fn test_orders_can_cross() {
572 let buy_order = FullOrder {
573 nonce: 1,
574 maker: Pubkey::new_unique(),
575 market: Pubkey::new_unique(),
576 base_mint: Pubkey::new_unique(),
577 quote_mint: Pubkey::new_unique(),
578 side: OrderSide::Bid,
579 maker_amount: 100, taker_amount: 50, expiration: 0,
582 signature: [0u8; 64],
583 };
584
585 let sell_order = FullOrder {
586 nonce: 2,
587 maker: Pubkey::new_unique(),
588 market: buy_order.market,
589 base_mint: buy_order.base_mint,
590 quote_mint: buy_order.quote_mint,
591 side: OrderSide::Ask,
592 maker_amount: 50, taker_amount: 90, expiration: 0,
595 signature: [0u8; 64],
596 };
597
598 assert!(orders_can_cross(&buy_order, &sell_order));
600 }
601
602 #[test]
603 fn test_orders_cannot_cross() {
604 let buy_order = FullOrder {
605 nonce: 1,
606 maker: Pubkey::new_unique(),
607 market: Pubkey::new_unique(),
608 base_mint: Pubkey::new_unique(),
609 quote_mint: Pubkey::new_unique(),
610 side: OrderSide::Bid,
611 maker_amount: 50, taker_amount: 50, expiration: 0,
614 signature: [0u8; 64],
615 };
616
617 let sell_order = FullOrder {
618 nonce: 2,
619 maker: Pubkey::new_unique(),
620 market: buy_order.market,
621 base_mint: buy_order.base_mint,
622 quote_mint: buy_order.quote_mint,
623 side: OrderSide::Ask,
624 maker_amount: 50, taker_amount: 100, expiration: 0,
627 signature: [0u8; 64],
628 };
629
630 assert!(!orders_can_cross(&buy_order, &sell_order));
632 }
633
634 #[test]
635 fn test_calculate_taker_fill() {
636 let maker_order = FullOrder {
637 nonce: 1,
638 maker: Pubkey::new_unique(),
639 market: Pubkey::new_unique(),
640 base_mint: Pubkey::new_unique(),
641 quote_mint: Pubkey::new_unique(),
642 side: OrderSide::Ask,
643 maker_amount: 100, taker_amount: 200, expiration: 0,
646 signature: [0u8; 64],
647 };
648
649 let taker_fill = calculate_taker_fill(&maker_order, 50).unwrap();
651 assert_eq!(taker_fill, 100);
652 }
653
654 #[test]
655 #[cfg(feature = "client")]
656 fn test_to_submit_request() {
657 use solana_keypair::Keypair;
658 use solana_signer::Signer;
659
660 let keypair = Keypair::new();
661 let maker = keypair.pubkey();
662 let market = Pubkey::new_unique();
663 let base_mint = Pubkey::new_unique();
664 let quote_mint = Pubkey::new_unique();
665
666 let mut order = FullOrder {
667 nonce: 42,
668 maker,
669 market,
670 base_mint,
671 quote_mint,
672 side: OrderSide::Bid,
673 maker_amount: 1_000_000,
674 taker_amount: 500_000,
675 expiration: 1234567890,
676 signature: [0u8; 64],
677 };
678
679 order.sign(&keypair);
680
681 let request = order.to_submit_request("test_orderbook");
682
683 assert_eq!(request.maker, maker.to_string());
684 assert_eq!(request.nonce, 42);
685 assert_eq!(request.market_pubkey, market.to_string());
686 assert_eq!(request.base_token, base_mint.to_string());
687 assert_eq!(request.quote_token, quote_mint.to_string());
688 assert_eq!(request.side, 0); assert_eq!(request.maker_amount, 1_000_000);
690 assert_eq!(request.taker_amount, 500_000);
691 assert_eq!(request.expiration, 1234567890);
692 assert_eq!(request.orderbook_id, "test_orderbook");
693 assert_eq!(request.signature.len(), 128); }
695
696 #[test]
697 fn test_derive_orderbook_id() {
698 let order = FullOrder {
699 nonce: 1,
700 maker: Pubkey::new_from_array([1u8; 32]),
701 market: Pubkey::new_from_array([2u8; 32]),
702 base_mint: Pubkey::new_from_array([3u8; 32]),
703 quote_mint: Pubkey::new_from_array([4u8; 32]),
704 side: OrderSide::Bid,
705 maker_amount: 100,
706 taker_amount: 50,
707 expiration: 0,
708 signature: [0u8; 64],
709 };
710
711 let orderbook_id = order.derive_orderbook_id();
712 let base_str = order.base_mint.to_string();
714 let quote_str = order.quote_mint.to_string();
715 let expected = format!("{}_{}", &base_str[..8], "e_str[..8]);
716 assert_eq!(orderbook_id, expected);
717 }
718
719 #[test]
720 #[cfg(feature = "client")]
721 fn test_is_signed() {
722 use solana_keypair::Keypair;
723 use solana_signer::Signer;
724
725 let keypair = Keypair::new();
726 let mut order = FullOrder {
727 nonce: 1,
728 maker: keypair.pubkey(),
729 market: Pubkey::new_unique(),
730 base_mint: Pubkey::new_unique(),
731 quote_mint: Pubkey::new_unique(),
732 side: OrderSide::Bid,
733 maker_amount: 100,
734 taker_amount: 50,
735 expiration: 0,
736 signature: [0u8; 64],
737 };
738
739 assert!(!order.is_signed());
740
741 order.sign(&keypair);
742
743 assert!(order.is_signed());
744 }
745
746 #[test]
747 #[cfg(feature = "client")]
748 fn test_signature_and_hash_hex() {
749 use solana_keypair::Keypair;
750 use solana_signer::Signer;
751
752 let keypair = Keypair::new();
753 let mut order = FullOrder {
754 nonce: 1,
755 maker: keypair.pubkey(),
756 market: Pubkey::new_unique(),
757 base_mint: Pubkey::new_unique(),
758 quote_mint: Pubkey::new_unique(),
759 side: OrderSide::Bid,
760 maker_amount: 100,
761 taker_amount: 50,
762 expiration: 0,
763 signature: [0u8; 64],
764 };
765
766 order.sign(&keypair);
767
768 let sig_hex = order.signature_hex();
769 let hash_hex = order.hash_hex();
770
771 assert_eq!(sig_hex.len(), 128);
773 assert_eq!(hash_hex.len(), 64);
775
776 assert!(hex::decode(&sig_hex).is_ok());
778 assert!(hex::decode(&hash_hex).is_ok());
779 }
780
781 #[test]
782 #[should_panic(expected = "Order must be signed before converting to submit request")]
783 fn test_to_submit_request_panics_unsigned() {
784 let order = FullOrder {
785 nonce: 1,
786 maker: Pubkey::new_unique(),
787 market: Pubkey::new_unique(),
788 base_mint: Pubkey::new_unique(),
789 quote_mint: Pubkey::new_unique(),
790 side: OrderSide::Bid,
791 maker_amount: 100,
792 taker_amount: 50,
793 expiration: 0,
794 signature: [0u8; 64],
795 };
796
797 order.to_submit_request("test_orderbook");
798 }
799}