Skip to main content

cow_settlement_interface/instruction/settle/
mod.rs

1//! `BeginSettle`/`FinalizeSettle` instruction tools, the instructions-sysvar
2//! account ID they all reference, and the off-chain instruction builders.
3
4use solana_account_view::AccountView;
5use solana_address::Address;
6use solana_program_error::ProgramError;
7
8pub use crate::token_program::TokenProgram;
9pub use solana_sdk_ids::sysvar::instructions::ID as INSTRUCTIONS_SYSVAR_ID;
10
11/// Only some tokens which necessitate the use of the `TransferChecked` instruction
12/// require the RO mint account to be specified. To reduce unnecessary account
13/// dependency, the instructions sysvar may be provided instead of the mint to
14/// call `Transfer` instead.
15pub const MINT_PLACEHOLDER: solana_pubkey::Pubkey = INSTRUCTIONS_SYSVAR_ID;
16
17/// The on-chain address of an account representation, that is, the generic `A`
18/// used in our parser. This can be used by implementations to use custom
19/// address types in the parser, as long as they implement `Keyed`.
20pub trait Keyed {
21    fn key(&self) -> &Address;
22}
23
24impl Keyed for AccountView {
25    fn key(&self) -> &Address {
26        self.address()
27    }
28}
29
30impl Keyed for Address {
31    fn key(&self) -> &Address {
32        self
33    }
34}
35
36/// A settle instruction's mint slot: a value whose address is either a real
37/// mint, naming a `TransferChecked`, or the [`MINT_PLACEHOLDER`] sentinel,
38/// naming a plain `Transfer`. The two are indistinguishable as raw addresses,
39/// so this wrapper forces callers through [`MaybeMint::get`] to resolve which,
40/// rather than handling a bare slot that is secretly one or the other.
41#[derive(Clone, Copy, Debug, PartialEq, Eq)]
42pub struct MaybeMint<'a, A>(&'a A);
43
44impl<'a, A> MaybeMint<'a, A> {
45    /// Wrap a mint-slot value, deferring the placeholder check to
46    /// [`MaybeMint::get`].
47    pub fn new(slot: &'a A) -> Self {
48        Self(slot)
49    }
50}
51
52impl<'a, A: Keyed> MaybeMint<'a, A> {
53    /// The mint to settle against, or `None` when the slot holds the
54    /// [`MINT_PLACEHOLDER`] sentinel selecting a plain `Transfer`.
55    pub fn get(&self) -> Option<&'a A> {
56        if self.0.key() == &MINT_PLACEHOLDER {
57            None
58        } else {
59            Some(self.0)
60        }
61    }
62}
63
64mod begin;
65mod finalize;
66
67pub use begin::{BeginSettle, BeginSettleInput, Pull, SettledOrder, SettledOrders};
68pub use finalize::{
69    finalize_push_data, FinalizeSettle, FinalizeSettleInput, Push, Pushes, FINALIZE_FIXED_ACCOUNTS,
70    FINALIZE_PUSH_ACCOUNTS,
71};
72
73/// Reads the first two bytes of a byte slice (instruction data) and
74/// interprets them as a little-endian u16, returning it together with the
75/// remaining bytes to parse.
76/// It's meant to be used for BeginSettle and FinalizeSettle to extract the
77/// counterpart index, that is, the index linking that instruction to the
78/// opposite instruction which is encoded as the first
79/// 2 bytes of the instruction data: `[0x37, 0x13]` → `0x1337`.
80/// Returns `InvalidInstructionData` if fewer than two bytes are provided.
81pub fn recover_counterpart(instruction_data: &[u8]) -> Result<(u16, &[u8]), ProgramError> {
82    match instruction_data {
83        [b1, b2, rest @ ..] => Ok((u16::from_le_bytes([*b1, *b2]), rest)),
84        _ => Err(ProgramError::InvalidInstructionData),
85    }
86}
87
88#[cfg(any(test, feature = "test-fixtures"))]
89pub mod fixtures {
90    use proptest::prelude::*;
91    use solana_pubkey::Pubkey;
92
93    /// Random pushes as the parallel lists the
94    /// [`FinalizeSettle`](super::FinalizeSettle) builder takes.
95    #[derive(Debug)]
96    pub struct ArbPushes {
97        pub source_buffers: Vec<Pubkey>,
98        pub destinations: Vec<Pubkey>,
99        pub mints: Vec<Option<Pubkey>>,
100        pub bumps: Vec<u8>,
101        pub amounts: Vec<u64>,
102    }
103
104    /// Strategy producing `count` random [`ArbPushes`].
105    pub fn arb_pushes(
106        count: impl Into<prop::collection::SizeRange>,
107    ) -> impl Strategy<Value = ArbPushes> {
108        prop::collection::vec(
109            (
110                any::<[u8; 32]>().prop_map(Pubkey::new_from_array),
111                any::<[u8; 32]>().prop_map(Pubkey::new_from_array),
112                any::<Option<[u8; 32]>>().prop_map(|mint| mint.map(Pubkey::new_from_array)),
113                any::<u8>(),
114                any::<u64>(),
115            ),
116            count,
117        )
118        .prop_map(|pushes| ArbPushes {
119            source_buffers: pushes.iter().map(|push| push.0).collect(),
120            destinations: pushes.iter().map(|push| push.1).collect(),
121            mints: pushes.iter().map(|push| push.2).collect(),
122            bumps: pushes.iter().map(|push| push.3).collect(),
123            amounts: pushes.iter().map(|push| push.4).collect(),
124        })
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131    use crate::fixtures::pubkey_from_seed;
132    use crate::instruction::fixtures::fake_account;
133    use hex_literal::hex;
134    use solana_address::Address;
135
136    /// Builds an instruction-data byte vector from a list of field chunks, so a
137    /// test can spell out the wire layout one field per line without repeating
138    /// the `&[..][..]` slicing. Each chunk is anything sliceable to `[u8]` (a
139    /// byte array, a `Vec<u8>`, the result of `to_le_bytes()`, ...).
140    macro_rules! ix_data {
141        ($($chunk:expr),* $(,)?) => {
142            [$(&$chunk[..]),*].concat()
143        };
144    }
145    pub(crate) use ix_data;
146
147    /// The address behind a mint slot, asserting it names a real mint rather
148    /// than the [`MINT_PLACEHOLDER`] placeholder. For parser tests reading a
149    /// settled order's or push's mint.
150    pub(crate) fn mint_address(mint: MaybeMint<'_, AccountView>) -> &Address {
151        mint.get().expect("a real mint").address()
152    }
153
154    #[test]
155    fn rejects_empty_payload() {
156        assert_eq!(
157            recover_counterpart(&[]),
158            Err(ProgramError::InvalidInstructionData),
159        );
160    }
161
162    #[test]
163    fn rejects_too_short_payload() {
164        assert_eq!(
165            recover_counterpart(&[42]),
166            Err(ProgramError::InvalidInstructionData),
167        );
168    }
169
170    #[test]
171    fn returns_trailing_bytes() {
172        assert_eq!(
173            recover_counterpart(
174                &[
175                    &hex!("3713")[..], // counterpart index, little-endian
176                    &[42][..],         // trailing
177                ]
178                .concat()
179            ),
180            Ok((0x1337, [42].as_slice())),
181        );
182    }
183
184    #[test]
185    fn maybe_mint_get_resolves_an_account_slot() {
186        let mint = pubkey_from_seed("a real mint");
187        let account = fake_account(mint);
188        assert_eq!(mint_address(MaybeMint::new(&account)), &mint);
189
190        let placeholder = fake_account(MINT_PLACEHOLDER);
191        assert!(MaybeMint::new(&placeholder).get().is_none());
192    }
193
194    #[test]
195    fn maybe_mint_get_resolves_an_address_slot() {
196        let mint = pubkey_from_seed("a real mint");
197        assert_eq!(MaybeMint::new(&mint).get(), Some(&mint));
198
199        assert_eq!(MaybeMint::new(&MINT_PLACEHOLDER).get(), None);
200    }
201}