Skip to main content

cow_settlement_interface/instruction/
create_settlement_owned_order.rs

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