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, SIZE};
320    use crate::data::intent::fixtures::{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
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        // `size_of_val` only consults the field type, so any value works.
415        let OrderFields {
416            bump,
417            cancelled,
418            amount_withdrawn,
419            amount_received,
420            created_by,
421            // Stored encoded, so its width matches `EncodedOrderIntent`, not the
422            // decoded `OrderIntent`.
423            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        // Indistinguishable from re-stamping the whole account with just the
489        // two amounts changed: every other byte is untouched.
490        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        // Indistinguishable from re-stamping the whole account with just the
511        // cancelled flag set: every other byte is untouched.
512        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        // Idempotent: re-stamping an already-cancelled order leaves every byte
528        // unchanged.
529        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        // Differs only in the embedded intent.
547        let encoded_intent: [u8; EncodedOrderIntent::SIZE] =
548            (&EncodedOrderIntent::from(&sample_order_fields(false).intent)).into();
549        // Hack: xoring each byte makes sure all bytes are different.
550        // In general, it isn't guaranteed that the result encodes to a
551        // valid intent, but in this case we know it because the only byte
552        // that may fail decoding is the flags byte, and `^0x01` only flips
553        // its `created_on_chain` flag bits, never a reserved one.
554        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        /// [`sample_order_bytes`] carrying its own canonical bump, plus the
611        /// address of the PDA it belongs at.
612        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            // An address unrelated to the intent's canonical seeds.
633            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            // Any bump other than the canonical one either derives a different
646            // address or fails to derive one at all (falling on curve); either
647            // way, the PDA can no longer be proven canonical.
648            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    // Property-based tests, non-deterministic.
671    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            // For any order fields, the bytes `OrderFields::encode` writes read
679            // back through the accessor as exactly those fields.
680            #[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            // For any bytes whose `cancelled` byte and intent flags are valid,
711            // reading every field back and re-encoding reproduces the same bytes.
712            #[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            // Overwriting the amounts in place equals writing the same fields
741            // with `OrderFields::encode` from scratch.
742            #[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}