Skip to main content

cow_settlement_interface/data/
order.rs

1//! Order account: its byte layout and the zero-copy accessor over it.
2//!
3//! The settlement program stores each authenticated order in a dedicated
4//! program-derived account. That account's data area is laid out here.
5//!
6//! ```text
7//!  ┌───── discriminator
8//!  │┌──── bump
9//!  ││┌─── cancelled
10//!  ┌┬┬┬───────┬───────┬───────────────────────────────┬─────────────────...─────────────────┐
11//!  ││││amount_│amount_│                               │                                     │
12//!  ││││with-  │re-    │           created_by          │     intent (EncodedOrderIntent)     │
13//!  ││││drawn  │ceived │                               │                                     │
14//!  └┴┴┴───────┴───────┴───────────────────────────────┴─────────────────...─────────────────┘
15//! 0 1 2 3     11      19                              51                ...               264
16//! ```
17//!
18//! [`OrderAccount`] is a zero-copy accessor over those bytes, generic over the
19//! borrow backing it (`&[u8]`, `&mut [u8]`): the reads are available for any
20//! borrow, the in-place writes only for a mutable one. It reads and updates the
21//! account data directly, so the program never copies the account into an owned
22//! struct.
23
24use 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
39/// Single-byte account discriminator at the front of the body.
40pub const DISCRIMINATOR: u8 = SettlementAccount::OrderAccount.discriminator();
41
42/// Canonical byte length of an order account's data area.
43pub const SIZE: usize = 264;
44
45// Per-field widths, derived from the order account's field types.
46const 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
54/// A borrowed view over an order's bytes, split into named slots so each field
55/// can be named. The slots hold raw encoded bytes, not decoded values.
56struct 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
66/// The mutable counterpart of [`OrderSlots`], for in-place writes.
67struct 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/// Split a body into its named slots.
78#[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/// [`order_slots`] over a mutable body, for in-place writes.
102#[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/// A pair of order fill amounts: sell token withdrawn and buy token received.
126#[derive(Clone, Debug, Eq, PartialEq)]
127pub struct FillAmounts {
128    pub withdrawn: u64,
129    pub received: u64,
130}
131
132/// A zero-copy accessor over an order account's canonical byte representation.
133///
134/// `T` is the borrow backing it, anything that dereferences to the account's
135/// bytes: `&[u8]` grants read access; `&mut [u8]` grants write access.
136pub struct OrderAccount<T>(T);
137
138impl<T: Deref<Target = [u8]>> OrderAccount<T> {
139    /// Wrap an account's bytes, checking they are exactly one order body long
140    /// and begin with the order discriminator. Every accessor relies on that
141    /// guarantee not to panic.
142    /// No further byte validation is performed: the intent may be invalid, or
143    /// the intent fail to decode, and `attach` would still return `Ok`.
144    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    /// Canonical bump of the PDA this account lives at.
161    pub fn bump(&self) -> u8 {
162        order_slots(self.body()).bump[0]
163    }
164
165    /// The canonical bump as a one-byte slice borrowed in place, ready to feed
166    /// the PDA seeds. Makes it a bit more efficient to write the PDA seeds
167    /// compared to using `bump()` and recreating the slice.
168    pub fn bump_slice(&self) -> &[u8; WIDTH_BUMP] {
169        order_slots(self.body()).bump
170    }
171
172    /// Whether the order has been cancelled by its owner. Fails with
173    /// [`ProgramError::InvalidAccountData`] if the stored byte is out of range.
174    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    /// The order's cumulative fill so far: the sell token withdrawn and the buy
183    /// token received across settlements.
184    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    /// Account that funded the rent for this PDA; the rent is refunded here
193    /// when the order is cleared.
194    pub fn created_by(&self) -> Pubkey {
195        Pubkey::new_from_array(*order_slots(self.body()).created_by)
196    }
197
198    /// The verbatim [`EncodedOrderIntent`] bytes stored in the account.
199    pub fn intent_bytes(&self) -> &[u8; EncodedOrderIntent::SIZE] {
200        order_slots(self.body()).intent
201    }
202
203    /// The order UID: the hash of the intent's canonical bytes. Computed over
204    /// the stored bytes directly, so no intent decoding is required.
205    /// It doesn't check that the underlying intent is valid, though this does
206    /// not happen for accounts created by the program.
207    pub fn intent_uid(&self) -> Hash {
208        intent::hash_bytes(self.intent_bytes())
209    }
210
211    /// Confirm the account lives at its own canonical order PDA: both the UID
212    /// and the bump feeding the derivation come from the stored body, so an
213    /// account planted at the wrong address is rejected. `address` is the
214    /// account this accessor is attached to.
215    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    /// Attach to the order at the given PDA and confirm the PDA is derivable
229    /// from its own data: both the UID and the bump feeding the derivation come
230    /// from the stored body.
231    #[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    /// Mutable counterpart of [`OrderAccount::load_from_pda`]: borrow the order
244    /// mutably and confirm it lives at its own canonical PDA before a caller
245    /// writes to it.
246    #[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    /// Writes a full order body into `bytes` and return the accessor over it.
260    /// `encoded_intent` is stored verbatim and unvalidated: an order's PDA is
261    /// derived from the hash of these exact bytes, so their canonicality is the
262    /// caller's responsibility.
263    ///
264    /// Fails with [`ProgramError::AccountDataTooSmall`] if the buffer isn't
265    /// exactly one order body long.
266    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    /// Overwrite the two cumulative fill amounts in place.
299    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    /// Mark the order cancelled. Idempotent: setting it on an already-cancelled
306    /// order writes back the same value.
307    pub fn set_cancelled(&mut self) {
308        let slots = order_slots_mut(self.body_mut());
309        *slots.cancelled = [true.into()];
310    }
311}
312
313/// Test scaffolding for building order-account bytes, shared by this crate's
314/// tests and its consumers' via the `test-fixtures` feature.
315#[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    // Hardcoded but verified in a sanity-check test.
324    pub const DISCRIMINATOR_OFFSET: usize = 0;
325    pub const CANCELLED_OFFSET: usize = 2;
326    pub const INTENT_OFFSET: usize = 51;
327
328    /// The fields an order account encodes, as plain owned values, so a test can
329    /// assert the accessor reads back exactly what was written.
330    #[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        /// Encode these fields into canonical order-account bytes.
342        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    /// Hand-picked example order fields wrapping [`sample_intent`]: the single
358    /// source for the sample account the tests build on.
359    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    /// The canonical bytes of [`sample_order_fields`].
371    pub fn sample_order_bytes(cancelled: bool) -> [u8; SIZE] {
372        sample_order_fields(cancelled).encode()
373    }
374
375    /// Any valid set of order fields.
376    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    /// Any order-account bytes that decode: a valid order discriminator and
400    /// cancelled byte wrapping [`arb_intent_bytes`].
401    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        // `size_of_val` only consults the field type, so any value works.
427        let OrderFields {
428            bump,
429            cancelled,
430            amount_withdrawn,
431            amount_received,
432            created_by,
433            // Stored encoded, so its width matches `EncodedOrderIntent`, not the
434            // decoded `OrderIntent`.
435            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        // Indistinguishable from re-stamping the whole account with just the
501        // two amounts changed: every other byte is untouched.
502        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        // Indistinguishable from re-stamping the whole account with just the
523        // cancelled flag set: every other byte is untouched.
524        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        // Idempotent: re-stamping an already-cancelled order leaves every byte
540        // unchanged.
541        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        // Differs only in the embedded intent.
559        let encoded_intent: [u8; EncodedOrderIntent::SIZE] =
560            (&EncodedOrderIntent::from(&sample_order_fields(false).intent)).into();
561        // Hack: xoring each byte makes sure all bytes are different.
562        // In general, it isn't guaranteed that the result encodes to a
563        // valid intent, but in this case we know it because the only byte
564        // that may fail decoding is the flags byte, and `^0x02` only flips
565        // its `kind` flag bit, never a reserved one.
566        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        /// [`sample_order_bytes`] carrying its own canonical bump, plus the
628        /// address of the PDA it belongs at.
629        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            // An address unrelated to the intent's canonical seeds.
650            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            // Any bump other than the canonical one either derives a different
663            // address or fails to derive one at all (falling on curve); either
664            // way, the PDA can no longer be proven canonical.
665            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    // Property-based tests, non-deterministic.
688    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            // For any order fields, the bytes `OrderFields::encode` writes read
696            // back through the accessor as exactly those fields.
697            #[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            // For any decodable order bytes, reading every field back and
728            // re-encoding reproduces the same bytes.
729            #[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            // Overwriting the amounts in place equals writing the same fields
750            // with `OrderFields::encode` from scratch.
751            #[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}