Skip to main content

cow_settlement_interface/data/
order.rs

1//! Order PDA bodies and their canonical byte representation.
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//! Two types live in this module:
7//!
8//! - [`OrderAccount`] is the idiomatic Rust representation. Every value is
9//!   valid by construction: `cancelled` is a `bool`, `intent` is a fully
10//!   decoded [`OrderIntent`].
11//! - [`EncodedOrderAccount`] is its canonical byte representation, that is,
12//!   the exact bytes written to/read from the PDA.
13//!
14//! Conversion is asymmetric: [`EncodedOrderAccount`]`::from(OrderAccount)`
15//! is infallible; decoding raw bytes via [`OrderAccount`]`::try_from`
16//! returns `Result` and rejects an out-of-range `cancelled` byte or any
17//! intent byte the intent decoder rejects. There is no path that produces
18//! an [`OrderAccount`] whose `cancelled` byte or `intent` slot was not
19//! validated.
20
21use core::mem::size_of;
22
23use arrayref::{array_refs, mut_array_refs};
24use derive_more::Deref;
25use solana_account_view::AccountView;
26use solana_address::Address;
27use solana_hash::Hash;
28use solana_program_error::ProgramError;
29use solana_pubkey::Pubkey;
30
31use crate::data::intent::{self, EncodedOrderIntent, OrderIntent, OrderKind};
32use crate::pda::is_pda_with_signer_seeds;
33use crate::pda::order::order_pda_signer_seeds;
34use crate::{SettlementAccount, SettlementError};
35
36/// Idiomatic representation of an order PDA's body.
37#[derive(Clone, Debug, Eq, PartialEq, Default)]
38pub struct OrderAccount {
39    /// Canonical bump of the PDA this account lives at. Written at creation
40    /// time so callers don't need to supply it; see [`Self::load_from_pda`].
41    pub bump: u8,
42
43    /// `false` = the order is still active and can be filled; `true` = the
44    /// order has been cancelled by the owner and must not be filled.
45    pub cancelled: bool,
46
47    /// Cumulative amount of the sell token withdrawn for this order
48    /// across settlements. Starts at zero; monotonically increases.
49    pub amount_withdrawn: u64,
50
51    /// Cumulative amount of the buy token received for this order
52    /// across settlements. Starts at zero; monotonically increases.
53    pub amount_received: u64,
54
55    /// Account that funded the rent for this PDA. The rent is refunded
56    /// here when the order is cleared.
57    pub created_by: Pubkey,
58
59    /// The order intent stored in this PDA.
60    pub intent: OrderIntent,
61}
62
63impl OrderAccount {
64    /// Load and decode the order at the given PDA, and confirm the PDA is
65    /// derivable from its own data: both the UID and the bump feeding the
66    /// derivation come from the stored body.
67    pub fn load_from_pda(
68        order_pda: &AccountView,
69        program_id: &Address,
70    ) -> Result<Self, ProgramError> {
71        let (account, uid) = {
72            let data = order_pda.try_borrow()?;
73            let bytes: &[u8; EncodedOrderAccount::SIZE] = (&*data)
74                .try_into()
75                .map_err(|_| ProgramError::InvalidAccountData)?;
76            EncodedOrderAccount::decode_and_hash(bytes)?
77        };
78
79        if !is_pda_with_signer_seeds(
80            order_pda.address(),
81            program_id,
82            order_pda_signer_seeds(&uid, &[account.bump]),
83        ) {
84            return Err(SettlementError::AccountNotDerivable.into());
85        }
86
87        Ok(account)
88    }
89
90    /// Whether the order has been filled to the full amount of its exact side,
91    /// so no settlement can ever fill it again.
92    pub fn is_fully_filled(&self) -> bool {
93        let (filled, order_amount) =
94            fill_progress(&self.intent, self.amount_withdrawn, self.amount_received);
95        filled >= order_amount
96    }
97}
98
99/// Extract the values relevant for understanding the fill of an order.
100/// Returns a tuple. First return value is the amount currently filled, and the second return
101/// value is the amount that has been requested to be filled by the intent.
102pub fn fill_progress(
103    intent: &OrderIntent,
104    amount_withdrawn: u64,
105    amount_received: u64,
106) -> (u64, u64) {
107    match intent.flags.kind {
108        OrderKind::Sell => (amount_withdrawn, intent.sell_amount),
109        OrderKind::Buy => (amount_received, intent.buy_amount),
110    }
111}
112
113/// Canonical 264-byte representation of an [`OrderAccount`]. The bytes
114/// written to/read from the order PDA's data area.
115///
116/// Layout: one character per byte, cell widths proportional to field size,
117/// each divider belongs to the cell on its right. Integers are little-endian
118/// (Anchor/Borsh convention). The intent slot holds a verbatim
119/// [`EncodedOrderIntent`]; see that type's docs for its inner layout.
120///
121/// ```text
122///  ┌───── discriminator
123///  │┌──── bump
124///  ││┌─── cancelled
125///  ┌┬┬┬───────┬───────┬───────────────────────────────┬─────────────────...─────────────────┐
126///  ││││amount_│amount_│                               │                                     │
127///  ││││with-  │re-    │           created_by          │     intent (EncodedOrderIntent)     │
128///  ││││drawn  │ceived │                               │                                     │
129///  └┴┴┴───────┴───────┴───────────────────────────────┴─────────────────...─────────────────┘
130/// 0 1 2 3      11      19                              51                ...               264
131/// ```
132#[derive(Clone, Debug, Deref, Eq, PartialEq)]
133pub struct EncodedOrderAccount([u8; Self::SIZE]);
134
135impl EncodedOrderAccount {
136    // Per-field widths, derived from the `OrderAccount` field types.
137    const W_DISCRIMINATOR: usize = size_of::<u8>();
138    const W_BUMP: usize = size_of::<u8>();
139    const W_CANCELLED: usize = size_of::<bool>();
140    const W_AMOUNT_WITHDRAWN: usize = size_of::<u64>();
141    const W_AMOUNT_RECEIVED: usize = size_of::<u64>();
142    const W_CREATED_BY: usize = size_of::<Pubkey>();
143    const W_INTENT: usize = EncodedOrderIntent::SIZE;
144
145    pub const SIZE: usize = 264;
146
147    /// Single-byte account discriminator. See [`SettlementAccount`].
148    pub const DISCRIMINATOR: u8 = SettlementAccount::OrderAccount.discriminator();
149
150    /// Decode the account body and compute the embedded intent's UID in one
151    /// shot, mirroring [`EncodedOrderIntent::decode_and_hash`]. Decoding
152    /// validates the discriminator and the intent; returns
153    /// [`ProgramError::InvalidAccountData`] on a decode error.
154    pub fn decode_and_hash(bytes: &[u8; Self::SIZE]) -> Result<(OrderAccount, Hash), ProgramError> {
155        let order_account = OrderAccount::try_from(*bytes)?;
156        // The order UID is the hash of the intent's canonical bytes. Decoding
157        // succeeded, so the intent slot already holds those exact bytes: hash
158        // them in place rather than using `intent.uid()` to avoid re-encoding.
159        let (_, raw_intent) = array_refs![
160            bytes,
161            EncodedOrderAccount::SIZE - EncodedOrderAccount::W_INTENT,
162            EncodedOrderAccount::W_INTENT
163        ];
164        let intent_uid = intent::hash_bytes(raw_intent);
165        Ok((order_account, intent_uid))
166    }
167}
168
169/// Writes the canonical [`EncodedOrderAccount`] encoding of the given fields
170/// into `buffer`. `encoded_intent` must be a canonical [`EncodedOrderIntent`]
171/// encoding: validating it is the caller's responsibility.
172pub fn write_account(
173    buffer: &mut [u8; EncodedOrderAccount::SIZE],
174    bump: u8,
175    cancelled: bool,
176    amount_withdrawn: u64,
177    amount_received: u64,
178    created_by: &Pubkey,
179    encoded_intent: &[u8; EncodedOrderIntent::SIZE],
180) {
181    let (
182        discriminator_slot,
183        bump_slot,
184        cancelled_slot,
185        amount_withdrawn_slot,
186        amount_received_slot,
187        created_by_slot,
188        intent_slot,
189    ) = mut_array_refs![
190        buffer,
191        EncodedOrderAccount::W_DISCRIMINATOR,
192        EncodedOrderAccount::W_BUMP,
193        EncodedOrderAccount::W_CANCELLED,
194        EncodedOrderAccount::W_AMOUNT_WITHDRAWN,
195        EncodedOrderAccount::W_AMOUNT_RECEIVED,
196        EncodedOrderAccount::W_CREATED_BY,
197        EncodedOrderAccount::W_INTENT
198    ];
199    *discriminator_slot = [EncodedOrderAccount::DISCRIMINATOR];
200    *bump_slot = [bump];
201    *cancelled_slot = [cancelled as u8];
202    *amount_withdrawn_slot = amount_withdrawn.to_le_bytes();
203    *amount_received_slot = amount_received.to_le_bytes();
204    *created_by_slot = created_by.to_bytes();
205    *intent_slot = *encoded_intent;
206}
207
208impl From<EncodedOrderAccount> for [u8; EncodedOrderAccount::SIZE] {
209    fn from(encoded: EncodedOrderAccount) -> Self {
210        encoded.0
211    }
212}
213
214impl From<OrderAccount> for EncodedOrderAccount {
215    fn from(account: OrderAccount) -> Self {
216        let mut out = [0u8; Self::SIZE];
217        write_account(
218            &mut out,
219            account.bump,
220            account.cancelled,
221            account.amount_withdrawn,
222            account.amount_received,
223            &account.created_by,
224            &EncodedOrderIntent::from(&account.intent),
225        );
226        Self(out)
227    }
228}
229
230impl TryFrom<[u8; EncodedOrderAccount::SIZE]> for OrderAccount {
231    type Error = ProgramError;
232
233    fn try_from(bytes: [u8; EncodedOrderAccount::SIZE]) -> Result<Self, Self::Error> {
234        let (discriminator, bump, cancelled, amount_withdrawn, amount_received, created_by, intent) = array_refs![
235            &bytes,
236            EncodedOrderAccount::W_DISCRIMINATOR,
237            EncodedOrderAccount::W_BUMP,
238            EncodedOrderAccount::W_CANCELLED,
239            EncodedOrderAccount::W_AMOUNT_WITHDRAWN,
240            EncodedOrderAccount::W_AMOUNT_RECEIVED,
241            EncodedOrderAccount::W_CREATED_BY,
242            EncodedOrderAccount::W_INTENT
243        ];
244
245        if *discriminator != [EncodedOrderAccount::DISCRIMINATOR] {
246            return Err(ProgramError::InvalidAccountData);
247        }
248
249        Ok(OrderAccount {
250            bump: bump[0],
251            cancelled: match cancelled {
252                [0] => false,
253                [1] => true,
254                _ => return Err(ProgramError::InvalidAccountData),
255            },
256            amount_withdrawn: u64::from_le_bytes(*amount_withdrawn),
257            amount_received: u64::from_le_bytes(*amount_received),
258            created_by: Pubkey::new_from_array(*created_by),
259            intent: OrderIntent::try_from(intent).map_err(|_| ProgramError::InvalidAccountData)?,
260        })
261    }
262}
263
264impl TryFrom<&[u8]> for OrderAccount {
265    type Error = ProgramError;
266
267    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
268        let bytes: &[u8; EncodedOrderAccount::SIZE] = bytes
269            .try_into()
270            .map_err(|_| ProgramError::InvalidAccountData)?;
271        OrderAccount::try_from(*bytes)
272    }
273}
274
275impl TryFrom<EncodedOrderAccount> for OrderAccount {
276    type Error = ProgramError;
277
278    fn try_from(encoded: EncodedOrderAccount) -> Result<Self, Self::Error> {
279        OrderAccount::try_from(encoded.0)
280    }
281}
282
283#[cfg(any(test, feature = "test-fixtures"))]
284pub mod fixtures {
285    use proptest::prelude::*;
286
287    use super::{OrderAccount, Pubkey};
288    use crate::data::intent::fixtures::{arb_order_intent, sample_intent};
289
290    // Hardcoded but verified in a sanity-check test.
291    pub const DISCRIMINATOR_OFFSET: usize = 0;
292    pub const CANCELLED_OFFSET: usize = 2;
293    pub const INTENT_OFFSET: usize = 51;
294
295    /// Hand-picked example order account wrapping [`sample_intent`].
296    pub fn sample_account(cancelled: bool) -> OrderAccount {
297        OrderAccount {
298            bump: 0xfd,
299            cancelled,
300            amount_withdrawn: 0x0112_2334_4556_6778,
301            amount_received: 0x899a_abbc_cdde_eff0,
302            created_by: Pubkey::new_from_array([0x43; 32]),
303            intent: sample_intent(Default::default()),
304        }
305    }
306
307    /// Any valid [`OrderAccount`].
308    pub fn arb_order_account() -> impl Strategy<Value = OrderAccount> {
309        (
310            any::<u8>(),
311            any::<bool>(),
312            any::<u64>(),
313            any::<u64>(),
314            any::<[u8; 32]>(),
315            arb_order_intent(),
316        )
317            .prop_map(
318                |(bump, cancelled, amount_withdrawn, amount_received, created_by, intent)| {
319                    OrderAccount {
320                        bump,
321                        cancelled,
322                        amount_withdrawn,
323                        amount_received,
324                        created_by: Pubkey::new_from_array(created_by),
325                        intent,
326                    }
327                },
328            )
329    }
330}
331
332#[cfg(test)]
333mod tests {
334    use core::mem::size_of;
335
336    use super::fixtures::{sample_account, CANCELLED_OFFSET, DISCRIMINATOR_OFFSET, INTENT_OFFSET};
337    use super::*;
338    use crate::data::intent::fixtures::{sample_intent, FLAGS_OFFSET};
339    use crate::data::intent::Flags;
340
341    // Pin each width to the size of the `OrderAccount` field it encodes. The
342    // widths summing to `SIZE` is enforced separately, at compile time, by the
343    // `array_refs!` / `mut_array_refs!` invocations in the codec.
344    #[test]
345    fn widths_match_field_sizes() {
346        use core::mem::size_of_val;
347
348        // Any `OrderAccount` works: `size_of_val` only consults the field
349        // type, never the data.
350        let OrderAccount {
351            bump,
352            cancelled,
353            amount_withdrawn,
354            amount_received,
355            created_by,
356            // `OrderAccount` decodes the intent, but the encoded order uses
357            // `EncodedOrderIntent`, not `OrderIntent`.
358            intent: _intent,
359        } = sample_account(false);
360
361        assert_eq!(EncodedOrderAccount::W_BUMP, size_of_val(&bump));
362        assert_eq!(EncodedOrderAccount::W_CANCELLED, size_of_val(&cancelled));
363        assert_eq!(
364            EncodedOrderAccount::W_AMOUNT_WITHDRAWN,
365            size_of_val(&amount_withdrawn)
366        );
367        assert_eq!(
368            EncodedOrderAccount::W_AMOUNT_RECEIVED,
369            size_of_val(&amount_received)
370        );
371        assert_eq!(EncodedOrderAccount::W_CREATED_BY, size_of_val(&created_by));
372
373        assert_eq!(
374            EncodedOrderAccount::W_INTENT,
375            size_of::<EncodedOrderIntent>()
376        );
377    }
378
379    #[test]
380    fn roundtrip_both_cancelled_states() {
381        for cancelled in [false, true] {
382            let account = sample_account(cancelled);
383            let encoded = EncodedOrderAccount::from(account.clone());
384            let decoded = OrderAccount::try_from(encoded).expect("example must decode");
385            assert_eq!(decoded, account);
386        }
387    }
388
389    #[test]
390    fn is_fully_filled_tracks_the_exact_side_only() {
391        const SELL_AMOUNT: u64 = 1_000;
392        const BUY_AMOUNT: u64 = 2_000;
393
394        let account = |kind, amount_withdrawn, amount_received| OrderAccount {
395            amount_withdrawn,
396            amount_received,
397            intent: OrderIntent {
398                sell_amount: SELL_AMOUNT,
399                buy_amount: BUY_AMOUNT,
400                ..sample_intent(Flags {
401                    kind,
402                    ..Default::default()
403                })
404            },
405            ..sample_account(false)
406        };
407
408        // (kind, withdrawn, received, expected)
409        let cases = [
410            (OrderKind::Sell, SELL_AMOUNT, 0, true), // fully filled SELL order (stolen money, generally impossible)
411            (OrderKind::Buy, 0, BUY_AMOUNT, true),   // fully filled BUY order (free money)
412            (OrderKind::Sell, u64::MAX, 0, true), // sell fill past the intent amount (should be impossible)
413            (OrderKind::Sell, u64::MAX, u64::MAX, true), // sell fill past the intent amount (should be impossible)
414            (OrderKind::Buy, 0, u64::MAX, true),         // buy fill past the intent amount
415            (OrderKind::Buy, u64::MAX, u64::MAX, true),  // buy fill past the intent amount
416            (OrderKind::Sell, 0, 0, false),              // unfilled order
417            (OrderKind::Sell, SELL_AMOUNT - 1, BUY_AMOUNT, false), // not fully filled SELL order
418            (OrderKind::Buy, SELL_AMOUNT, BUY_AMOUNT - 1, false), // not fully filled BUY order with fully filled sell side (generally should be impossible)
419        ];
420        for (kind, withdrawn, received, expected) in cases {
421            assert_eq!(
422                account(kind, withdrawn, received).is_fully_filled(),
423                expected,
424                "{kind:?} order withdrawn={withdrawn} received={received}",
425            );
426        }
427    }
428
429    #[test]
430    fn sanity_check_offsets() {
431        fn first_differing_byte(lhs: &[u8], rhs: &[u8]) -> Option<usize> {
432            lhs.iter().zip(rhs).position(|(l, r)| l != r)
433        }
434
435        let mut sample_account_base = sample_account(false);
436        let base: [u8; EncodedOrderAccount::SIZE] =
437            EncodedOrderAccount::from(sample_account_base.clone()).into();
438        let cancelled: [u8; EncodedOrderAccount::SIZE] =
439            EncodedOrderAccount::from(sample_account(true)).into();
440        assert_eq!(
441            first_differing_byte(&base, &cancelled).expect("should differ in the cancelled byte"),
442            CANCELLED_OFFSET
443        );
444
445        // Differs only in the embedded intent.
446        let encoded_intent: [u8; EncodedOrderIntent::SIZE] =
447            (&EncodedOrderIntent::from(&sample_account_base.intent)).into();
448        // Hack: xoring each byte makes sure all bytes are different.
449        // In general, it isn't guaranteed that the result encodes to a
450        // valid intent, but in this case we know it because the only byte
451        // that may fail decoding is the flags byte, and `^0x01` only flips
452        // its `created_on_chain` flag bits, never a reserved one.
453        let bitwise_different_encoded_intent: [u8; EncodedOrderIntent::SIZE] =
454            encoded_intent.map(|b| b ^ 0x01);
455        sample_account_base.intent =
456            OrderIntent::try_from(&bitwise_different_encoded_intent).expect("hack should work");
457        let changed_intent: [u8; EncodedOrderAccount::SIZE] =
458            EncodedOrderAccount::from(sample_account_base).into();
459        assert_eq!(
460            first_differing_byte(&base, &changed_intent).expect("should differ in the intent slot"),
461            INTENT_OFFSET
462        );
463    }
464
465    #[test]
466    fn decode_rejects_wrong_discriminator() {
467        let mut bytes: [u8; EncodedOrderAccount::SIZE] =
468            EncodedOrderAccount::from(sample_account(false)).into();
469        bytes[0] ^= 0xff;
470        let err = OrderAccount::try_from(bytes).expect_err("wrong discriminator must be rejected");
471        assert_eq!(err, ProgramError::InvalidAccountData);
472    }
473
474    #[test]
475    fn decode_rejects_non_boolean_cancelled() {
476        let mut bytes: [u8; EncodedOrderAccount::SIZE] =
477            EncodedOrderAccount::from(sample_account(false)).into();
478        for bad in 0x02u8..=0xff {
479            bytes[CANCELLED_OFFSET] = bad;
480            let err = OrderAccount::try_from(bytes)
481                .expect_err("non-boolean cancelled byte must be rejected");
482            assert_eq!(err, ProgramError::InvalidAccountData);
483        }
484    }
485
486    #[test]
487    fn decode_propagates_invalid_intent() {
488        let mut bytes: [u8; EncodedOrderAccount::SIZE] =
489            EncodedOrderAccount::from(sample_account(false)).into();
490        // Set a reserved bit of the flags byte inside the intent slot: the
491        // intent decoder rejects it and the order-account decode surfaces
492        // that failure as `InvalidAccountData`.
493        bytes[INTENT_OFFSET + FLAGS_OFFSET] = 0xff;
494        let err = OrderAccount::try_from(bytes)
495            .expect_err("an invalid intent flags byte must propagate as a decode failure");
496        assert_eq!(err, ProgramError::InvalidAccountData);
497    }
498
499    #[test]
500    fn decode_and_hash_catches_errors() {
501        let mut bytes: [u8; EncodedOrderAccount::SIZE] =
502            EncodedOrderAccount::from(sample_account(false)).into();
503        // Corrupt the `cancelled` byte to an out-of-range value so the
504        // underlying `try_from` rejects it.
505        bytes[CANCELLED_OFFSET] = 0xff;
506        let err = EncodedOrderAccount::decode_and_hash(&bytes)
507            .expect_err("decode_and_hash must propagate the try_from error");
508        assert_eq!(err, ProgramError::InvalidAccountData);
509    }
510
511    #[test]
512    fn try_from_exact_length_slice_decodes() {
513        let account = sample_account(false);
514        let bytes: [u8; EncodedOrderAccount::SIZE] =
515            EncodedOrderAccount::from(account.clone()).into();
516
517        let decoded = OrderAccount::try_from(&bytes[..]).expect("exact-length slice must decode");
518        assert_eq!(decoded, account);
519    }
520
521    #[test]
522    fn try_from_wrong_length_slice_is_rejected() {
523        let bytes: [u8; EncodedOrderAccount::SIZE] =
524            EncodedOrderAccount::from(sample_account(false)).into();
525
526        assert_eq!(
527            OrderAccount::try_from(&bytes[..EncodedOrderAccount::SIZE - 1]),
528            Err(ProgramError::InvalidAccountData),
529        );
530
531        let too_long = [bytes.as_slice(), [0].as_slice()].concat();
532        assert_eq!(
533            OrderAccount::try_from(&too_long[..]),
534            Err(ProgramError::InvalidAccountData),
535        );
536    }
537
538    #[test]
539    fn try_from_slice_forwards_decoding_errors() {
540        let mut bytes: [u8; EncodedOrderAccount::SIZE] =
541            EncodedOrderAccount::from(sample_account(false)).into();
542        bytes[CANCELLED_OFFSET] = 0xff;
543        assert_eq!(
544            OrderAccount::try_from(&bytes[..]),
545            Err(ProgramError::InvalidAccountData),
546        );
547    }
548
549    mod load_from_pda {
550        use super::*;
551        use crate::instruction::fixtures::fake_account_with_data;
552        use crate::pda::order::find_order_pda;
553
554        const PROGRAM_ID: Address = Address::new_from_array([0xc0; 32]);
555
556        /// [`sample_account`] carrying its own canonical bump, plus the address
557        /// of the PDA it belongs at.
558        fn canonical_account(cancelled: bool) -> (OrderAccount, Address) {
559            let mut account = sample_account(cancelled);
560            let (pda_address, bump) = find_order_pda(&PROGRAM_ID, &account.intent.uid());
561            account.bump = bump;
562            (account, pda_address)
563        }
564
565        #[test]
566        fn accepts_the_canonical_pda() {
567            let (account, pda_address) = canonical_account(false);
568            let order_pda = fake_account_with_data(
569                pda_address,
570                &EncodedOrderAccount::from(account.clone())[..],
571            );
572
573            let loaded = OrderAccount::load_from_pda(&order_pda, &PROGRAM_ID)
574                .expect("canonical PDA must load");
575            assert_eq!(loaded, account);
576        }
577
578        #[test]
579        fn rejects_a_non_canonical_address() {
580            let (account, _) = canonical_account(false);
581            // An address unrelated to the intent's canonical seeds.
582            let wrong_address = Pubkey::new_from_array([0x42; 32]);
583            let order_pda =
584                fake_account_with_data(wrong_address, &EncodedOrderAccount::from(account)[..]);
585
586            let err = OrderAccount::load_from_pda(&order_pda, &PROGRAM_ID)
587                .expect_err("a non-canonical address must be rejected");
588            assert_eq!(err, SettlementError::AccountNotDerivable.into());
589        }
590
591        #[test]
592        fn rejects_a_stored_bump_that_does_not_derive_the_address() {
593            let (mut account, pda_address) = canonical_account(false);
594            // Any bump other than the canonical one either derives a
595            // different address or fails to derive one at all (falling on
596            // curve); either way, the PDA can no longer be proven canonical.
597            account.bump = account.bump.wrapping_sub(1);
598            let order_pda =
599                fake_account_with_data(pda_address, &EncodedOrderAccount::from(account)[..]);
600
601            let err = OrderAccount::load_from_pda(&order_pda, &PROGRAM_ID)
602                .expect_err("a non-canonical bump must be rejected");
603            assert_eq!(err, SettlementError::AccountNotDerivable.into());
604        }
605
606        #[test]
607        fn propagates_decode_errors() {
608            let (account, pda_address) = canonical_account(false);
609            let mut bytes: [u8; EncodedOrderAccount::SIZE] =
610                EncodedOrderAccount::from(account).into();
611            bytes[CANCELLED_OFFSET] = 0xff;
612            let order_pda = fake_account_with_data(pda_address, &bytes);
613
614            let err = OrderAccount::load_from_pda(&order_pda, &PROGRAM_ID)
615                .expect_err("a corrupt account must fail to decode");
616            assert_eq!(err, ProgramError::InvalidAccountData);
617        }
618    }
619
620    #[test]
621    fn direct_write_account_matches_order_account_decoding() {
622        let bump = 254;
623        let cancelled = true;
624        let amount_withdrawn = 1337;
625        let amount_received = 31337;
626        let intent = sample_intent(Default::default());
627        let created_by = Pubkey::new_from_array([0x42u8; 32]);
628
629        let mut buffer = [0u8; EncodedOrderAccount::SIZE];
630        write_account(
631            &mut buffer,
632            bump,
633            cancelled,
634            amount_withdrawn,
635            amount_received,
636            &created_by,
637            &<[u8; EncodedOrderIntent::SIZE]>::from(&EncodedOrderIntent::from(&intent)),
638        );
639        let direct = EncodedOrderAccount(buffer);
640        let via_order_account = EncodedOrderAccount::from(OrderAccount {
641            bump,
642            cancelled,
643            amount_withdrawn,
644            amount_received,
645            created_by,
646            intent,
647        });
648
649        assert_eq!(direct, via_order_account);
650    }
651
652    // Property-based tests, non-deterministic.
653    mod proptest {
654        use ::proptest::{prelude::*, test_runner::TestCaseError};
655
656        use super::*;
657        use crate::data::{intent::fixtures::arb_flags_byte, order::fixtures::arb_order_account};
658
659        proptest! {
660            // For any `OrderAccount`, encode then decode returns the same
661            // account.
662            #[test]
663            fn account_roundtrip(account in arb_order_account()) {
664                let encoded = EncodedOrderAccount::from(account.clone());
665                let decoded = OrderAccount::try_from(encoded)
666                    .map_err(|e| TestCaseError::fail(format!("decode failed: {e:?}")))?;
667                prop_assert_eq!(decoded, account);
668            }
669
670            // For any bytes whose `cancelled` and embedded intent
671            // discriminants are valid, decode + re-encode produces the
672            // same bytes back.
673            #[test]
674            fn bytes_roundtrip(
675                mut bytes in any::<[u8; EncodedOrderAccount::SIZE]>(),
676                cancelled in any::<bool>(),
677                flags in arb_flags_byte(),
678            ) {
679                bytes[DISCRIMINATOR_OFFSET] = EncodedOrderAccount::DISCRIMINATOR;
680                bytes[CANCELLED_OFFSET] = cancelled as u8;
681                bytes[INTENT_OFFSET + FLAGS_OFFSET] = flags;
682                let account = OrderAccount::try_from(bytes)
683                    .map_err(|e| TestCaseError::fail(format!("decode failed: {e:?}")))?;
684                prop_assert_eq!(*EncodedOrderAccount::from(account), bytes);
685            }
686
687            #[test]
688            fn consistent_decode_and_hash(account in arb_order_account()) {
689                let encoded = EncodedOrderAccount::from(account.clone());
690                let (decoded, hash) = EncodedOrderAccount::decode_and_hash(&encoded)
691                    .map_err(|e| TestCaseError::fail(format!("decode failed: {e:?}")))?;
692                prop_assert_eq!(hash, account.intent.uid());
693                prop_assert_eq!(decoded, account);
694            }
695        }
696    }
697}