Skip to main content

cow_settlement_interface/data/
intent.rs

1//! Order intents and their canonical byte representation.
2//!
3//! The intent has two representations:
4//!
5//! - [`OrderIntent`] is the idiomatic Rust representation.
6//! - [`EncodedOrderIntent`] is its canonical byte representation: the only
7//!   thing sent on the wire and also the data encoding used to generate the
8//!   order UID. There, `kind` and the intent's booleans share a single flags
9//!   byte.
10//!
11//! Conversion is asymmetric: [`EncodedOrderIntent`]`::from(OrderIntent)` is
12//! infallible, but decoding raw bytes via [`OrderIntent`]`::try_from` returns
13//! `Result` and rejects a flags byte carrying a bit the encoding doesn't
14//! define.
15
16use core::mem::size_of;
17
18use arrayref::{array_refs, mut_array_refs};
19use derive_more::Deref;
20use solana_hash::Hash;
21use solana_program_error::ProgramError;
22use solana_pubkey::Pubkey;
23
24/// Direction of the trade. The discriminants are the values the `kind` bit of
25/// the encoded flags byte takes.
26#[derive(Clone, Copy, Debug, Eq, PartialEq, Default)]
27#[repr(u8)]
28pub enum OrderKind {
29    #[default]
30    Sell = 0,
31    Buy = 1,
32}
33
34/// Collection of [`OrderIntent`] fields that can be represented as a single bit.
35#[derive(Clone, Copy, Debug, Eq, PartialEq, Default)]
36pub struct Flags {
37    /// How the order is authenticated: `true` if the owner creates it
38    /// themselves with a `CreateOrder` instruction they sign; `false` if it's
39    /// authenticated off-chain by an Ed25519 signature, which lets anyone
40    /// holding that signature create the order.
41    pub created_on_chain: bool,
42
43    /// Whether `sell_amount` or `buy_amount` is the exact figure; the
44    /// other side is treated as the limit (minimum to receive for `Sell`,
45    /// maximum to spend for `Buy`).
46    pub kind: OrderKind,
47
48    /// If `true`, the order may be filled across multiple settlements;
49    /// proceeds and consumption scale proportionally with the amount of
50    /// the sell side that's been used. If `false`, a single settlement
51    /// must consume the full sell amount (fill-or-kill).
52    pub partially_fillable: bool,
53}
54
55impl Flags {
56    // The bit each field occupies
57    const CREATED_ON_CHAIN: u8 = 1 << 0;
58    const KIND: u8 = 1 << 1;
59    const PARTIALLY_FILLABLE: u8 = 1 << 2;
60
61    /// Every bit the encoding defines; the others are reserved.
62    const DEFINED: u8 = Self::CREATED_ON_CHAIN | Self::KIND | Self::PARTIALLY_FILLABLE;
63}
64
65impl From<Flags> for [u8; 1] {
66    /// The canonical flags byte. Reserved bits are left clear.
67    fn from(flags: Flags) -> Self {
68        let mut byte = 0;
69        if flags.created_on_chain {
70            byte |= Flags::CREATED_ON_CHAIN;
71        }
72        if flags.kind == OrderKind::Buy {
73            byte |= Flags::KIND;
74        }
75        if flags.partially_fillable {
76            byte |= Flags::PARTIALLY_FILLABLE;
77        }
78        [byte]
79    }
80}
81
82impl TryFrom<[u8; 1]> for Flags {
83    type Error = ProgramError;
84
85    /// Decodes a flags byte, rejecting any reserved bit with
86    /// [`ProgramError::InvalidInstructionData`]. A reserved bit carries no
87    /// meaning to this version of the program, so accepting it would give the
88    /// same flags several encodings, and with them several UIDs.
89    fn try_from(bytes: [u8; 1]) -> Result<Self, Self::Error> {
90        let [byte] = bytes;
91        if byte & !Self::DEFINED != 0 {
92            return Err(ProgramError::InvalidInstructionData);
93        }
94        Ok(Flags {
95            created_on_chain: byte & Self::CREATED_ON_CHAIN != 0,
96            kind: if byte & Self::KIND == 0 {
97                OrderKind::Sell
98            } else {
99                OrderKind::Buy
100            },
101            partially_fillable: byte & Self::PARTIALLY_FILLABLE != 0,
102        })
103    }
104}
105
106#[derive(Clone, Debug, Eq, PartialEq, Default)]
107pub struct OrderIntent {
108    /// Account authorized to create and invalidate this order and whose
109    /// signature authenticates it. For off-chain orders this is the Ed25519
110    /// signer; for on-chain creation it must be the transaction signer.
111    pub owner: Pubkey,
112
113    /// Token account that receives the buy-side proceeds. Implicitly
114    /// encodes the recipient.
115    pub buy_token_account: Pubkey,
116
117    /// Mint of the buy token.
118    pub buy_mint: Pubkey,
119
120    /// Token account the sell-side funds are pulled from. Implicitly
121    /// encodes the spender. The settlement state PDA must hold the SPL
122    /// `delegate` on this account for the order to be settleable.
123    /// This token account must be owned by the intent owner. An intent
124    /// that doesn't satisfy this property will be rejected.
125    pub sell_token_account: Pubkey,
126
127    /// Mint of the sell token.
128    pub sell_mint: Pubkey,
129
130    /// Amount of the sell token. For `Sell` orders this is the exact
131    /// amount to be sold (subject to `partially_fillable`); for `Buy`
132    /// orders it is the maximum the user is willing to spend.
133    pub sell_amount: u64,
134
135    /// Amount of the buy token. For `Buy` orders this is the exact amount
136    /// to be received (subject to `partially_fillable`); for `Sell`
137    /// orders it is the minimum the user is willing to receive.
138    pub buy_amount: u64,
139
140    /// Unix timestamp after which the order expires.
141    /// The order cannot be executed after expiration.
142    pub valid_to: u32,
143
144    /// The settings the encoding packs bit by bit into a single byte; see
145    /// [`Flags`].
146    pub flags: Flags,
147
148    /// Opaque 32 bytes set by the order creator. Not interpreted by the
149    /// settlement program; used off-chain for metadata such as the
150    /// frontend version, slippage hints, or attribution.
151    pub app_data: [u8; 32],
152}
153
154/// Canonical 213-byte representation of an [`OrderIntent`]. The wire format and
155/// the order UID preimage.
156///
157/// Layout: one character per byte, cell widths proportional to field size,
158/// each divider belongs to the cell on its right. The byte range is
159/// annotated below. Amounts and `valid_to` are little-endian encoded.
160///
161/// ```text
162///                                                                                                                                                                           flags ────┐
163/// ┌───────────────────────────────┬───────────────────────────────┬───────────────────────────────┬───────────────────────────────┬───────────────────────────────┬───────┬───────┬───┬┬───────────────────────────────┐
164/// │                               │                               │                               │                               │                               │sell_  │buy_   │val││                               │
165/// │             owner             │       buy_token_account       │           buy_mint            │      sell_token_account       │           sell_mint           │       │       │id_││           app_data            │
166/// │                               │                               │                               │                               │                               │amount │amount │to ││                               │
167/// └───────────────────────────────┴───────────────────────────────┴───────────────────────────────┴───────────────────────────────┴───────────────────────────────┴───────┴───────┴───┴┴───────────────────────────────┘
168/// 0                               32                              64                              96                              128                             160     168     176 180                           213
169///                                                                                                                                                                                      181
170/// ```
171///
172#[derive(Clone, Debug, Deref, Eq, PartialEq)]
173pub struct EncodedOrderIntent([u8; Self::SIZE]);
174
175impl EncodedOrderIntent {
176    // Per-field widths, derived from the `OrderIntent` field types.
177    const WIDTH_OWNER: usize = size_of::<Pubkey>();
178    const WIDTH_BUY_TOKEN: usize = size_of::<Pubkey>();
179    const WIDTH_BUY_MINT: usize = size_of::<Pubkey>();
180    const WIDTH_SELL_TOKEN: usize = size_of::<Pubkey>();
181    const WIDTH_SELL_MINT: usize = size_of::<Pubkey>();
182    const WIDTH_SELL_AMOUNT: usize = size_of::<u64>();
183    const WIDTH_BUY_AMOUNT: usize = size_of::<u64>();
184    const WIDTH_VALID_TO: usize = size_of::<u32>();
185    const WIDTH_FLAGS: usize = size_of::<u8>();
186    const WIDTH_APP_DATA: usize = size_of::<[u8; 32]>();
187
188    pub const SIZE: usize = 213;
189
190    /// Canonical hash of the bytes.
191    pub fn hash(&self) -> Hash {
192        hash_bytes(&self.0)
193    }
194
195    /// Decode raw bytes to an [`OrderIntent`] and compute the UID in one shot.
196    /// Returns [`ProgramError::InvalidInstructionData`] for a flags byte that
197    /// doesn't encode correctly; every other byte combination decodes.
198    pub fn decode_and_hash(bytes: &[u8; Self::SIZE]) -> Result<(OrderIntent, Hash), ProgramError> {
199        let intent = OrderIntent::try_from(bytes)?;
200        // The UID is the SHA-256 of the input bytes. Hashing the input
201        // (no re-encode) is correct because encode/decode is a bijection on
202        // inputs that pass validation. Any normalization added to the `From`
203        // or `TryFrom` impls later would break this and the UID would silently
204        // diverge from `OrderIntent::uid()`.
205        let uid = hash_bytes(bytes);
206        Ok((intent, uid))
207    }
208}
209
210pub fn hash_bytes(bytes: &[u8; EncodedOrderIntent::SIZE]) -> Hash {
211    solana_sha256_hasher::hashv(&[bytes.as_slice()])
212}
213
214impl From<&EncodedOrderIntent> for [u8; EncodedOrderIntent::SIZE] {
215    fn from(encoded: &EncodedOrderIntent) -> Self {
216        encoded.0
217    }
218}
219
220impl From<&OrderIntent> for EncodedOrderIntent {
221    fn from(intent: &OrderIntent) -> Self {
222        // `mut_array_refs` checks that `SIZE` is consistent with the sum of
223        // the widths.
224        let mut out = [0u8; Self::SIZE];
225        let (
226            owner,
227            buy_token,
228            buy_mint,
229            sell_token,
230            sell_mint,
231            sell_amount,
232            buy_amount,
233            valid_to,
234            flags,
235            app_data,
236        ) = mut_array_refs![
237            &mut out,
238            EncodedOrderIntent::WIDTH_OWNER,
239            EncodedOrderIntent::WIDTH_BUY_TOKEN,
240            EncodedOrderIntent::WIDTH_BUY_MINT,
241            EncodedOrderIntent::WIDTH_SELL_TOKEN,
242            EncodedOrderIntent::WIDTH_SELL_MINT,
243            EncodedOrderIntent::WIDTH_SELL_AMOUNT,
244            EncodedOrderIntent::WIDTH_BUY_AMOUNT,
245            EncodedOrderIntent::WIDTH_VALID_TO,
246            EncodedOrderIntent::WIDTH_FLAGS,
247            EncodedOrderIntent::WIDTH_APP_DATA
248        ];
249        *owner = intent.owner.to_bytes();
250        *buy_token = intent.buy_token_account.to_bytes();
251        *buy_mint = intent.buy_mint.to_bytes();
252        *sell_token = intent.sell_token_account.to_bytes();
253        *sell_mint = intent.sell_mint.to_bytes();
254        *sell_amount = intent.sell_amount.to_le_bytes();
255        *buy_amount = intent.buy_amount.to_le_bytes();
256        *valid_to = intent.valid_to.to_le_bytes();
257        *flags = intent.flags.into();
258        *app_data = intent.app_data;
259        Self(out)
260    }
261}
262
263impl TryFrom<&[u8; EncodedOrderIntent::SIZE]> for OrderIntent {
264    type Error = ProgramError;
265
266    fn try_from(bytes: &[u8; EncodedOrderIntent::SIZE]) -> Result<Self, Self::Error> {
267        // It's important that the byte representation of an intent is unique.
268        // This function should be injective: there shouldn't be two byte
269        // sequences that decode to the same order intent.
270        // If this were to happen, then the user intent may not be recognized
271        // as valid or it might be possible to replay the same order more
272        // than once.
273
274        let (
275            owner,
276            buy_token,
277            buy_mint,
278            sell_token,
279            sell_mint,
280            sell_amount,
281            buy_amount,
282            valid_to,
283            flags,
284            app_data,
285        ) = array_refs![
286            bytes,
287            EncodedOrderIntent::WIDTH_OWNER,
288            EncodedOrderIntent::WIDTH_BUY_TOKEN,
289            EncodedOrderIntent::WIDTH_BUY_MINT,
290            EncodedOrderIntent::WIDTH_SELL_TOKEN,
291            EncodedOrderIntent::WIDTH_SELL_MINT,
292            EncodedOrderIntent::WIDTH_SELL_AMOUNT,
293            EncodedOrderIntent::WIDTH_BUY_AMOUNT,
294            EncodedOrderIntent::WIDTH_VALID_TO,
295            EncodedOrderIntent::WIDTH_FLAGS,
296            EncodedOrderIntent::WIDTH_APP_DATA
297        ];
298
299        Ok(OrderIntent {
300            owner: Pubkey::new_from_array(*owner),
301            buy_token_account: Pubkey::new_from_array(*buy_token),
302            buy_mint: Pubkey::new_from_array(*buy_mint),
303            sell_token_account: Pubkey::new_from_array(*sell_token),
304            sell_mint: Pubkey::new_from_array(*sell_mint),
305            sell_amount: u64::from_le_bytes(*sell_amount),
306            buy_amount: u64::from_le_bytes(*buy_amount),
307            valid_to: u32::from_le_bytes(*valid_to),
308            flags: Flags::try_from(*flags)?,
309            app_data: *app_data,
310        })
311    }
312}
313
314impl TryFrom<&EncodedOrderIntent> for OrderIntent {
315    type Error = ProgramError;
316
317    fn try_from(encoded: &EncodedOrderIntent) -> Result<Self, Self::Error> {
318        OrderIntent::try_from(&encoded.0)
319    }
320}
321
322impl OrderIntent {
323    /// SHA-256 of the canonical bytes. Doubles as the order UID and the
324    /// middle seed of the order PDA. On SBF this compiles to a single
325    /// `sol_sha256` syscall; off-target it goes through the `sha2` crate.
326    pub fn uid(&self) -> Hash {
327        EncodedOrderIntent::from(self).hash()
328    }
329}
330
331#[cfg(any(test, feature = "test-fixtures"))]
332pub mod fixtures {
333    use proptest::{prelude::*, strategy::Union};
334
335    use super::{Flags, OrderIntent, OrderKind, Pubkey};
336
337    /// Every valid [`OrderKind`].
338    pub const ALL_ORDER_KINDS: [OrderKind; 2] = [OrderKind::Sell, OrderKind::Buy];
339
340    // Hardcoded but verified in a sanity-check test.
341    pub const FLAGS_OFFSET: usize = 180;
342
343    pub fn sample_intent(flags: Flags) -> OrderIntent {
344        OrderIntent {
345            owner: Pubkey::new_from_array([0x11; 32]),
346            buy_token_account: Pubkey::new_from_array([0x22; 32]),
347            buy_mint: Pubkey::new_from_array([0x33; 32]),
348            sell_token_account: Pubkey::new_from_array([0x44; 32]),
349            sell_mint: Pubkey::new_from_array([0x55; 32]),
350            sell_amount: 0x0123_4567_89ab_cdef,
351            buy_amount: 0xfedc_ba98_7654_3210,
352            valid_to: 0xdead_beef,
353            flags,
354            app_data: [0x66; 32],
355        }
356    }
357
358    /// Any valid [`OrderKind`].
359    pub fn arb_order_kind() -> impl Strategy<Value = OrderKind> {
360        Union::new(ALL_ORDER_KINDS.map(Just))
361    }
362
363    /// Any valid [`Flags`].
364    pub fn arb_flags() -> impl Strategy<Value = Flags> {
365        (any::<bool>(), arb_order_kind(), any::<bool>()).prop_map(
366            |(created_on_chain, kind, partially_fillable)| Flags {
367                created_on_chain,
368                kind,
369                partially_fillable,
370            },
371        )
372    }
373
374    /// Any flags byte the decoder accepts.
375    pub fn arb_flags_byte() -> impl Strategy<Value = u8> {
376        any::<u8>().prop_map(|byte| byte & Flags::DEFINED)
377    }
378
379    /// Any flags byte the decoder rejects.
380    pub fn arb_invalid_flags_byte() -> impl Strategy<Value = u8> {
381        any::<u8>().prop_filter("must have at least one bit that is undefined", |byte| {
382            byte & !Flags::DEFINED > 0
383        })
384    }
385
386    /// Any valid [`OrderIntent`].
387    pub fn arb_order_intent() -> impl Strategy<Value = OrderIntent> {
388        (
389            any::<[u8; 32]>(),
390            any::<[u8; 32]>(),
391            any::<[u8; 32]>(),
392            any::<[u8; 32]>(),
393            any::<[u8; 32]>(),
394            any::<u64>(),
395            any::<u64>(),
396            any::<u32>(),
397            arb_flags(),
398            any::<[u8; 32]>(),
399        )
400            .prop_map(
401                |(
402                    owner,
403                    buy_tok,
404                    buy_mint,
405                    sell_tok,
406                    sell_mint,
407                    sell_amount,
408                    buy_amount,
409                    valid_to,
410                    flags,
411                    app,
412                )| {
413                    OrderIntent {
414                        owner: Pubkey::new_from_array(owner),
415                        buy_token_account: Pubkey::new_from_array(buy_tok),
416                        buy_mint: Pubkey::new_from_array(buy_mint),
417                        sell_token_account: Pubkey::new_from_array(sell_tok),
418                        sell_mint: Pubkey::new_from_array(sell_mint),
419                        sell_amount,
420                        buy_amount,
421                        valid_to,
422                        flags,
423                        app_data: app,
424                    }
425                },
426            )
427    }
428}
429
430#[cfg(test)]
431mod tests {
432    use crate::data::intent::fixtures::FLAGS_OFFSET;
433
434    use super::fixtures::sample_intent;
435    use super::*;
436
437    // Every shape an `OrderIntent` can take on its validated axes: the
438    // `created_on_chain` flag bit, the `kind` enum, and the
439    // `partially_fillable` flag bit.
440    fn all_flag_shapes() -> impl Iterator<Item = OrderIntent> {
441        [false, true].into_iter().flat_map(|created_on_chain| {
442            fixtures::ALL_ORDER_KINDS.into_iter().flat_map(move |kind| {
443                [false, true].into_iter().map(move |partially_fillable| {
444                    sample_intent(Flags {
445                        created_on_chain,
446                        kind,
447                        partially_fillable,
448                    })
449                })
450            })
451        })
452    }
453
454    // Pin each width to the size of the `OrderIntent` field it encodes. The
455    // widths summing to `SIZE` is enforced separately, at compile time, by the
456    // `array_refs!` / `mut_array_refs!` invocations in the codec.
457    #[test]
458    fn widths_match_field_sizes() {
459        use core::mem::{size_of, size_of_val};
460
461        // Any `OrderIntent` works: `size_of_val` only consults the field
462        // type, never the data.
463        let intent = sample_intent(Default::default());
464
465        assert_eq!(EncodedOrderIntent::WIDTH_OWNER, size_of_val(&intent.owner));
466        assert_eq!(
467            EncodedOrderIntent::WIDTH_BUY_TOKEN,
468            size_of_val(&intent.buy_token_account)
469        );
470        assert_eq!(
471            EncodedOrderIntent::WIDTH_BUY_MINT,
472            size_of_val(&intent.buy_mint)
473        );
474        assert_eq!(
475            EncodedOrderIntent::WIDTH_SELL_TOKEN,
476            size_of_val(&intent.sell_token_account)
477        );
478        assert_eq!(
479            EncodedOrderIntent::WIDTH_SELL_MINT,
480            size_of_val(&intent.sell_mint)
481        );
482        assert_eq!(
483            EncodedOrderIntent::WIDTH_SELL_AMOUNT,
484            size_of_val(&intent.sell_amount)
485        );
486        assert_eq!(
487            EncodedOrderIntent::WIDTH_BUY_AMOUNT,
488            size_of_val(&intent.buy_amount)
489        );
490        assert_eq!(
491            EncodedOrderIntent::WIDTH_VALID_TO,
492            size_of_val(&intent.valid_to)
493        );
494        assert_eq!(
495            EncodedOrderIntent::WIDTH_FLAGS,
496            // in truth if there was a problem here it would actually cause a compilation error
497            size_of_val::<[u8; 1]>(&Flags::default().into())
498        );
499        assert_eq!(
500            EncodedOrderIntent::WIDTH_APP_DATA,
501            size_of_val(&intent.app_data)
502        );
503
504        assert_eq!(EncodedOrderIntent::SIZE, size_of::<EncodedOrderIntent>());
505    }
506
507    #[test]
508    fn every_flag_owns_a_distinct_bit() {
509        let byte = |flags: Flags| <[u8; 1]>::from(flags)[0];
510        let cleared = Flags {
511            created_on_chain: false,
512            kind: OrderKind::Sell,
513            partially_fillable: false,
514        };
515        assert_eq!(byte(cleared), 0);
516
517        let set_one_by_one = [
518            (
519                Flags::CREATED_ON_CHAIN,
520                Flags {
521                    created_on_chain: true,
522                    ..cleared
523                },
524            ),
525            (
526                Flags::KIND,
527                Flags {
528                    kind: OrderKind::Buy,
529                    ..cleared
530                },
531            ),
532            (
533                Flags::PARTIALLY_FILLABLE,
534                Flags {
535                    partially_fillable: true,
536                    ..cleared
537                },
538            ),
539        ];
540        let mut seen = 0u8;
541        for (bit, flags) in set_one_by_one {
542            assert_eq!(bit.count_ones(), 1, "a flag must occupy a single bit");
543            assert_eq!(seen & bit, 0, "two flags must not share a bit");
544            assert!(
545                bit > seen,
546                "each flag must be more significant than the ones before it"
547            );
548            seen |= bit;
549            assert_eq!(byte(flags), bit);
550        }
551        assert_eq!(seen, Flags::DEFINED);
552    }
553
554    #[test]
555    fn roundtrip_all_kind_and_flag_combinations() {
556        for intent in all_flag_shapes() {
557            let encoded = EncodedOrderIntent::from(&intent);
558            let (decoded, _uid) =
559                EncodedOrderIntent::decode_and_hash(&encoded).expect("example must decode");
560            assert_eq!(decoded, intent);
561        }
562    }
563
564    // Locks the bijection invariant called out in `decode_and_hash`: the
565    // UID computed over the raw input bytes must equal the hash of the
566    // canonical re-encoding. If anything ever normalizes during
567    // encode/decode, this test fails.
568    #[test]
569    fn decode_and_hash_uid_matches_encoded_hash() {
570        for intent in all_flag_shapes() {
571            let encoded = EncodedOrderIntent::from(&intent);
572            let (_intent, uid) =
573                EncodedOrderIntent::decode_and_hash(&encoded).expect("example must decode");
574            assert_eq!(uid, encoded.hash());
575        }
576    }
577
578    #[test]
579    fn decode_accepts_defined_flag_bits_only() {
580        let encoded = EncodedOrderIntent::from(&sample_intent(Default::default()));
581        let mut bytes: [u8; EncodedOrderIntent::SIZE] = *encoded;
582        for flags in u8::MIN..=u8::MAX {
583            bytes[FLAGS_OFFSET] = flags;
584            let decoded = EncodedOrderIntent::decode_and_hash(&bytes);
585            if flags & !Flags::DEFINED != 0 {
586                assert_eq!(
587                    decoded.err(),
588                    Some(ProgramError::InvalidInstructionData),
589                    "flags {flags:#04x} sets a reserved bit and must be rejected",
590                );
591            }
592        }
593    }
594
595    #[test]
596    fn uid_digest_regression() {
597        // Compared as hex: `Hash`'s `Display` and `Debug` are both base58,
598        // which is not how we represent order UIDs elsewhere.
599        fn hex(bytes: &[u8]) -> String {
600            bytes.iter().map(|b| format!("{b:02x}")).collect()
601        }
602        let intent = sample_intent(Flags {
603            created_on_chain: true,
604            kind: OrderKind::Buy,
605            partially_fillable: true,
606        });
607        assert_eq!(
608            hex(intent.uid().as_ref()),
609            "de4096c6c100056f1e4636ea4fafefad40fc1d0b37692fe3ca1e0db3644b86bd",
610        );
611    }
612
613    #[test]
614    fn encoding_regression() {
615        let encoded = EncodedOrderIntent::from(&sample_intent(Flags {
616            created_on_chain: true,
617            kind: OrderKind::Buy,
618            partially_fillable: true,
619        }));
620        let encoding: [u8; EncodedOrderIntent::SIZE] = *encoded;
621        #[rustfmt::skip]
622        let expected: [u8; EncodedOrderIntent::SIZE] = [
623            // owner ([0x11; 32])
624            0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
625            0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
626            0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
627            0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
628            // buy_token_account ([0x22; 32])
629            0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
630            0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
631            0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
632            0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
633            // buy_mint ([0x33; 32])
634            0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33,
635            0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33,
636            0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33,
637            0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33,
638            // sell_token_account ([0x44; 32])
639            0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
640            0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
641            0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
642            0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
643            // sell_mint ([0x55; 32])
644            0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
645            0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
646            0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
647            0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
648            // sell_amount (0x0123_4567_89ab_cdef, LE u64)
649            0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0x01,
650            // buy_amount (0xfedc_ba98_7654_3210, LE u64)
651            0x10, 0x32, 0x54, 0x76, 0x98, 0xba, 0xdc, 0xfe,
652            // valid_to (0xdead_beef, LE u32)
653            0xef, 0xbe, 0xad, 0xde,
654            // flags (created_on_chain | kind (Buy = 1) | partially_fillable)
655            0b00000111,
656            // app_data ([0x66; 32])
657            0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
658            0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
659            0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
660            0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
661        ];
662        assert_eq!(encoding, expected);
663    }
664
665    // Property-based tests, non-deterministic.
666    mod proptest {
667        use ::proptest::{prelude::*, test_runner::TestCaseError};
668
669        use super::*;
670        use crate::data::intent::fixtures::{
671            arb_flags_byte, arb_invalid_flags_byte, arb_order_intent, FLAGS_OFFSET,
672        };
673
674        proptest! {
675            // For any `OrderIntent`, encoding an intent into an encoded
676            // intent and then decoding it with `decode_and_hash()` returns
677            // the same intent plus a UID that matches the encoded bytes'
678            // hash.
679            #[test]
680            fn intent_roundtrip(intent in arb_order_intent()) {
681                let encoded = EncodedOrderIntent::from(&intent);
682                let (decoded, uid) = EncodedOrderIntent::decode_and_hash(&encoded)
683                    .map_err(|e| TestCaseError::fail(format!("decode failed: {e:?}")))?;
684                prop_assert_eq!(decoded, intent);
685                prop_assert_eq!(uid, encoded.hash());
686            }
687
688            // For any bytes whose flags slot is valid, `decode_and_hash` and
689            // then re-encoding produces back the original bytes.
690            #[test]
691            fn bytes_roundtrip(
692                mut bytes in any::<[u8; EncodedOrderIntent::SIZE]>(),
693                flags in arb_flags_byte(),
694            ) {
695                bytes[FLAGS_OFFSET] = flags;
696                let (intent, _uid) = EncodedOrderIntent::decode_and_hash(&bytes)
697                    .map_err(|e| TestCaseError::fail(format!("decode failed: {e:?}")))?;
698                prop_assert_eq!(*EncodedOrderIntent::from(&intent), bytes);
699            }
700
701            // Symmetric: any bytes whose flags byte carries a reserved bit
702            // return `InvalidInstructionData`.
703            #[test]
704            fn rejects_reserved_flag_bits(
705                mut bytes in any::<[u8; EncodedOrderIntent::SIZE]>(),
706                bad_flags in arb_invalid_flags_byte(),
707            ) {
708                bytes[FLAGS_OFFSET] = bad_flags;
709                prop_assert_eq!(
710                    EncodedOrderIntent::decode_and_hash(&bytes),
711                    Err(ProgramError::InvalidInstructionData),
712                );
713            }
714        }
715    }
716}