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, SIZE};
320 use crate::data::intent::fixtures::{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
400#[cfg(test)]
401mod tests {
402 use super::fixtures::{
403 sample_order_bytes, sample_order_fields, OrderFields, CANCELLED_OFFSET,
404 DISCRIMINATOR_OFFSET, INTENT_OFFSET,
405 };
406 use super::*;
407 use crate::data::intent::fixtures::FLAGS_OFFSET;
408 use crate::data::intent::OrderIntent;
409
410 #[test]
411 fn widths_match_order_fields() {
412 use core::mem::size_of_val;
413
414 let OrderFields {
416 bump,
417 cancelled,
418 amount_withdrawn,
419 amount_received,
420 created_by,
421 intent: _intent,
424 } = sample_order_fields(false);
425
426 assert_eq!(WIDTH_BUMP, size_of_val(&bump));
427 assert_eq!(WIDTH_CANCELLED, size_of_val(&cancelled));
428 assert_eq!(WIDTH_AMOUNT_WITHDRAWN, size_of_val(&amount_withdrawn));
429 assert_eq!(WIDTH_AMOUNT_RECEIVED, size_of_val(&amount_received));
430 assert_eq!(WIDTH_CREATED_BY, size_of_val(&created_by));
431 assert_eq!(WIDTH_INTENT, size_of::<EncodedOrderIntent>());
432 }
433
434 #[test]
435 fn reads_fields_it_was_initialized_with() {
436 for cancelled in [false, true] {
437 let fields = sample_order_fields(cancelled);
438 let bytes = fields.encode();
439 let OrderFields {
440 bump,
441 cancelled,
442 amount_withdrawn,
443 amount_received,
444 created_by,
445 intent,
446 } = fields;
447 let order = OrderAccount::attach(&bytes[..]).expect("sample must attach");
448
449 assert_eq!(order.bump(), bump);
450 assert_eq!(order.cancelled().expect("valid cancelled"), cancelled);
451 assert_eq!(
452 order.filled_amounts(),
453 FillAmounts {
454 withdrawn: amount_withdrawn,
455 received: amount_received,
456 }
457 );
458 assert_eq!(order.created_by(), created_by);
459 assert_eq!(
460 OrderIntent::try_from(order.intent_bytes()).expect("valid intent"),
461 intent
462 );
463 assert_eq!(order.intent_uid(), intent.uid());
464 }
465 }
466
467 #[test]
468 fn set_amounts_writes_only_the_two_amount_fields() {
469 let mut bytes = sample_order_bytes(false);
470
471 let filled = FillAmounts {
472 withdrawn: 0xdead_beef_dead_beef,
473 received: 0x0123_4567_89ab_cdef,
474 };
475 let mut account = OrderAccount::attach(&mut bytes[..]).expect("sample must attach");
476 let current = account.filled_amounts();
477 assert_ne!(
478 filled.withdrawn, current.withdrawn,
479 "sanity check: new withdrawn is different"
480 );
481 assert_ne!(
482 filled.received, current.received,
483 "sanity check: new received is different"
484 );
485
486 account.set_amounts(filled.clone());
487
488 let expected = OrderFields {
491 amount_withdrawn: filled.withdrawn,
492 amount_received: filled.received,
493 ..sample_order_fields(false)
494 }
495 .encode();
496 assert_eq!(bytes, expected);
497 }
498
499 #[test]
500 fn set_cancelled_writes_only_the_cancelled_byte() {
501 let mut bytes = sample_order_bytes(false);
502
503 let mut account = OrderAccount::attach(&mut bytes[..]).expect("sample must attach");
504 assert!(
505 !account.cancelled().expect("valid cancelled"),
506 "sanity check: order starts active"
507 );
508 account.set_cancelled();
509
510 let expected = sample_order_fields(true).encode();
513 assert_eq!(bytes, expected);
514 }
515
516 #[test]
517 fn set_cancelled_on_cancelled_order_is_a_no_op() {
518 let mut bytes = sample_order_bytes(true);
519
520 let mut account = OrderAccount::attach(&mut bytes[..]).expect("sample must attach");
521 assert!(
522 account.cancelled().expect("valid cancelled"),
523 "sanity check: order starts cancelled"
524 );
525 account.set_cancelled();
526
527 let expected = sample_order_fields(true).encode();
530 assert_eq!(bytes, expected);
531 }
532
533 #[test]
534 fn sanity_check_offsets() {
535 fn first_differing_byte(lhs: &[u8], rhs: &[u8]) -> Option<usize> {
536 lhs.iter().zip(rhs).position(|(l, r)| l != r)
537 }
538
539 let base = sample_order_bytes(false);
540 let cancelled = sample_order_bytes(true);
541 assert_eq!(
542 first_differing_byte(&base, &cancelled).expect("should differ in the cancelled byte"),
543 CANCELLED_OFFSET
544 );
545
546 let encoded_intent: [u8; EncodedOrderIntent::SIZE] =
548 (&EncodedOrderIntent::from(&sample_order_fields(false).intent)).into();
549 let bitwise_different_encoded_intent: [u8; EncodedOrderIntent::SIZE] =
555 encoded_intent.map(|b| b ^ 0x01);
556 let changed_intent_field =
557 OrderIntent::try_from(&bitwise_different_encoded_intent).expect("hack should work");
558 let changed_intent = OrderFields {
559 intent: changed_intent_field,
560 ..sample_order_fields(false)
561 }
562 .encode();
563 assert_eq!(
564 first_differing_byte(&base, &changed_intent).expect("should differ in the intent slot"),
565 INTENT_OFFSET
566 );
567 }
568
569 #[test]
570 fn attach_rejects_wrong_discriminator() {
571 let mut bytes = sample_order_bytes(false);
572 bytes[DISCRIMINATOR_OFFSET] ^= 0xff;
573 assert_eq!(
574 OrderAccount::attach(&bytes[..]).err(),
575 Some(ProgramError::InvalidAccountData),
576 );
577 }
578
579 #[test]
580 fn attach_rejects_wrong_length() {
581 let bytes = sample_order_bytes(false);
582 assert_eq!(
583 OrderAccount::attach(&bytes[..SIZE - 1]).err(),
584 Some(ProgramError::InvalidAccountData),
585 );
586 let too_long = [bytes.as_slice(), [0].as_slice()].concat();
587 assert_eq!(
588 OrderAccount::attach(&too_long[..]).err(),
589 Some(ProgramError::InvalidAccountData),
590 );
591 }
592
593 #[test]
594 fn cancelled_rejects_non_boolean_byte() {
595 let mut bytes = sample_order_bytes(false);
596 for bad in 0x02u8..=0xff {
597 bytes[CANCELLED_OFFSET] = bad;
598 let order =
599 OrderAccount::attach(&bytes[..]).expect("attach ignores the cancelled byte");
600 assert_eq!(order.cancelled(), Err(ProgramError::InvalidAccountData));
601 }
602 }
603
604 mod load_from_pda {
605 use super::*;
606 use crate::fixtures::PROGRAM_ID;
607 use crate::instruction::fixtures::fake_account_with_data;
608 use crate::pda::order::find_order_pda;
609
610 fn canonical_bytes(cancelled: bool) -> ([u8; SIZE], Address) {
613 let fields = sample_order_fields(cancelled);
614 let (pda_address, bump) = find_order_pda(&PROGRAM_ID, &fields.intent.uid());
615 let bytes = OrderFields { bump, ..fields }.encode();
616 (bytes, pda_address)
617 }
618
619 #[test]
620 fn accepts_the_canonical_pda() {
621 let (bytes, pda_address) = canonical_bytes(false);
622 let order_pda = fake_account_with_data(pda_address, &bytes[..]);
623
624 let order = OrderAccount::load_from_pda(&order_pda, &PROGRAM_ID)
625 .expect("canonical PDA must load");
626 assert_eq!(order.bump(), bytes[1]);
627 }
628
629 #[test]
630 fn rejects_a_non_canonical_address() {
631 let (bytes, _) = canonical_bytes(false);
632 let wrong_address = Pubkey::new_from_array([0x42; 32]);
634 let order_pda = fake_account_with_data(wrong_address, &bytes[..]);
635
636 assert_eq!(
637 OrderAccount::load_from_pda(&order_pda, &PROGRAM_ID).err(),
638 Some(SettlementError::AccountNotDerivable.into()),
639 );
640 }
641
642 #[test]
643 fn rejects_a_stored_bump_that_does_not_derive_the_address() {
644 let (mut bytes, pda_address) = canonical_bytes(false);
645 bytes[1] = bytes[1].wrapping_sub(1);
649 let order_pda = fake_account_with_data(pda_address, &bytes[..]);
650
651 assert_eq!(
652 OrderAccount::load_from_pda(&order_pda, &PROGRAM_ID).err(),
653 Some(SettlementError::AccountNotDerivable.into()),
654 );
655 }
656
657 #[test]
658 fn attach_propagates_decode_errors() {
659 let (mut bytes, pda_address) = canonical_bytes(false);
660 bytes[DISCRIMINATOR_OFFSET] ^= 0xff;
661 let order_pda = fake_account_with_data(pda_address, &bytes);
662
663 assert_eq!(
664 OrderAccount::load_from_pda(&order_pda, &PROGRAM_ID).err(),
665 Some(ProgramError::InvalidAccountData),
666 );
667 }
668 }
669
670 mod proptest {
672 use ::proptest::{prelude::*, test_runner::TestCaseError};
673
674 use super::*;
675 use crate::data::{intent::fixtures::arb_flags_byte, order::fixtures::arb_order_account};
676
677 proptest! {
678 #[test]
681 fn reads_match_written_fields(fields in arb_order_account()) {
682 let bytes = fields.encode();
683 let order = OrderAccount::attach(&bytes[..])
684 .map_err(|e| TestCaseError::fail(format!("attach failed: {e:?}")))?;
685 let OrderFields {
686 bump,
687 cancelled,
688 amount_withdrawn,
689 amount_received,
690 created_by,
691 intent,
692 } = fields;
693 prop_assert_eq!(order.bump(), bump);
694 prop_assert_eq!(order.cancelled().expect("valid cancelled"), cancelled);
695 prop_assert_eq!(
696 order.filled_amounts(),
697 FillAmounts {
698 withdrawn: amount_withdrawn,
699 received: amount_received,
700 }
701 );
702 prop_assert_eq!(order.created_by(), created_by);
703 prop_assert_eq!(order.intent_uid(), intent.uid());
704 prop_assert_eq!(
705 OrderIntent::try_from(order.intent_bytes()).expect("valid intent"),
706 intent
707 );
708 }
709
710 #[test]
713 fn bytes_roundtrip(
714 mut bytes in any::<[u8; SIZE]>(),
715 cancelled in any::<bool>(),
716 flags in arb_flags_byte(),
717 ) {
718 bytes[DISCRIMINATOR_OFFSET] = DISCRIMINATOR;
719 bytes[CANCELLED_OFFSET] = cancelled as u8;
720 bytes[INTENT_OFFSET + FLAGS_OFFSET] = flags;
721
722 let order = OrderAccount::attach(&bytes[..])
723 .map_err(|e| TestCaseError::fail(format!("attach failed: {e:?}")))?;
724 let filled = order.filled_amounts();
725 let reencoded = OrderFields {
726 bump: order.bump(),
727 cancelled: order
728 .cancelled()
729 .map_err(|e| TestCaseError::fail(format!("cancelled: {e:?}")))?,
730 amount_withdrawn: filled.withdrawn,
731 amount_received: filled.received,
732 created_by: order.created_by(),
733 intent: OrderIntent::try_from(order.intent_bytes())
734 .map_err(|e| TestCaseError::fail(format!("intent: {e:?}")))?,
735 }
736 .encode();
737 prop_assert_eq!(reencoded, bytes);
738 }
739
740 #[test]
743 fn set_amounts_matches_a_fresh_encode(
744 fields in arb_order_account(),
745 new_withdrawn in any::<u64>(),
746 new_received in any::<u64>(),
747 ) {
748 let mut bytes = fields.encode();
749 OrderAccount::attach(&mut bytes[..])
750 .map_err(|e| TestCaseError::fail(format!("attach failed: {e:?}")))?
751 .set_amounts(FillAmounts {
752 withdrawn: new_withdrawn,
753 received: new_received,
754 });
755 let expected = OrderFields {
756 amount_withdrawn: new_withdrawn,
757 amount_received: new_received,
758 ..fields
759 }
760 .encode();
761 prop_assert_eq!(bytes, expected);
762 }
763 }
764 }
765}