Skip to main content

cow_settlement_interface/instruction/
create_self_order.rs

1//! `CreateSelfOrder` instruction builder.
2
3use solana_instruction::{AccountMeta, Instruction};
4use solana_program_error::ProgramError;
5use solana_pubkey::Pubkey;
6
7pub use solana_system_interface::program::ID as SYSTEM_PROGRAM_ID;
8
9use super::InstructionInputParsing;
10use crate::{data::intent::EncodedOrderIntent, SettlementInstruction};
11
12/// Builder for a `CreateSelfOrder` instruction.
13///
14/// Allocates an [order PDA](`crate::pda::order`) for an order owned by
15/// the settlement state PDA, so the fees that accumulate in the buffer accounts
16/// can be sold through a regular settlement.
17///
18/// Similar to [`CreateOrder`](crate::instruction::create_order::CreateOrder),
19/// but the instruction is gated by the
20/// [`SelfOrderAuthority`](crate::Role::SelfOrderAuthority), and doesn't need
21/// the owner's signature. The program forces `intent.owner` to be the state
22/// PDA. The order this function created is a normal order and settles through
23/// the standard `BeginSettle`/`FinalizeSettle` flow.
24///
25/// The only enforced parameters are `created_on_chain` (should be true) and
26/// the owner (should be the state PDA).
27///
28/// `created_by` funds the new order PDA's rent and will get the rent back when
29/// executing `ReclaimOrder`.
30///
31/// Wire format: `[discriminator=10, ..intent bytes]`,
32/// `1 + EncodedOrderIntent::SIZE` bytes. Required accounts:
33/// `[authority (S), created_by (W,S), state_pda (R), order_pda (W),
34/// system_program (R)]`.
35pub struct CreateSelfOrder {
36    pub program_id: Pubkey,
37    pub authority: Pubkey,
38    pub created_by: Pubkey,
39    pub state_pda: Pubkey,
40    pub order_pda: Pubkey,
41    pub intent_bytes: [u8; EncodedOrderIntent::SIZE],
42}
43
44impl From<CreateSelfOrder> for Instruction {
45    fn from(builder: CreateSelfOrder) -> Self {
46        let mut data = Vec::with_capacity(1 + EncodedOrderIntent::SIZE);
47        data.push(SettlementInstruction::CreateSelfOrder.discriminator());
48        data.extend_from_slice(&builder.intent_bytes);
49
50        Instruction {
51            program_id: builder.program_id,
52            accounts: vec![
53                AccountMeta::new_readonly(builder.authority, true),
54                AccountMeta::new(builder.created_by, true),
55                AccountMeta::new_readonly(builder.state_pda, false),
56                AccountMeta::new(builder.order_pda, false),
57                AccountMeta::new_readonly(SYSTEM_PROGRAM_ID, false),
58            ],
59            data,
60        }
61    }
62}
63
64/// Parsed inputs of a `CreateSelfOrder` instruction.
65pub struct CreateSelfOrderInput<'a, A> {
66    pub intent_bytes: [u8; EncodedOrderIntent::SIZE],
67    pub authority: &'a A,
68    pub created_by: &'a A,
69    pub state_pda: &'a A,
70    pub order_pda: &'a A,
71}
72
73impl<'a, A> InstructionInputParsing<'a, A> for CreateSelfOrderInput<'a, A> {
74    const DISCRIMINATOR: SettlementInstruction = SettlementInstruction::CreateSelfOrder;
75
76    fn parse_body(instruction_data: &'a [u8], accounts: &'a [A]) -> Result<Self, ProgramError> {
77        // Body (discriminator already stripped): exactly the intent bytes.
78        let intent_bytes: [u8; EncodedOrderIntent::SIZE] = instruction_data
79            .try_into()
80            .map_err(|_| ProgramError::InvalidInstructionData)?;
81
82        // Accounts: [authority (S), created_by (W,S), state_pda (R), order_pda (W),
83        // system_program (R)]. We check that there are five accounts because
84        // the instruction needs to specify `SYSTEM_PROGRAM_ID` as one of them
85        // but it doesn't have to be the fifth one.
86        let [authority, created_by, state_pda, order_pda, _system_program, ..] = accounts else {
87            return Err(ProgramError::NotEnoughAccountKeys);
88        };
89
90        Ok(Self {
91            intent_bytes,
92            authority,
93            created_by,
94            state_pda,
95            order_pda,
96        })
97    }
98}
99
100/// Test scaffolding for `CreateSelfOrder` parsing and handling, shared by
101/// this crate's tests and the settlement program's via the `test-fixtures`
102/// feature.
103#[cfg(any(test, feature = "test-fixtures"))]
104pub mod fixtures {
105    use solana_address::Address;
106
107    use super::{CreateSelfOrder, EncodedOrderIntent, Instruction};
108    use crate::data::intent::OrderIntent;
109
110    /// Number of accounts `CreateSelfOrder` expects: authority, created_by, state
111    /// PDA, order PDA, and the system program.
112    pub const NUM_ACCOUNTS: usize = 5;
113
114    /// `CreateSelfOrder` instruction data carrying `intent`, with
115    /// placeholder addresses for failure cases where the addresses don't matter.
116    pub fn self_order_data(intent: &OrderIntent) -> Vec<u8> {
117        let zero = Address::new_from_array([0; 32]);
118        Instruction::from(CreateSelfOrder {
119            program_id: zero,
120            authority: zero,
121            created_by: zero,
122            state_pda: zero,
123            order_pda: zero,
124            intent_bytes: (&EncodedOrderIntent::from(intent)).into(),
125        })
126        .data
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use super::fixtures::{self_order_data, NUM_ACCOUNTS};
133    use super::*;
134    use crate::data::intent::fixtures::sample_intent;
135    use crate::data::intent::OrderIntent;
136    use crate::fixtures::pubkey_from_seed;
137    use crate::instruction::fixtures::{fake_account, fake_sequential_accounts};
138    use crate::instruction::tests::{
139        assert_readonly_nonsigner, assert_readonly_signer, assert_writable_nonsigner,
140        assert_writable_signer,
141    };
142    use solana_account_view::AccountView;
143
144    /// A well-formed sample self order intent for these tests. The owner
145    /// and flags aren't checked at this layer (only the handler does), so any
146    /// well-formed intent works.
147    fn intent() -> OrderIntent {
148        sample_intent(Default::default())
149    }
150
151    #[test]
152    fn create_self_order_input_parses_valid_input() {
153        let program_id = pubkey_from_seed("program id");
154        let authority = pubkey_from_seed("authority");
155        let created_by = pubkey_from_seed("created_by");
156        let state_pda = pubkey_from_seed("state pda");
157        let order_pda = pubkey_from_seed("order pda");
158        let intent_bytes: [u8; EncodedOrderIntent::SIZE] =
159            (&EncodedOrderIntent::from(&intent())).into();
160
161        let data = Instruction::from(CreateSelfOrder {
162            program_id,
163            authority,
164            created_by,
165            state_pda,
166            order_pda,
167            intent_bytes,
168        })
169        .data;
170        let accounts = [
171            fake_account(authority),
172            fake_account(created_by),
173            fake_account(state_pda),
174            fake_account(order_pda),
175            fake_account(pubkey_from_seed("system program")),
176        ];
177
178        let CreateSelfOrderInput {
179            intent_bytes: derived_intent_bytes,
180            authority: derived_authority,
181            created_by: derived_created_by,
182            state_pda: derived_state_pda,
183            order_pda: derived_order_pda,
184        } = CreateSelfOrderInput::parse(&data, &accounts).expect("parse should succeed");
185
186        assert_eq!(derived_intent_bytes, intent_bytes);
187        assert_eq!(*derived_authority.address(), authority);
188        assert_eq!(*derived_created_by.address(), created_by);
189        assert_eq!(*derived_state_pda.address(), state_pda);
190        assert_eq!(*derived_order_pda.address(), order_pda);
191    }
192
193    #[test]
194    fn create_self_order_input_rejects_short_data() {
195        let mut data = self_order_data(&intent());
196        data.pop();
197        let accounts = fake_sequential_accounts::<NUM_ACCOUNTS>();
198        assert_eq!(
199            CreateSelfOrderInput::parse(&data, &accounts).err(),
200            Some(ProgramError::InvalidInstructionData),
201        );
202    }
203
204    #[test]
205    fn create_self_order_input_rejects_long_data() {
206        let mut data = self_order_data(&intent());
207        data.push(0); // trailing byte
208        let accounts = fake_sequential_accounts::<NUM_ACCOUNTS>();
209        assert_eq!(
210            CreateSelfOrderInput::parse(&data, &accounts).err(),
211            Some(ProgramError::InvalidInstructionData),
212        );
213    }
214
215    #[test]
216    fn create_self_order_input_rejects_missing_accounts() {
217        let data = self_order_data(&intent());
218        let mut accounts: Vec<AccountView> = fake_sequential_accounts::<NUM_ACCOUNTS>().into();
219        accounts.pop();
220        assert_eq!(
221            CreateSelfOrderInput::parse(&data, &accounts).err(),
222            Some(ProgramError::NotEnoughAccountKeys),
223        );
224    }
225
226    #[test]
227    fn instruction_data_has_expected_layout() {
228        let intent_bytes = [0x42u8; EncodedOrderIntent::SIZE];
229
230        let Instruction { data, .. } = CreateSelfOrder {
231            program_id: pubkey_from_seed("program id"),
232            authority: pubkey_from_seed("authority"),
233            created_by: pubkey_from_seed("created_by"),
234            state_pda: pubkey_from_seed("state pda"),
235            order_pda: pubkey_from_seed("order pda"),
236            intent_bytes,
237        }
238        .into();
239
240        assert_eq!(data.len(), 1 + EncodedOrderIntent::SIZE);
241        assert_eq!(
242            data[0],
243            SettlementInstruction::CreateSelfOrder.discriminator()
244        );
245        assert_eq!(&data[1..], &intent_bytes);
246    }
247
248    #[test]
249    fn instruction_data_has_expected_accounts() {
250        let program_id = pubkey_from_seed("program id");
251        let authority = pubkey_from_seed("authority");
252        let created_by = pubkey_from_seed("created_by");
253        let state_pda = pubkey_from_seed("state pda");
254        let order_pda = pubkey_from_seed("order pda");
255        let intent_bytes = [0u8; EncodedOrderIntent::SIZE];
256
257        let Instruction { accounts, .. } = CreateSelfOrder {
258            program_id,
259            authority,
260            created_by,
261            state_pda,
262            order_pda,
263            intent_bytes,
264        }
265        .into();
266
267        assert_eq!(accounts.len(), 5);
268        // authority gates the order without paying rent; created_by funds the new
269        // PDA's rent; state_pda is only read; order_pda is created. The system
270        // program is only referenced.
271        assert_readonly_signer(&accounts[0], authority);
272        assert_writable_signer(&accounts[1], created_by);
273        assert_readonly_nonsigner(&accounts[2], state_pda);
274        assert_writable_nonsigner(&accounts[3], order_pda);
275        assert_readonly_nonsigner(&accounts[4], SYSTEM_PROGRAM_ID);
276    }
277}