1use core::mem::size_of;
25use core::ops::{Deref, DerefMut};
26
27use arrayref::{array_refs, mut_array_refs};
28use solana_account_view::{AccountView, Ref, RefMut};
29use solana_address::Address;
30use solana_hash::Hash;
31use solana_program_error::ProgramError;
32use solana_pubkey::Pubkey;
33
34use crate::data::intent::{self, EncodedOrderIntent};
35use crate::pda::is_pda_with_signer_seeds;
36use crate::pda::order::order_pda_signer_seeds;
37use crate::{SettlementAccount, SettlementError};
38
39pub const DISCRIMINATOR: u8 = SettlementAccount::OrderAccount.discriminator();
41
42pub const SIZE: usize = 264;
44
45const WIDTH_DISCRIMINATOR: usize = size_of::<u8>();
47const WIDTH_BUMP: usize = size_of::<u8>();
48const WIDTH_CANCELLED: usize = size_of::<bool>();
49const WIDTH_AMOUNT_WITHDRAWN: usize = size_of::<u64>();
50const WIDTH_AMOUNT_RECEIVED: usize = size_of::<u64>();
51const WIDTH_CREATED_BY: usize = size_of::<Pubkey>();
52const WIDTH_INTENT: usize = EncodedOrderIntent::SIZE;
53
54struct OrderSlots<'a> {
57 discriminator: &'a [u8; WIDTH_DISCRIMINATOR],
58 bump: &'a [u8; WIDTH_BUMP],
59 cancelled: &'a [u8; WIDTH_CANCELLED],
60 amount_withdrawn: &'a [u8; WIDTH_AMOUNT_WITHDRAWN],
61 amount_received: &'a [u8; WIDTH_AMOUNT_RECEIVED],
62 created_by: &'a [u8; WIDTH_CREATED_BY],
63 intent: &'a [u8; WIDTH_INTENT],
64}
65
66struct OrderSlotsMut<'a> {
68 discriminator: &'a mut [u8; WIDTH_DISCRIMINATOR],
69 bump: &'a mut [u8; WIDTH_BUMP],
70 cancelled: &'a mut [u8; WIDTH_CANCELLED],
71 amount_withdrawn: &'a mut [u8; WIDTH_AMOUNT_WITHDRAWN],
72 amount_received: &'a mut [u8; WIDTH_AMOUNT_RECEIVED],
73 created_by: &'a mut [u8; WIDTH_CREATED_BY],
74 intent: &'a mut [u8; WIDTH_INTENT],
75}
76
77#[inline]
79fn order_slots(body: &[u8; SIZE]) -> OrderSlots<'_> {
80 let (discriminator, bump, cancelled, amount_withdrawn, amount_received, created_by, intent) = array_refs![
81 body,
82 WIDTH_DISCRIMINATOR,
83 WIDTH_BUMP,
84 WIDTH_CANCELLED,
85 WIDTH_AMOUNT_WITHDRAWN,
86 WIDTH_AMOUNT_RECEIVED,
87 WIDTH_CREATED_BY,
88 WIDTH_INTENT
89 ];
90 OrderSlots {
91 discriminator,
92 bump,
93 cancelled,
94 amount_withdrawn,
95 amount_received,
96 created_by,
97 intent,
98 }
99}
100
101#[inline]
103fn order_slots_mut(body: &mut [u8; SIZE]) -> OrderSlotsMut<'_> {
104 let (discriminator, bump, cancelled, amount_withdrawn, amount_received, created_by, intent) = mut_array_refs![
105 body,
106 WIDTH_DISCRIMINATOR,
107 WIDTH_BUMP,
108 WIDTH_CANCELLED,
109 WIDTH_AMOUNT_WITHDRAWN,
110 WIDTH_AMOUNT_RECEIVED,
111 WIDTH_CREATED_BY,
112 WIDTH_INTENT
113 ];
114 OrderSlotsMut {
115 discriminator,
116 bump,
117 cancelled,
118 amount_withdrawn,
119 amount_received,
120 created_by,
121 intent,
122 }
123}
124
125#[derive(Clone, Debug, Eq, PartialEq)]
127pub struct FillAmounts {
128 pub withdrawn: u64,
129 pub received: u64,
130}
131
132pub struct OrderAccount<T>(T);
137
138impl<T: Deref<Target = [u8]>> OrderAccount<T> {
139 pub fn attach(bytes: T) -> Result<Self, ProgramError> {
145 let body: &[u8; SIZE] = (&*bytes)
146 .try_into()
147 .map_err(|_| ProgramError::InvalidAccountData)?;
148 if order_slots(body).discriminator != &[DISCRIMINATOR] {
149 return Err(ProgramError::InvalidAccountData);
150 }
151 Ok(Self(bytes))
152 }
153
154 fn body(&self) -> &[u8; SIZE] {
155 self.0
156 .first_chunk::<SIZE>()
157 .expect("body length is guaranteed by any constructor of `OrderAccount`")
158 }
159
160 pub fn bump(&self) -> u8 {
162 order_slots(self.body()).bump[0]
163 }
164
165 pub fn bump_slice(&self) -> &[u8; WIDTH_BUMP] {
169 order_slots(self.body()).bump
170 }
171
172 pub fn cancelled(&self) -> Result<bool, ProgramError> {
175 match order_slots(self.body()).cancelled {
176 [0] => Ok(false),
177 [1] => Ok(true),
178 _ => Err(ProgramError::InvalidAccountData),
179 }
180 }
181
182 pub fn filled_amounts(&self) -> FillAmounts {
185 let slots = order_slots(self.body());
186 FillAmounts {
187 withdrawn: u64::from_le_bytes(*slots.amount_withdrawn),
188 received: u64::from_le_bytes(*slots.amount_received),
189 }
190 }
191
192 pub fn created_by(&self) -> Pubkey {
195 Pubkey::new_from_array(*order_slots(self.body()).created_by)
196 }
197
198 pub fn intent_bytes(&self) -> &[u8; EncodedOrderIntent::SIZE] {
200 order_slots(self.body()).intent
201 }
202
203 pub fn intent_uid(&self) -> Hash {
208 intent::hash_bytes(self.intent_bytes())
209 }
210
211 pub fn check_pda(&self, address: &Address, program_id: &Address) -> Result<(), ProgramError> {
216 if !is_pda_with_signer_seeds(
217 address,
218 program_id,
219 order_pda_signer_seeds(&self.intent_uid(), self.bump_slice()),
220 ) {
221 return Err(SettlementError::AccountNotDerivable.into());
222 }
223 Ok(())
224 }
225}
226
227impl<'a> OrderAccount<Ref<'a, [u8]>> {
228 #[inline]
232 pub fn load_from_pda(
233 order_pda: &'a AccountView,
234 program_id: &Address,
235 ) -> Result<Self, ProgramError> {
236 let order = Self::attach(order_pda.try_borrow()?)?;
237 order.check_pda(order_pda.address(), program_id)?;
238 Ok(order)
239 }
240}
241
242impl<'a> OrderAccount<RefMut<'a, [u8]>> {
243 #[inline]
247 pub fn load_from_pda_mut(
248 order_pda: &'a mut AccountView,
249 program_id: &Address,
250 ) -> Result<Self, ProgramError> {
251 let address = *order_pda.address();
252 let order = Self::attach(order_pda.try_borrow_mut()?)?;
253 order.check_pda(&address, program_id)?;
254 Ok(order)
255 }
256}
257
258impl<T: DerefMut<Target = [u8]>> OrderAccount<T> {
259 pub fn initialize(
267 mut bytes: T,
268 bump: u8,
269 cancelled: bool,
270 amount_withdrawn: u64,
271 amount_received: u64,
272 created_by: &Pubkey,
273 encoded_intent: &[u8; EncodedOrderIntent::SIZE],
274 ) -> Result<Self, ProgramError> {
275 {
276 let body: &mut [u8; SIZE] = (&mut *bytes)
277 .try_into()
278 .map_err(|_| ProgramError::AccountDataTooSmall)?;
279 let slots = order_slots_mut(body);
280 *slots.discriminator = [DISCRIMINATOR];
281 *slots.bump = [bump];
282 *slots.cancelled = [cancelled as u8];
283 *slots.amount_withdrawn = amount_withdrawn.to_le_bytes();
284 *slots.amount_received = amount_received.to_le_bytes();
285 *slots.created_by = created_by.to_bytes();
286 *slots.intent = *encoded_intent;
287 }
288 Ok(Self(bytes))
289 }
290
291 fn body_mut(&mut self) -> &mut [u8; SIZE] {
292 let bytes: &mut [u8] = &mut self.0;
293 bytes
294 .first_chunk_mut::<SIZE>()
295 .expect("body length is guaranteed by any constructor of `OrderAccount`")
296 }
297
298 pub fn set_amounts(&mut self, amounts: FillAmounts) {
300 let slots = order_slots_mut(self.body_mut());
301 *slots.amount_withdrawn = amounts.withdrawn.to_le_bytes();
302 *slots.amount_received = amounts.received.to_le_bytes();
303 }
304
305 pub fn set_cancelled(&mut self) {
308 let slots = order_slots_mut(self.body_mut());
309 *slots.cancelled = [true.into()];
310 }
311}
312
313#[cfg(any(test, feature = "test-fixtures"))]
316pub mod fixtures {
317 use proptest::prelude::*;
318
319 use super::{EncodedOrderIntent, OrderAccount, Pubkey, DISCRIMINATOR, SIZE};
320 use crate::data::intent::fixtures::{arb_intent_bytes, arb_order_intent, sample_intent};
321 use crate::data::intent::OrderIntent;
322
323 pub const DISCRIMINATOR_OFFSET: usize = 0;
325 pub const CANCELLED_OFFSET: usize = 2;
326 pub const INTENT_OFFSET: usize = 51;
327
328 #[derive(Clone, Debug, Eq, PartialEq)]
331 pub struct OrderFields {
332 pub bump: u8,
333 pub cancelled: bool,
334 pub amount_withdrawn: u64,
335 pub amount_received: u64,
336 pub created_by: Pubkey,
337 pub intent: OrderIntent,
338 }
339
340 impl OrderFields {
341 pub fn encode(&self) -> [u8; SIZE] {
343 let mut bytes = [0u8; SIZE];
344 OrderAccount::initialize(
345 &mut bytes[..],
346 self.bump,
347 self.cancelled,
348 self.amount_withdrawn,
349 self.amount_received,
350 &self.created_by,
351 &EncodedOrderIntent::from(&self.intent),
352 )
353 .expect("a full-length buffer initializes");
354 bytes
355 }
356 }
357 pub fn sample_order_fields(cancelled: bool) -> OrderFields {
360 OrderFields {
361 bump: 0xbd,
362 cancelled,
363 amount_withdrawn: 0x0112_2334_4556_6778,
364 amount_received: 0x899a_abbc_cdde_eff0,
365 created_by: Pubkey::new_from_array([0x42; 32]),
366 intent: sample_intent(Default::default()),
367 }
368 }
369
370 pub fn sample_order_bytes(cancelled: bool) -> [u8; SIZE] {
372 sample_order_fields(cancelled).encode()
373 }
374
375 pub fn arb_order_account() -> impl Strategy<Value = OrderFields> {
377 (
378 any::<u8>(),
379 any::<bool>(),
380 any::<u64>(),
381 any::<u64>(),
382 any::<[u8; 32]>(),
383 arb_order_intent(),
384 )
385 .prop_map(
386 |(bump, cancelled, amount_withdrawn, amount_received, created_by, intent)| {
387 OrderFields {
388 bump,
389 cancelled,
390 amount_withdrawn,
391 amount_received,
392 created_by: Pubkey::new_from_array(created_by),
393 intent,
394 }
395 },
396 )
397 }
398
399 pub fn arb_order_bytes() -> impl Strategy<Value = [u8; SIZE]> {
402 (any::<[u8; SIZE]>(), any::<bool>(), arb_intent_bytes()).prop_map(
403 |(mut bytes, cancelled, intent)| {
404 bytes[DISCRIMINATOR_OFFSET] = DISCRIMINATOR;
405 bytes[CANCELLED_OFFSET] = cancelled as u8;
406 bytes[INTENT_OFFSET..].copy_from_slice(&intent);
407 bytes
408 },
409 )
410 }
411}
412
413#[cfg(test)]
414mod tests {
415 use super::fixtures::{
416 sample_order_bytes, sample_order_fields, OrderFields, CANCELLED_OFFSET,
417 DISCRIMINATOR_OFFSET, INTENT_OFFSET,
418 };
419 use super::*;
420 use crate::data::intent::OrderIntent;
421
422 #[test]
423 fn widths_match_order_fields() {
424 use core::mem::size_of_val;
425
426 let OrderFields {
428 bump,
429 cancelled,
430 amount_withdrawn,
431 amount_received,
432 created_by,
433 intent: _intent,
436 } = sample_order_fields(false);
437
438 assert_eq!(WIDTH_BUMP, size_of_val(&bump));
439 assert_eq!(WIDTH_CANCELLED, size_of_val(&cancelled));
440 assert_eq!(WIDTH_AMOUNT_WITHDRAWN, size_of_val(&amount_withdrawn));
441 assert_eq!(WIDTH_AMOUNT_RECEIVED, size_of_val(&amount_received));
442 assert_eq!(WIDTH_CREATED_BY, size_of_val(&created_by));
443 assert_eq!(WIDTH_INTENT, size_of::<EncodedOrderIntent>());
444 }
445
446 #[test]
447 fn reads_fields_it_was_initialized_with() {
448 for cancelled in [false, true] {
449 let fields = sample_order_fields(cancelled);
450 let bytes = fields.encode();
451 let OrderFields {
452 bump,
453 cancelled,
454 amount_withdrawn,
455 amount_received,
456 created_by,
457 intent,
458 } = fields;
459 let order = OrderAccount::attach(&bytes[..]).expect("sample must attach");
460
461 assert_eq!(order.bump(), bump);
462 assert_eq!(order.cancelled().expect("valid cancelled"), cancelled);
463 assert_eq!(
464 order.filled_amounts(),
465 FillAmounts {
466 withdrawn: amount_withdrawn,
467 received: amount_received,
468 }
469 );
470 assert_eq!(order.created_by(), created_by);
471 assert_eq!(
472 OrderIntent::try_from(order.intent_bytes()).expect("valid intent"),
473 intent
474 );
475 assert_eq!(order.intent_uid(), intent.uid());
476 }
477 }
478
479 #[test]
480 fn set_amounts_writes_only_the_two_amount_fields() {
481 let mut bytes = sample_order_bytes(false);
482
483 let filled = FillAmounts {
484 withdrawn: 0xdead_beef_dead_beef,
485 received: 0x0123_4567_89ab_cdef,
486 };
487 let mut account = OrderAccount::attach(&mut bytes[..]).expect("sample must attach");
488 let current = account.filled_amounts();
489 assert_ne!(
490 filled.withdrawn, current.withdrawn,
491 "sanity check: new withdrawn is different"
492 );
493 assert_ne!(
494 filled.received, current.received,
495 "sanity check: new received is different"
496 );
497
498 account.set_amounts(filled.clone());
499
500 let expected = OrderFields {
503 amount_withdrawn: filled.withdrawn,
504 amount_received: filled.received,
505 ..sample_order_fields(false)
506 }
507 .encode();
508 assert_eq!(bytes, expected);
509 }
510
511 #[test]
512 fn set_cancelled_writes_only_the_cancelled_byte() {
513 let mut bytes = sample_order_bytes(false);
514
515 let mut account = OrderAccount::attach(&mut bytes[..]).expect("sample must attach");
516 assert!(
517 !account.cancelled().expect("valid cancelled"),
518 "sanity check: order starts active"
519 );
520 account.set_cancelled();
521
522 let expected = sample_order_fields(true).encode();
525 assert_eq!(bytes, expected);
526 }
527
528 #[test]
529 fn set_cancelled_on_cancelled_order_is_a_no_op() {
530 let mut bytes = sample_order_bytes(true);
531
532 let mut account = OrderAccount::attach(&mut bytes[..]).expect("sample must attach");
533 assert!(
534 account.cancelled().expect("valid cancelled"),
535 "sanity check: order starts cancelled"
536 );
537 account.set_cancelled();
538
539 let expected = sample_order_fields(true).encode();
542 assert_eq!(bytes, expected);
543 }
544
545 #[test]
546 fn sanity_check_offsets() {
547 fn first_differing_byte(lhs: &[u8], rhs: &[u8]) -> Option<usize> {
548 lhs.iter().zip(rhs).position(|(l, r)| l != r)
549 }
550
551 let base = sample_order_bytes(false);
552 let cancelled = sample_order_bytes(true);
553 assert_eq!(
554 first_differing_byte(&base, &cancelled).expect("should differ in the cancelled byte"),
555 CANCELLED_OFFSET
556 );
557
558 let encoded_intent: [u8; EncodedOrderIntent::SIZE] =
560 (&EncodedOrderIntent::from(&sample_order_fields(false).intent)).into();
561 let bitwise_different_encoded_intent: [u8; EncodedOrderIntent::SIZE] =
567 encoded_intent.map(|b| b ^ 0x02);
568 let changed_intent_field =
569 OrderIntent::try_from(&bitwise_different_encoded_intent).expect("hack should work");
570 let changed_intent = OrderFields {
571 intent: changed_intent_field,
572 ..sample_order_fields(false)
573 }
574 .encode();
575 assert_eq!(
576 first_differing_byte(&base, &changed_intent).expect("should differ in the intent slot"),
577 INTENT_OFFSET
578 );
579 }
580
581 #[test]
582 fn attach_rejects_wrong_discriminator() {
583 let mut bytes = sample_order_bytes(false);
584 bytes[DISCRIMINATOR_OFFSET] ^= 0xff;
585 assert_eq!(
586 OrderAccount::attach(&bytes[..]).err(),
587 Some(ProgramError::InvalidAccountData),
588 );
589 }
590
591 #[test]
592 fn attach_rejects_wrong_length() {
593 let bytes = sample_order_bytes(false);
594 assert_eq!(
595 OrderAccount::attach(&bytes[..SIZE - 1]).err(),
596 Some(ProgramError::InvalidAccountData),
597 );
598 let too_long = [bytes.as_slice(), [0].as_slice()].concat();
599 assert_eq!(
600 OrderAccount::attach(&too_long[..]).err(),
601 Some(ProgramError::InvalidAccountData),
602 );
603 }
604
605 #[test]
606 fn cancelled_rejects_non_boolean_byte() {
607 let mut bytes = sample_order_bytes(false);
608 for bad in 0x02u8..=0xff {
609 bytes[CANCELLED_OFFSET] = bad;
610 let order =
611 OrderAccount::attach(&bytes[..]).expect("attach ignores the cancelled byte");
612 assert_eq!(order.cancelled(), Err(ProgramError::InvalidAccountData));
613 }
614 }
615
616 mod load_from_pda {
617 use std::sync::LazyLock;
618
619 use super::*;
620 use crate::fixtures::pubkey_from_seed;
621 use crate::instruction::fixtures::fake_account_with_data;
622 use crate::pda::order::find_order_pda;
623
624 static PROGRAM_ID: LazyLock<Pubkey> =
625 LazyLock::new(|| pubkey_from_seed("tests::load_from_pda's arbitrary program id"));
626
627 fn canonical_bytes(cancelled: bool) -> ([u8; SIZE], Address) {
630 let fields = sample_order_fields(cancelled);
631 let (pda_address, bump) = find_order_pda(&PROGRAM_ID, &fields.intent.uid());
632 let bytes = OrderFields { bump, ..fields }.encode();
633 (bytes, pda_address)
634 }
635
636 #[test]
637 fn accepts_the_canonical_pda() {
638 let (bytes, pda_address) = canonical_bytes(false);
639 let order_pda = fake_account_with_data(pda_address, &bytes[..]);
640
641 let order = OrderAccount::load_from_pda(&order_pda, &PROGRAM_ID)
642 .expect("canonical PDA must load");
643 assert_eq!(order.bump(), bytes[1]);
644 }
645
646 #[test]
647 fn rejects_a_non_canonical_address() {
648 let (bytes, _) = canonical_bytes(false);
649 let wrong_address = Pubkey::new_from_array([0x42; 32]);
651 let order_pda = fake_account_with_data(wrong_address, &bytes[..]);
652
653 assert_eq!(
654 OrderAccount::load_from_pda(&order_pda, &PROGRAM_ID).err(),
655 Some(SettlementError::AccountNotDerivable.into()),
656 );
657 }
658
659 #[test]
660 fn rejects_a_stored_bump_that_does_not_derive_the_address() {
661 let (mut bytes, pda_address) = canonical_bytes(false);
662 bytes[1] = bytes[1].wrapping_sub(1);
666 let order_pda = fake_account_with_data(pda_address, &bytes[..]);
667
668 assert_eq!(
669 OrderAccount::load_from_pda(&order_pda, &PROGRAM_ID).err(),
670 Some(SettlementError::AccountNotDerivable.into()),
671 );
672 }
673
674 #[test]
675 fn attach_propagates_decode_errors() {
676 let (mut bytes, pda_address) = canonical_bytes(false);
677 bytes[DISCRIMINATOR_OFFSET] ^= 0xff;
678 let order_pda = fake_account_with_data(pda_address, &bytes);
679
680 assert_eq!(
681 OrderAccount::load_from_pda(&order_pda, &PROGRAM_ID).err(),
682 Some(ProgramError::InvalidAccountData),
683 );
684 }
685 }
686
687 mod proptest {
689 use ::proptest::{prelude::*, test_runner::TestCaseError};
690
691 use super::*;
692 use crate::data::order::fixtures::{arb_order_account, arb_order_bytes};
693
694 proptest! {
695 #[test]
698 fn reads_match_written_fields(fields in arb_order_account()) {
699 let bytes = fields.encode();
700 let order = OrderAccount::attach(&bytes[..])
701 .map_err(|e| TestCaseError::fail(format!("attach failed: {e:?}")))?;
702 let OrderFields {
703 bump,
704 cancelled,
705 amount_withdrawn,
706 amount_received,
707 created_by,
708 intent,
709 } = fields;
710 prop_assert_eq!(order.bump(), bump);
711 prop_assert_eq!(order.cancelled().expect("valid cancelled"), cancelled);
712 prop_assert_eq!(
713 order.filled_amounts(),
714 FillAmounts {
715 withdrawn: amount_withdrawn,
716 received: amount_received,
717 }
718 );
719 prop_assert_eq!(order.created_by(), created_by);
720 prop_assert_eq!(order.intent_uid(), intent.uid());
721 prop_assert_eq!(
722 OrderIntent::try_from(order.intent_bytes()).expect("valid intent"),
723 intent
724 );
725 }
726
727 #[test]
730 fn bytes_roundtrip(bytes in arb_order_bytes()) {
731 let order = OrderAccount::attach(&bytes[..])
732 .map_err(|e| TestCaseError::fail(format!("attach failed: {e:?}")))?;
733 let filled = order.filled_amounts();
734 let reencoded = OrderFields {
735 bump: order.bump(),
736 cancelled: order
737 .cancelled()
738 .map_err(|e| TestCaseError::fail(format!("cancelled: {e:?}")))?,
739 amount_withdrawn: filled.withdrawn,
740 amount_received: filled.received,
741 created_by: order.created_by(),
742 intent: OrderIntent::try_from(order.intent_bytes())
743 .map_err(|e| TestCaseError::fail(format!("intent: {e:?}")))?,
744 }
745 .encode();
746 prop_assert_eq!(reencoded, bytes);
747 }
748
749 #[test]
752 fn set_amounts_matches_a_fresh_encode(
753 fields in arb_order_account(),
754 new_withdrawn in any::<u64>(),
755 new_received in any::<u64>(),
756 ) {
757 let mut bytes = fields.encode();
758 OrderAccount::attach(&mut bytes[..])
759 .map_err(|e| TestCaseError::fail(format!("attach failed: {e:?}")))?
760 .set_amounts(FillAmounts {
761 withdrawn: new_withdrawn,
762 received: new_received,
763 });
764 let expected = OrderFields {
765 amount_withdrawn: new_withdrawn,
766 amount_received: new_received,
767 ..fields
768 }
769 .encode();
770 prop_assert_eq!(bytes, expected);
771 }
772 }
773 }
774}