1use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
7use sha3::{Digest, Keccak256};
8use solana_sdk::{pubkey::Pubkey, signature::Keypair};
9
10use crate::program::constants::{COMPACT_ORDER_SIZE, FULL_ORDER_SIZE};
11use crate::program::error::{SdkError, SdkResult};
12use crate::program::types::{AskOrderParams, BidOrderParams, OrderSide};
13use crate::shared::SubmitOrderRequest;
14
15#[derive(Debug, Clone)]
33pub struct FullOrder {
34 pub nonce: u64,
36 pub maker: Pubkey,
38 pub market: Pubkey,
40 pub base_mint: Pubkey,
42 pub quote_mint: Pubkey,
44 pub side: OrderSide,
46 pub maker_amount: u64,
48 pub taker_amount: u64,
50 pub expiration: i64,
52 pub signature: [u8; 64],
54}
55
56impl FullOrder {
57 pub const LEN: usize = FULL_ORDER_SIZE;
59
60 pub fn new_bid(params: BidOrderParams) -> Self {
62 Self {
63 nonce: params.nonce,
64 maker: params.maker,
65 market: params.market,
66 base_mint: params.base_mint,
67 quote_mint: params.quote_mint,
68 side: OrderSide::Bid,
69 maker_amount: params.maker_amount,
70 taker_amount: params.taker_amount,
71 expiration: params.expiration,
72 signature: [0u8; 64],
73 }
74 }
75
76 pub fn new_ask(params: AskOrderParams) -> Self {
78 Self {
79 nonce: params.nonce,
80 maker: params.maker,
81 market: params.market,
82 base_mint: params.base_mint,
83 quote_mint: params.quote_mint,
84 side: OrderSide::Ask,
85 maker_amount: params.maker_amount,
86 taker_amount: params.taker_amount,
87 expiration: params.expiration,
88 signature: [0u8; 64],
89 }
90 }
91
92 pub fn hash(&self) -> [u8; 32] {
105 let mut hasher = Keccak256::new();
106
107 hasher.update(self.nonce.to_le_bytes());
108 hasher.update(self.maker.as_ref());
109 hasher.update(self.market.as_ref());
110 hasher.update(self.base_mint.as_ref());
111 hasher.update(self.quote_mint.as_ref());
112 hasher.update([self.side as u8]);
113 hasher.update(self.maker_amount.to_le_bytes());
114 hasher.update(self.taker_amount.to_le_bytes());
115 hasher.update(self.expiration.to_le_bytes());
116
117 hasher.finalize().into()
118 }
119
120 pub fn sign(&mut self, keypair: &Keypair) {
122 let hash = self.hash();
123 let signing_key = SigningKey::from_bytes(keypair.secret_bytes());
124 let signature = signing_key.sign(&hash);
125 self.signature = signature.to_bytes();
126 }
127
128 pub fn new_bid_signed(params: BidOrderParams, keypair: &Keypair) -> Self {
130 let mut order = Self::new_bid(params);
131 order.sign(keypair);
132 order
133 }
134
135 pub fn new_ask_signed(params: AskOrderParams, keypair: &Keypair) -> Self {
137 let mut order = Self::new_ask(params);
138 order.sign(keypair);
139 order
140 }
141
142 pub fn verify_signature(&self) -> SdkResult<bool> {
144 let hash = self.hash();
145 let pubkey_bytes: &[u8; 32] = self.maker.as_ref().try_into()
146 .map_err(|_| SdkError::InvalidPubkey("Invalid maker pubkey".to_string()))?;
147 let verifying_key = VerifyingKey::from_bytes(pubkey_bytes)
148 .map_err(|_| SdkError::InvalidPubkey("Invalid maker pubkey".to_string()))?;
149 let signature = Signature::from_bytes(&self.signature);
150
151 Ok(verifying_key.verify(&hash, &signature).is_ok())
152 }
153
154 pub fn serialize(&self) -> [u8; FULL_ORDER_SIZE] {
156 let mut data = [0u8; FULL_ORDER_SIZE];
157
158 data[0..8].copy_from_slice(&self.nonce.to_le_bytes());
159 data[8..40].copy_from_slice(self.maker.as_ref());
160 data[40..72].copy_from_slice(self.market.as_ref());
161 data[72..104].copy_from_slice(self.base_mint.as_ref());
162 data[104..136].copy_from_slice(self.quote_mint.as_ref());
163 data[136] = self.side as u8;
164 data[137..145].copy_from_slice(&self.maker_amount.to_le_bytes());
165 data[145..153].copy_from_slice(&self.taker_amount.to_le_bytes());
166 data[153..161].copy_from_slice(&self.expiration.to_le_bytes());
167 data[161..225].copy_from_slice(&self.signature);
168
169 data
170 }
171
172 pub fn deserialize(data: &[u8]) -> SdkResult<Self> {
174 if data.len() < FULL_ORDER_SIZE {
175 return Err(SdkError::InvalidDataLength {
176 expected: FULL_ORDER_SIZE,
177 actual: data.len(),
178 });
179 }
180
181 let mut nonce_bytes = [0u8; 8];
182 nonce_bytes.copy_from_slice(&data[0..8]);
183
184 let mut maker_bytes = [0u8; 32];
185 maker_bytes.copy_from_slice(&data[8..40]);
186
187 let mut market_bytes = [0u8; 32];
188 market_bytes.copy_from_slice(&data[40..72]);
189
190 let mut base_mint_bytes = [0u8; 32];
191 base_mint_bytes.copy_from_slice(&data[72..104]);
192
193 let mut quote_mint_bytes = [0u8; 32];
194 quote_mint_bytes.copy_from_slice(&data[104..136]);
195
196 let mut maker_amount_bytes = [0u8; 8];
197 maker_amount_bytes.copy_from_slice(&data[137..145]);
198
199 let mut taker_amount_bytes = [0u8; 8];
200 taker_amount_bytes.copy_from_slice(&data[145..153]);
201
202 let mut expiration_bytes = [0u8; 8];
203 expiration_bytes.copy_from_slice(&data[153..161]);
204
205 let mut signature = [0u8; 64];
206 signature.copy_from_slice(&data[161..225]);
207
208 Ok(Self {
209 nonce: u64::from_le_bytes(nonce_bytes),
210 maker: Pubkey::new_from_array(maker_bytes),
211 market: Pubkey::new_from_array(market_bytes),
212 base_mint: Pubkey::new_from_array(base_mint_bytes),
213 quote_mint: Pubkey::new_from_array(quote_mint_bytes),
214 side: OrderSide::try_from(data[136])?,
215 maker_amount: u64::from_le_bytes(maker_amount_bytes),
216 taker_amount: u64::from_le_bytes(taker_amount_bytes),
217 expiration: i64::from_le_bytes(expiration_bytes),
218 signature,
219 })
220 }
221
222 pub fn to_compact(&self) -> CompactOrder {
224 CompactOrder {
225 nonce: self.nonce,
226 maker: self.maker,
227 side: self.side,
228 maker_amount: self.maker_amount,
229 taker_amount: self.taker_amount,
230 expiration: self.expiration,
231 }
232 }
233
234 pub fn to_submit_request(&self, orderbook_id: impl Into<String>) -> SubmitOrderRequest {
260 assert!(
261 self.signature != [0u8; 64],
262 "Order must be signed before converting to submit request"
263 );
264
265 SubmitOrderRequest {
266 maker: self.maker.to_string(),
267 nonce: self.nonce,
268 market_pubkey: self.market.to_string(),
269 base_token: self.base_mint.to_string(),
270 quote_token: self.quote_mint.to_string(),
271 side: self.side as u32,
272 maker_amount: self.maker_amount,
273 taker_amount: self.taker_amount,
274 expiration: self.expiration,
275 signature: hex::encode(self.signature),
276 orderbook_id: orderbook_id.into(),
277 }
278 }
279
280 pub fn derive_orderbook_id(&self) -> String {
284 crate::shared::derive_orderbook_id(
285 &self.base_mint.to_string(),
286 &self.quote_mint.to_string(),
287 )
288 }
289
290 pub fn signature_hex(&self) -> String {
292 hex::encode(self.signature)
293 }
294
295 pub fn hash_hex(&self) -> String {
297 hex::encode(self.hash())
298 }
299
300 pub fn is_signed(&self) -> bool {
302 self.signature != [0u8; 64]
303 }
304}
305
306#[derive(Debug, Clone)]
322pub struct CompactOrder {
323 pub nonce: u64,
325 pub maker: Pubkey,
327 pub side: OrderSide,
329 pub maker_amount: u64,
331 pub taker_amount: u64,
333 pub expiration: i64,
335}
336
337impl CompactOrder {
338 pub const LEN: usize = COMPACT_ORDER_SIZE;
340
341 pub fn serialize(&self) -> [u8; COMPACT_ORDER_SIZE] {
343 let mut data = [0u8; COMPACT_ORDER_SIZE];
344
345 data[0..8].copy_from_slice(&self.nonce.to_le_bytes());
346 data[8..40].copy_from_slice(self.maker.as_ref());
347 data[40] = self.side as u8;
348 data[41..49].copy_from_slice(&self.maker_amount.to_le_bytes());
349 data[49..57].copy_from_slice(&self.taker_amount.to_le_bytes());
350 data[57..65].copy_from_slice(&self.expiration.to_le_bytes());
351
352 data
353 }
354
355 pub fn deserialize(data: &[u8]) -> SdkResult<Self> {
357 if data.len() < COMPACT_ORDER_SIZE {
358 return Err(SdkError::InvalidDataLength {
359 expected: COMPACT_ORDER_SIZE,
360 actual: data.len(),
361 });
362 }
363
364 let mut nonce_bytes = [0u8; 8];
365 nonce_bytes.copy_from_slice(&data[0..8]);
366
367 let mut maker_bytes = [0u8; 32];
368 maker_bytes.copy_from_slice(&data[8..40]);
369
370 let mut maker_amount_bytes = [0u8; 8];
371 maker_amount_bytes.copy_from_slice(&data[41..49]);
372
373 let mut taker_amount_bytes = [0u8; 8];
374 taker_amount_bytes.copy_from_slice(&data[49..57]);
375
376 let mut expiration_bytes = [0u8; 8];
377 expiration_bytes.copy_from_slice(&data[57..65]);
378
379 Ok(Self {
380 nonce: u64::from_le_bytes(nonce_bytes),
381 maker: Pubkey::new_from_array(maker_bytes),
382 side: OrderSide::try_from(data[40])?,
383 maker_amount: u64::from_le_bytes(maker_amount_bytes),
384 taker_amount: u64::from_le_bytes(taker_amount_bytes),
385 expiration: i64::from_le_bytes(expiration_bytes),
386 })
387 }
388
389 pub fn to_full_order(
391 &self,
392 market: Pubkey,
393 base_mint: Pubkey,
394 quote_mint: Pubkey,
395 signature: [u8; 64],
396 ) -> FullOrder {
397 FullOrder {
398 nonce: self.nonce,
399 maker: self.maker,
400 market,
401 base_mint,
402 quote_mint,
403 side: self.side,
404 maker_amount: self.maker_amount,
405 taker_amount: self.taker_amount,
406 expiration: self.expiration,
407 signature,
408 }
409 }
410}
411
412pub fn is_order_expired(order: &FullOrder, current_time: i64) -> bool {
418 order.expiration != 0 && current_time >= order.expiration
419}
420
421pub fn orders_can_cross(buy_order: &FullOrder, sell_order: &FullOrder) -> bool {
425 if buy_order.side != OrderSide::Bid || sell_order.side != OrderSide::Ask {
426 return false;
427 }
428
429 if buy_order.maker_amount == 0
430 || buy_order.taker_amount == 0
431 || sell_order.maker_amount == 0
432 || sell_order.taker_amount == 0
433 {
434 return false;
435 }
436
437 let buyer_cross = (buy_order.maker_amount as u128) * (sell_order.maker_amount as u128);
446 let seller_cross = (buy_order.taker_amount as u128) * (sell_order.taker_amount as u128);
447
448 buyer_cross >= seller_cross
449}
450
451pub fn calculate_taker_fill(maker_order: &FullOrder, maker_fill_amount: u64) -> SdkResult<u64> {
453 if maker_order.maker_amount == 0 {
454 return Err(SdkError::Overflow);
455 }
456
457 let result = (maker_fill_amount as u128)
458 .checked_mul(maker_order.taker_amount as u128)
459 .ok_or(SdkError::Overflow)?
460 .checked_div(maker_order.maker_amount as u128)
461 .ok_or(SdkError::Overflow)?;
462
463 if result > u64::MAX as u128 {
464 return Err(SdkError::Overflow);
465 }
466
467 Ok(result as u64)
468}
469
470pub fn derive_condition_id(oracle: &Pubkey, question_id: &[u8; 32], num_outcomes: u8) -> [u8; 32] {
472 let mut hasher = Keccak256::new();
473 hasher.update(oracle.as_ref());
474 hasher.update(question_id);
475 hasher.update([num_outcomes]);
476 hasher.finalize().into()
477}
478
479#[cfg(test)]
480mod tests {
481 use super::*;
482
483 #[test]
484 fn test_order_serialization_roundtrip() {
485 let order = FullOrder {
486 nonce: 12345,
487 maker: Pubkey::new_unique(),
488 market: Pubkey::new_unique(),
489 base_mint: Pubkey::new_unique(),
490 quote_mint: Pubkey::new_unique(),
491 side: OrderSide::Bid,
492 maker_amount: 1000000,
493 taker_amount: 500000,
494 expiration: 1234567890,
495 signature: [0u8; 64],
496 };
497
498 let serialized = order.serialize();
499 let deserialized = FullOrder::deserialize(&serialized).unwrap();
500
501 assert_eq!(order.nonce, deserialized.nonce);
502 assert_eq!(order.maker, deserialized.maker);
503 assert_eq!(order.market, deserialized.market);
504 assert_eq!(order.base_mint, deserialized.base_mint);
505 assert_eq!(order.quote_mint, deserialized.quote_mint);
506 assert_eq!(order.side, deserialized.side);
507 assert_eq!(order.maker_amount, deserialized.maker_amount);
508 assert_eq!(order.taker_amount, deserialized.taker_amount);
509 assert_eq!(order.expiration, deserialized.expiration);
510 }
511
512 #[test]
513 fn test_compact_order_serialization_roundtrip() {
514 let order = CompactOrder {
515 nonce: 12345,
516 maker: Pubkey::new_unique(),
517 side: OrderSide::Ask,
518 maker_amount: 1000000,
519 taker_amount: 500000,
520 expiration: 1234567890,
521 };
522
523 let serialized = order.serialize();
524 let deserialized = CompactOrder::deserialize(&serialized).unwrap();
525
526 assert_eq!(order.nonce, deserialized.nonce);
527 assert_eq!(order.maker, deserialized.maker);
528 assert_eq!(order.side, deserialized.side);
529 assert_eq!(order.maker_amount, deserialized.maker_amount);
530 assert_eq!(order.taker_amount, deserialized.taker_amount);
531 assert_eq!(order.expiration, deserialized.expiration);
532 }
533
534 #[test]
535 fn test_order_hash_consistency() {
536 let order = FullOrder {
537 nonce: 1,
538 maker: Pubkey::new_from_array([1u8; 32]),
539 market: Pubkey::new_from_array([2u8; 32]),
540 base_mint: Pubkey::new_from_array([3u8; 32]),
541 quote_mint: Pubkey::new_from_array([4u8; 32]),
542 side: OrderSide::Bid,
543 maker_amount: 100,
544 taker_amount: 50,
545 expiration: 0,
546 signature: [0u8; 64],
547 };
548
549 let hash1 = order.hash();
550 let hash2 = order.hash();
551 assert_eq!(hash1, hash2);
552 }
553
554 #[test]
555 fn test_orders_can_cross() {
556 let buy_order = FullOrder {
557 nonce: 1,
558 maker: Pubkey::new_unique(),
559 market: Pubkey::new_unique(),
560 base_mint: Pubkey::new_unique(),
561 quote_mint: Pubkey::new_unique(),
562 side: OrderSide::Bid,
563 maker_amount: 100, taker_amount: 50, expiration: 0,
566 signature: [0u8; 64],
567 };
568
569 let sell_order = FullOrder {
570 nonce: 2,
571 maker: Pubkey::new_unique(),
572 market: buy_order.market,
573 base_mint: buy_order.base_mint,
574 quote_mint: buy_order.quote_mint,
575 side: OrderSide::Ask,
576 maker_amount: 50, taker_amount: 90, expiration: 0,
579 signature: [0u8; 64],
580 };
581
582 assert!(orders_can_cross(&buy_order, &sell_order));
584 }
585
586 #[test]
587 fn test_orders_cannot_cross() {
588 let buy_order = FullOrder {
589 nonce: 1,
590 maker: Pubkey::new_unique(),
591 market: Pubkey::new_unique(),
592 base_mint: Pubkey::new_unique(),
593 quote_mint: Pubkey::new_unique(),
594 side: OrderSide::Bid,
595 maker_amount: 50, taker_amount: 50, expiration: 0,
598 signature: [0u8; 64],
599 };
600
601 let sell_order = FullOrder {
602 nonce: 2,
603 maker: Pubkey::new_unique(),
604 market: buy_order.market,
605 base_mint: buy_order.base_mint,
606 quote_mint: buy_order.quote_mint,
607 side: OrderSide::Ask,
608 maker_amount: 50, taker_amount: 100, expiration: 0,
611 signature: [0u8; 64],
612 };
613
614 assert!(!orders_can_cross(&buy_order, &sell_order));
616 }
617
618 #[test]
619 fn test_calculate_taker_fill() {
620 let maker_order = FullOrder {
621 nonce: 1,
622 maker: Pubkey::new_unique(),
623 market: Pubkey::new_unique(),
624 base_mint: Pubkey::new_unique(),
625 quote_mint: Pubkey::new_unique(),
626 side: OrderSide::Ask,
627 maker_amount: 100, taker_amount: 200, expiration: 0,
630 signature: [0u8; 64],
631 };
632
633 let taker_fill = calculate_taker_fill(&maker_order, 50).unwrap();
635 assert_eq!(taker_fill, 100);
636 }
637
638 #[test]
639 fn test_to_submit_request() {
640 use solana_sdk::signature::Keypair;
641 use solana_sdk::signer::Signer;
642
643 let keypair = Keypair::new();
644 let maker = keypair.pubkey();
645 let market = Pubkey::new_unique();
646 let base_mint = Pubkey::new_unique();
647 let quote_mint = Pubkey::new_unique();
648
649 let mut order = FullOrder {
650 nonce: 42,
651 maker,
652 market,
653 base_mint,
654 quote_mint,
655 side: OrderSide::Bid,
656 maker_amount: 1_000_000,
657 taker_amount: 500_000,
658 expiration: 1234567890,
659 signature: [0u8; 64],
660 };
661
662 order.sign(&keypair);
663
664 let request = order.to_submit_request("test_orderbook");
665
666 assert_eq!(request.maker, maker.to_string());
667 assert_eq!(request.nonce, 42);
668 assert_eq!(request.market_pubkey, market.to_string());
669 assert_eq!(request.base_token, base_mint.to_string());
670 assert_eq!(request.quote_token, quote_mint.to_string());
671 assert_eq!(request.side, 0); assert_eq!(request.maker_amount, 1_000_000);
673 assert_eq!(request.taker_amount, 500_000);
674 assert_eq!(request.expiration, 1234567890);
675 assert_eq!(request.orderbook_id, "test_orderbook");
676 assert_eq!(request.signature.len(), 128); }
678
679 #[test]
680 fn test_derive_orderbook_id() {
681 let order = FullOrder {
682 nonce: 1,
683 maker: Pubkey::new_from_array([1u8; 32]),
684 market: Pubkey::new_from_array([2u8; 32]),
685 base_mint: Pubkey::new_from_array([3u8; 32]),
686 quote_mint: Pubkey::new_from_array([4u8; 32]),
687 side: OrderSide::Bid,
688 maker_amount: 100,
689 taker_amount: 50,
690 expiration: 0,
691 signature: [0u8; 64],
692 };
693
694 let orderbook_id = order.derive_orderbook_id();
695 let base_str = order.base_mint.to_string();
697 let quote_str = order.quote_mint.to_string();
698 let expected = format!("{}_{}", &base_str[..8], "e_str[..8]);
699 assert_eq!(orderbook_id, expected);
700 }
701
702 #[test]
703 fn test_is_signed() {
704 use solana_sdk::signature::Keypair;
705 use solana_sdk::signer::Signer;
706
707 let keypair = Keypair::new();
708 let mut order = FullOrder {
709 nonce: 1,
710 maker: keypair.pubkey(),
711 market: Pubkey::new_unique(),
712 base_mint: Pubkey::new_unique(),
713 quote_mint: Pubkey::new_unique(),
714 side: OrderSide::Bid,
715 maker_amount: 100,
716 taker_amount: 50,
717 expiration: 0,
718 signature: [0u8; 64],
719 };
720
721 assert!(!order.is_signed());
722
723 order.sign(&keypair);
724
725 assert!(order.is_signed());
726 }
727
728 #[test]
729 fn test_signature_and_hash_hex() {
730 use solana_sdk::signature::Keypair;
731 use solana_sdk::signer::Signer;
732
733 let keypair = Keypair::new();
734 let mut order = FullOrder {
735 nonce: 1,
736 maker: keypair.pubkey(),
737 market: Pubkey::new_unique(),
738 base_mint: Pubkey::new_unique(),
739 quote_mint: Pubkey::new_unique(),
740 side: OrderSide::Bid,
741 maker_amount: 100,
742 taker_amount: 50,
743 expiration: 0,
744 signature: [0u8; 64],
745 };
746
747 order.sign(&keypair);
748
749 let sig_hex = order.signature_hex();
750 let hash_hex = order.hash_hex();
751
752 assert_eq!(sig_hex.len(), 128);
754 assert_eq!(hash_hex.len(), 64);
756
757 assert!(hex::decode(&sig_hex).is_ok());
759 assert!(hex::decode(&hash_hex).is_ok());
760 }
761
762 #[test]
763 #[should_panic(expected = "Order must be signed before converting to submit request")]
764 fn test_to_submit_request_panics_unsigned() {
765 let order = FullOrder {
766 nonce: 1,
767 maker: Pubkey::new_unique(),
768 market: Pubkey::new_unique(),
769 base_mint: Pubkey::new_unique(),
770 quote_mint: Pubkey::new_unique(),
771 side: OrderSide::Bid,
772 maker_amount: 100,
773 taker_amount: 50,
774 expiration: 0,
775 signature: [0u8; 64],
776 };
777
778 order.to_submit_request("test_orderbook");
779 }
780}