1use core::mem::size_of;
17
18use arrayref::{array_refs, mut_array_refs};
19use derive_more::Deref;
20use solana_hash::Hash;
21use solana_program_error::ProgramError;
22use solana_pubkey::Pubkey;
23
24#[derive(Clone, Copy, Debug, Eq, PartialEq, Default)]
27#[repr(u8)]
28pub enum OrderKind {
29 #[default]
30 Sell = 0,
31 Buy = 1,
32}
33
34#[derive(Clone, Copy, Debug, Eq, PartialEq, Default)]
36pub struct Flags {
37 pub created_on_chain: bool,
42
43 pub kind: OrderKind,
47
48 pub partially_fillable: bool,
53}
54
55impl Flags {
56 const CREATED_ON_CHAIN: u8 = 1 << 0;
58 const KIND: u8 = 1 << 1;
59 const PARTIALLY_FILLABLE: u8 = 1 << 2;
60
61 const DEFINED: u8 = Self::CREATED_ON_CHAIN | Self::KIND | Self::PARTIALLY_FILLABLE;
63}
64
65impl From<Flags> for [u8; 1] {
66 fn from(flags: Flags) -> Self {
68 let mut byte = 0;
69 if flags.created_on_chain {
70 byte |= Flags::CREATED_ON_CHAIN;
71 }
72 if flags.kind == OrderKind::Buy {
73 byte |= Flags::KIND;
74 }
75 if flags.partially_fillable {
76 byte |= Flags::PARTIALLY_FILLABLE;
77 }
78 [byte]
79 }
80}
81
82impl TryFrom<[u8; 1]> for Flags {
83 type Error = ProgramError;
84
85 fn try_from(bytes: [u8; 1]) -> Result<Self, Self::Error> {
90 let [byte] = bytes;
91 if byte & !Self::DEFINED != 0 {
92 return Err(ProgramError::InvalidInstructionData);
93 }
94 Ok(Flags {
95 created_on_chain: byte & Self::CREATED_ON_CHAIN != 0,
96 kind: if byte & Self::KIND == 0 {
97 OrderKind::Sell
98 } else {
99 OrderKind::Buy
100 },
101 partially_fillable: byte & Self::PARTIALLY_FILLABLE != 0,
102 })
103 }
104}
105
106#[derive(Clone, Debug, Eq, PartialEq, Default)]
110pub struct OrderIntent {
111 pub owner: Pubkey,
115
116 pub sell_token_account: Pubkey,
122
123 pub sell_mint: Pubkey,
125
126 pub buy_token_account: Pubkey,
129
130 pub buy_mint: Pubkey,
132
133 pub sell_amount: u64,
137
138 pub buy_amount: u64,
142
143 pub valid_to: u32,
146
147 pub flags: Flags,
150
151 pub app_data: [u8; 32],
155}
156
157#[derive(Clone, Debug, Deref, Eq, PartialEq)]
176pub struct EncodedOrderIntent([u8; Self::SIZE]);
177
178impl EncodedOrderIntent {
179 const WIDTH_OWNER: usize = size_of::<Pubkey>();
181 const WIDTH_SELL_TOKEN: usize = size_of::<Pubkey>();
182 const WIDTH_SELL_MINT: usize = size_of::<Pubkey>();
183 const WIDTH_BUY_TOKEN: usize = size_of::<Pubkey>();
184 const WIDTH_BUY_MINT: usize = size_of::<Pubkey>();
185 const WIDTH_SELL_AMOUNT: usize = size_of::<u64>();
186 const WIDTH_BUY_AMOUNT: usize = size_of::<u64>();
187 const WIDTH_VALID_TO: usize = size_of::<u32>();
188 const WIDTH_FLAGS: usize = size_of::<u8>();
189 const WIDTH_APP_DATA: usize = size_of::<[u8; 32]>();
190
191 pub const SIZE: usize = 213;
192
193 pub fn hash(&self) -> Hash {
195 hash_bytes(&self.0)
196 }
197
198 pub fn decode_and_hash(bytes: &[u8; Self::SIZE]) -> Result<(OrderIntent, Hash), ProgramError> {
202 let intent = OrderIntent::try_from(bytes)?;
203 let uid = hash_bytes(bytes);
209 Ok((intent, uid))
210 }
211}
212
213pub fn hash_bytes(bytes: &[u8; EncodedOrderIntent::SIZE]) -> Hash {
214 solana_sha256_hasher::hashv(&[bytes.as_slice()])
215}
216
217impl From<&EncodedOrderIntent> for [u8; EncodedOrderIntent::SIZE] {
218 fn from(encoded: &EncodedOrderIntent) -> Self {
219 encoded.0
220 }
221}
222
223impl From<&OrderIntent> for EncodedOrderIntent {
224 fn from(intent: &OrderIntent) -> Self {
225 let mut out = [0u8; Self::SIZE];
228 let (
229 owner,
230 sell_token,
231 sell_mint,
232 buy_token,
233 buy_mint,
234 sell_amount,
235 buy_amount,
236 valid_to,
237 flags,
238 app_data,
239 ) = mut_array_refs![
240 &mut out,
241 EncodedOrderIntent::WIDTH_OWNER,
242 EncodedOrderIntent::WIDTH_SELL_TOKEN,
243 EncodedOrderIntent::WIDTH_SELL_MINT,
244 EncodedOrderIntent::WIDTH_BUY_TOKEN,
245 EncodedOrderIntent::WIDTH_BUY_MINT,
246 EncodedOrderIntent::WIDTH_SELL_AMOUNT,
247 EncodedOrderIntent::WIDTH_BUY_AMOUNT,
248 EncodedOrderIntent::WIDTH_VALID_TO,
249 EncodedOrderIntent::WIDTH_FLAGS,
250 EncodedOrderIntent::WIDTH_APP_DATA
251 ];
252 *owner = intent.owner.to_bytes();
253 *sell_token = intent.sell_token_account.to_bytes();
254 *sell_mint = intent.sell_mint.to_bytes();
255 *buy_token = intent.buy_token_account.to_bytes();
256 *buy_mint = intent.buy_mint.to_bytes();
257 *sell_amount = intent.sell_amount.to_le_bytes();
258 *buy_amount = intent.buy_amount.to_le_bytes();
259 *valid_to = intent.valid_to.to_le_bytes();
260 *flags = intent.flags.into();
261 *app_data = intent.app_data;
262 Self(out)
263 }
264}
265
266impl TryFrom<&[u8; EncodedOrderIntent::SIZE]> for OrderIntent {
267 type Error = ProgramError;
268
269 fn try_from(bytes: &[u8; EncodedOrderIntent::SIZE]) -> Result<Self, Self::Error> {
270 let (
278 owner,
279 sell_token,
280 sell_mint,
281 buy_token,
282 buy_mint,
283 sell_amount,
284 buy_amount,
285 valid_to,
286 flags,
287 app_data,
288 ) = array_refs![
289 bytes,
290 EncodedOrderIntent::WIDTH_OWNER,
291 EncodedOrderIntent::WIDTH_SELL_TOKEN,
292 EncodedOrderIntent::WIDTH_SELL_MINT,
293 EncodedOrderIntent::WIDTH_BUY_TOKEN,
294 EncodedOrderIntent::WIDTH_BUY_MINT,
295 EncodedOrderIntent::WIDTH_SELL_AMOUNT,
296 EncodedOrderIntent::WIDTH_BUY_AMOUNT,
297 EncodedOrderIntent::WIDTH_VALID_TO,
298 EncodedOrderIntent::WIDTH_FLAGS,
299 EncodedOrderIntent::WIDTH_APP_DATA
300 ];
301
302 Ok(OrderIntent {
303 owner: Pubkey::new_from_array(*owner),
304 sell_token_account: Pubkey::new_from_array(*sell_token),
305 sell_mint: Pubkey::new_from_array(*sell_mint),
306 buy_token_account: Pubkey::new_from_array(*buy_token),
307 buy_mint: Pubkey::new_from_array(*buy_mint),
308 sell_amount: u64::from_le_bytes(*sell_amount),
309 buy_amount: u64::from_le_bytes(*buy_amount),
310 valid_to: u32::from_le_bytes(*valid_to),
311 flags: Flags::try_from(*flags)?,
312 app_data: *app_data,
313 })
314 }
315}
316
317impl TryFrom<&EncodedOrderIntent> for OrderIntent {
318 type Error = ProgramError;
319
320 fn try_from(encoded: &EncodedOrderIntent) -> Result<Self, Self::Error> {
321 OrderIntent::try_from(&encoded.0)
322 }
323}
324
325impl OrderIntent {
326 pub fn uid(&self) -> Hash {
330 EncodedOrderIntent::from(self).hash()
331 }
332}
333
334#[cfg(any(test, feature = "test-fixtures"))]
335pub mod fixtures {
336 use proptest::{prelude::*, strategy::Union};
337
338 use super::{Flags, OrderIntent, OrderKind, Pubkey};
339
340 pub const ALL_ORDER_KINDS: [OrderKind; 2] = [OrderKind::Sell, OrderKind::Buy];
342
343 pub const FLAGS_OFFSET: usize = 180;
345
346 pub fn sample_intent(flags: Flags) -> OrderIntent {
347 OrderIntent {
348 owner: Pubkey::new_from_array([0x11; 32]),
349 sell_token_account: Pubkey::new_from_array([0x22; 32]),
350 sell_mint: Pubkey::new_from_array([0x33; 32]),
351 buy_token_account: Pubkey::new_from_array([0x44; 32]),
352 buy_mint: Pubkey::new_from_array([0x55; 32]),
353 sell_amount: 0x0123_4567_89ab_cdef,
354 buy_amount: 0xfedc_ba98_7654_3210,
355 valid_to: 0xdead_beef,
356 flags,
357 app_data: [0x66; 32],
358 }
359 }
360
361 pub fn arb_order_kind() -> impl Strategy<Value = OrderKind> {
363 Union::new(ALL_ORDER_KINDS.map(Just))
364 }
365
366 pub fn arb_flags() -> impl Strategy<Value = Flags> {
368 (any::<bool>(), arb_order_kind(), any::<bool>()).prop_map(
369 |(created_on_chain, kind, partially_fillable)| Flags {
370 created_on_chain,
371 kind,
372 partially_fillable,
373 },
374 )
375 }
376
377 pub fn arb_flags_byte() -> impl Strategy<Value = u8> {
379 any::<u8>().prop_map(|byte| byte & Flags::DEFINED)
380 }
381
382 pub fn arb_invalid_flags_byte() -> impl Strategy<Value = u8> {
384 any::<u8>().prop_filter("must have at least one bit that is undefined", |byte| {
385 byte & !Flags::DEFINED > 0
386 })
387 }
388
389 pub fn arb_order_intent() -> impl Strategy<Value = OrderIntent> {
391 (
392 any::<[u8; 32]>(),
393 any::<[u8; 32]>(),
394 any::<[u8; 32]>(),
395 any::<[u8; 32]>(),
396 any::<[u8; 32]>(),
397 any::<u64>(),
398 any::<u64>(),
399 any::<u32>(),
400 arb_flags(),
401 any::<[u8; 32]>(),
402 )
403 .prop_map(
404 |(
405 owner,
406 sell_tok,
407 sell_mint,
408 buy_tok,
409 buy_mint,
410 sell_amount,
411 buy_amount,
412 valid_to,
413 flags,
414 app,
415 )| {
416 OrderIntent {
417 owner: Pubkey::new_from_array(owner),
418 sell_token_account: Pubkey::new_from_array(sell_tok),
419 sell_mint: Pubkey::new_from_array(sell_mint),
420 buy_token_account: Pubkey::new_from_array(buy_tok),
421 buy_mint: Pubkey::new_from_array(buy_mint),
422 sell_amount,
423 buy_amount,
424 valid_to,
425 flags,
426 app_data: app,
427 }
428 },
429 )
430 }
431}
432
433#[cfg(test)]
434mod tests {
435 use crate::data::intent::fixtures::FLAGS_OFFSET;
436
437 use super::fixtures::sample_intent;
438 use super::*;
439
440 fn all_flag_shapes() -> impl Iterator<Item = OrderIntent> {
444 [false, true].into_iter().flat_map(|created_on_chain| {
445 fixtures::ALL_ORDER_KINDS.into_iter().flat_map(move |kind| {
446 [false, true].into_iter().map(move |partially_fillable| {
447 sample_intent(Flags {
448 created_on_chain,
449 kind,
450 partially_fillable,
451 })
452 })
453 })
454 })
455 }
456
457 #[test]
461 fn widths_match_field_sizes() {
462 use core::mem::{size_of, size_of_val};
463
464 let intent = sample_intent(Default::default());
467
468 assert_eq!(EncodedOrderIntent::WIDTH_OWNER, size_of_val(&intent.owner));
469 assert_eq!(
470 EncodedOrderIntent::WIDTH_SELL_TOKEN,
471 size_of_val(&intent.sell_token_account)
472 );
473 assert_eq!(
474 EncodedOrderIntent::WIDTH_SELL_MINT,
475 size_of_val(&intent.sell_mint)
476 );
477 assert_eq!(
478 EncodedOrderIntent::WIDTH_BUY_TOKEN,
479 size_of_val(&intent.buy_token_account)
480 );
481 assert_eq!(
482 EncodedOrderIntent::WIDTH_BUY_MINT,
483 size_of_val(&intent.buy_mint)
484 );
485 assert_eq!(
486 EncodedOrderIntent::WIDTH_SELL_AMOUNT,
487 size_of_val(&intent.sell_amount)
488 );
489 assert_eq!(
490 EncodedOrderIntent::WIDTH_BUY_AMOUNT,
491 size_of_val(&intent.buy_amount)
492 );
493 assert_eq!(
494 EncodedOrderIntent::WIDTH_VALID_TO,
495 size_of_val(&intent.valid_to)
496 );
497 assert_eq!(
498 EncodedOrderIntent::WIDTH_FLAGS,
499 size_of_val::<[u8; 1]>(&Flags::default().into())
501 );
502 assert_eq!(
503 EncodedOrderIntent::WIDTH_APP_DATA,
504 size_of_val(&intent.app_data)
505 );
506
507 assert_eq!(EncodedOrderIntent::SIZE, size_of::<EncodedOrderIntent>());
508 }
509
510 #[test]
511 fn every_flag_owns_a_distinct_bit() {
512 let byte = |flags: Flags| <[u8; 1]>::from(flags)[0];
513 let cleared = Flags {
514 created_on_chain: false,
515 kind: OrderKind::Sell,
516 partially_fillable: false,
517 };
518 assert_eq!(byte(cleared), 0);
519
520 let set_one_by_one = [
521 (
522 Flags::CREATED_ON_CHAIN,
523 Flags {
524 created_on_chain: true,
525 ..cleared
526 },
527 ),
528 (
529 Flags::KIND,
530 Flags {
531 kind: OrderKind::Buy,
532 ..cleared
533 },
534 ),
535 (
536 Flags::PARTIALLY_FILLABLE,
537 Flags {
538 partially_fillable: true,
539 ..cleared
540 },
541 ),
542 ];
543 let mut seen = 0u8;
544 for (bit, flags) in set_one_by_one {
545 assert_eq!(bit.count_ones(), 1, "a flag must occupy a single bit");
546 assert_eq!(seen & bit, 0, "two flags must not share a bit");
547 assert!(
548 bit > seen,
549 "each flag must be more significant than the ones before it"
550 );
551 seen |= bit;
552 assert_eq!(byte(flags), bit);
553 }
554 assert_eq!(seen, Flags::DEFINED);
555 }
556
557 #[test]
558 fn roundtrip_all_kind_and_flag_combinations() {
559 for intent in all_flag_shapes() {
560 let encoded = EncodedOrderIntent::from(&intent);
561 let (decoded, _uid) =
562 EncodedOrderIntent::decode_and_hash(&encoded).expect("example must decode");
563 assert_eq!(decoded, intent);
564 }
565 }
566
567 #[test]
572 fn decode_and_hash_uid_matches_encoded_hash() {
573 for intent in all_flag_shapes() {
574 let encoded = EncodedOrderIntent::from(&intent);
575 let (_intent, uid) =
576 EncodedOrderIntent::decode_and_hash(&encoded).expect("example must decode");
577 assert_eq!(uid, encoded.hash());
578 }
579 }
580
581 #[test]
582 fn decode_accepts_defined_flag_bits_only() {
583 let encoded = EncodedOrderIntent::from(&sample_intent(Default::default()));
584 let mut bytes: [u8; EncodedOrderIntent::SIZE] = *encoded;
585 for flags in u8::MIN..=u8::MAX {
586 bytes[FLAGS_OFFSET] = flags;
587 let decoded = EncodedOrderIntent::decode_and_hash(&bytes);
588 if flags & !Flags::DEFINED != 0 {
589 assert_eq!(
590 decoded.err(),
591 Some(ProgramError::InvalidInstructionData),
592 "flags {flags:#04x} sets a reserved bit and must be rejected",
593 );
594 }
595 }
596 }
597
598 #[test]
599 fn uid_digest_regression() {
600 fn hex(bytes: &[u8]) -> String {
603 bytes.iter().map(|b| format!("{b:02x}")).collect()
604 }
605 let intent = sample_intent(Flags {
606 created_on_chain: true,
607 kind: OrderKind::Buy,
608 partially_fillable: true,
609 });
610 assert_eq!(
611 hex(intent.uid().as_ref()),
612 "de4096c6c100056f1e4636ea4fafefad40fc1d0b37692fe3ca1e0db3644b86bd",
613 );
614 }
615
616 #[test]
617 fn encoding_regression() {
618 let encoded = EncodedOrderIntent::from(&sample_intent(Flags {
619 created_on_chain: true,
620 kind: OrderKind::Buy,
621 partially_fillable: true,
622 }));
623 let encoding: [u8; EncodedOrderIntent::SIZE] = *encoded;
624 #[rustfmt::skip]
625 let expected: [u8; EncodedOrderIntent::SIZE] = [
626 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
628 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
629 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
630 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
631 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
633 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
634 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
635 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
636 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33,
638 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33,
639 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33,
640 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33,
641 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
643 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
644 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
645 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
646 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
648 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
649 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
650 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
651 0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0x01,
653 0x10, 0x32, 0x54, 0x76, 0x98, 0xba, 0xdc, 0xfe,
655 0xef, 0xbe, 0xad, 0xde,
657 0b00000111,
659 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
661 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
662 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
663 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
664 ];
665 assert_eq!(encoding, expected);
666 }
667
668 mod proptest {
670 use ::proptest::{prelude::*, test_runner::TestCaseError};
671
672 use super::*;
673 use crate::data::intent::fixtures::{
674 arb_flags_byte, arb_invalid_flags_byte, arb_order_intent, FLAGS_OFFSET,
675 };
676
677 proptest! {
678 #[test]
683 fn intent_roundtrip(intent in arb_order_intent()) {
684 let encoded = EncodedOrderIntent::from(&intent);
685 let (decoded, uid) = EncodedOrderIntent::decode_and_hash(&encoded)
686 .map_err(|e| TestCaseError::fail(format!("decode failed: {e:?}")))?;
687 prop_assert_eq!(decoded, intent);
688 prop_assert_eq!(uid, encoded.hash());
689 }
690
691 #[test]
694 fn bytes_roundtrip(
695 mut bytes in any::<[u8; EncodedOrderIntent::SIZE]>(),
696 flags in arb_flags_byte(),
697 ) {
698 bytes[FLAGS_OFFSET] = flags;
699 let (intent, _uid) = EncodedOrderIntent::decode_and_hash(&bytes)
700 .map_err(|e| TestCaseError::fail(format!("decode failed: {e:?}")))?;
701 prop_assert_eq!(*EncodedOrderIntent::from(&intent), bytes);
702 }
703
704 #[test]
707 fn rejects_reserved_flag_bits(
708 mut bytes in any::<[u8; EncodedOrderIntent::SIZE]>(),
709 bad_flags in arb_invalid_flags_byte(),
710 ) {
711 bytes[FLAGS_OFFSET] = bad_flags;
712 prop_assert_eq!(
713 EncodedOrderIntent::decode_and_hash(&bytes),
714 Err(ProgramError::InvalidInstructionData),
715 );
716 }
717 }
718 }
719}