Skip to main content

cow_settlement_interface/instruction/
initialize.rs

1//! `Initialize` instruction builder.
2//!
3//! Allocates the singleton settlement state PDA (see [`crate::pda::state`])
4//! and the native SOL buffer (see [`crate::pda::buffer::NATIVE_SOL_BUFFER_PDA`]).
5
6use core::mem::size_of;
7
8use arrayref::array_refs;
9use solana_instruction::{AccountMeta, Instruction};
10use solana_program_error::ProgramError;
11use solana_pubkey::Pubkey;
12
13pub use solana_system_interface::program::ID as SYSTEM_PROGRAM_ID;
14
15use super::InstructionInputParsing;
16use crate::SettlementInstruction;
17
18/// Builder for an `Initialize` instruction.
19///
20/// `payer` funds the new accounts' rent and signs. It is meant to be the
21/// transaction's fee payer: the state is created once at deployment and never
22/// deallocated, so there's no need for a dedicated funding account separate
23/// from whoever pays for the deployment transaction.
24///
25/// `state_pda` must be [`crate::pda::state::STATE_PDA`] and
26/// `native_sol_buffer` must be [`crate::pda::buffer::NATIVE_SOL_BUFFER_PDA`];
27/// the program rejects any other address.
28///
29/// All data input accounts are recorded verbatim in the state PDA's data: the
30/// account authorized to transfer any role, the account authorized to add and
31/// remove solvers, the account authorized to reclaim rent for buffers, and the
32/// account authorized to place settlement-owned orders. See
33/// [`crate::data::state::StateAccount`].
34///
35/// Both accounts are owned by the settlement program; the native SOL buffer
36/// holds no data. This instruction succeeds only once: a second call fails
37/// because the accounts already exist.
38///
39/// Wire format: `[discriminator=3, manager (32 bytes), solver_authority (32
40/// bytes), reclaim_authority (32 bytes), settlement_owned_order_authority (32
41/// bytes)]`, 129 bytes. Required accounts: `[payer (W,S), state_pda (W),
42/// native_sol_buffer (W), system_program (R)]`. The system program must be available for the
43/// `CreateAccount` CPI but doesn't need to sit at that specific position.
44pub struct Initialize {
45    pub program_id: Pubkey,
46    pub payer: Pubkey,
47    pub state_pda: Pubkey,
48    pub native_sol_buffer: Pubkey,
49    pub manager: Pubkey,
50    pub solver_authority: Pubkey,
51    pub reclaim_authority: Pubkey,
52    pub settlement_owned_order_authority: Pubkey,
53}
54
55impl From<Initialize> for Instruction {
56    fn from(builder: Initialize) -> Self {
57        let mut data = vec![SettlementInstruction::Initialize.discriminator()];
58        data.extend_from_slice(&builder.manager.to_bytes());
59        data.extend_from_slice(&builder.solver_authority.to_bytes());
60        data.extend_from_slice(&builder.reclaim_authority.to_bytes());
61        data.extend_from_slice(&builder.settlement_owned_order_authority.to_bytes());
62        Instruction {
63            program_id: builder.program_id,
64            accounts: vec![
65                AccountMeta::new(builder.payer, true),
66                AccountMeta::new(builder.state_pda, false),
67                AccountMeta::new(builder.native_sol_buffer, false),
68                AccountMeta::new_readonly(SYSTEM_PROGRAM_ID, false),
69            ],
70            data,
71        }
72    }
73}
74
75/// Parsed inputs of an `Initialize` instruction.
76pub struct InitializeInput<'a, A> {
77    pub payer: &'a A,
78    pub state_pda: &'a A,
79    pub native_sol_buffer: &'a A,
80    pub manager: Pubkey,
81    pub solver_authority: Pubkey,
82    pub reclaim_authority: Pubkey,
83    pub settlement_owned_order_authority: Pubkey,
84}
85
86impl<'a, A> InstructionInputParsing<'a, A> for InitializeInput<'a, A> {
87    const DISCRIMINATOR: SettlementInstruction = SettlementInstruction::Initialize;
88
89    fn parse_body(instruction_data: &[u8], accounts: &'a [A]) -> Result<Self, ProgramError> {
90        let authorities: &[u8; 4 * size_of::<Pubkey>()] = instruction_data
91            .try_into()
92            .map_err(|_| ProgramError::InvalidInstructionData)?;
93        let (manager, solver_authority, reclaim_authority, settlement_owned_order_authority) = array_refs![
94            authorities,
95            size_of::<Pubkey>(),
96            size_of::<Pubkey>(),
97            size_of::<Pubkey>(),
98            size_of::<Pubkey>()
99        ];
100        let manager = Pubkey::new_from_array(*manager);
101        let solver_authority = Pubkey::new_from_array(*solver_authority);
102        let reclaim_authority = Pubkey::new_from_array(*reclaim_authority);
103        let settlement_owned_order_authority =
104            Pubkey::new_from_array(*settlement_owned_order_authority);
105
106        // Accounts: [payer (W,S), state_pda (W), native_sol_buffer (W),
107        // system_program (R)]. The system program needs to be present for the
108        // `CreateAccount` CPI but doesn't need to be referenced directly and can
109        // be at any later position.
110        let [payer, state_pda, native_sol_buffer, _system, ..] = accounts else {
111            return Err(ProgramError::NotEnoughAccountKeys);
112        };
113
114        Ok(Self {
115            payer,
116            state_pda,
117            native_sol_buffer,
118            manager,
119            solver_authority,
120            reclaim_authority,
121            settlement_owned_order_authority,
122        })
123    }
124}
125
126/// Test scaffolding for `Initialize` parsing and handling, shared by this
127/// crate's tests and the settlement program's via the `test-fixtures` feature.
128#[cfg(any(test, feature = "test-fixtures"))]
129pub mod fixtures {
130    use solana_address::Address;
131
132    use super::{Initialize, Instruction};
133
134    /// Number of accounts `Initialize` expects: payer, state PDA, native SOL
135    /// buffer, system program.
136    pub const NUM_ACCOUNTS: usize = 4;
137
138    /// `Initialize` instruction data with placeholder addresses, for failure
139    /// cases where the actual addresses don't matter.
140    pub fn initialize_data() -> Vec<u8> {
141        let zero = Address::new_from_array([0; 32]);
142        Instruction::from(Initialize {
143            program_id: zero,
144            payer: zero,
145            state_pda: zero,
146            native_sol_buffer: zero,
147            manager: zero,
148            solver_authority: zero,
149            reclaim_authority: zero,
150            settlement_owned_order_authority: zero,
151        })
152        .data
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use super::fixtures::{initialize_data, NUM_ACCOUNTS};
159    use super::*;
160    use crate::fixtures::pubkey_from_seed;
161    use crate::instruction::fixtures::{fake_account, fake_sequential_accounts};
162    use crate::instruction::tests::{
163        assert_readonly_nonsigner, assert_writable_nonsigner, assert_writable_signer,
164    };
165    use solana_account_view::AccountView;
166    use solana_address::Address;
167
168    #[test]
169    fn initialize_input_parses_valid_input() {
170        let program_id = Address::new_unique();
171        let payer = fake_account(pubkey_from_seed("payer"));
172        let state_pda = fake_account(pubkey_from_seed("state pda"));
173        let native_sol_buffer = fake_account(pubkey_from_seed("native sol buffer"));
174        let manager = pubkey_from_seed("manager");
175        let solver_authority = pubkey_from_seed("solver authority");
176        let reclaim_authority = pubkey_from_seed("reclaim authority");
177        let settlement_owned_order_authority = pubkey_from_seed("settlement-owned-order authority");
178        let data = Instruction::from(Initialize {
179            program_id,
180            payer: *payer.address(),
181            state_pda: *state_pda.address(),
182            native_sol_buffer: *native_sol_buffer.address(),
183            manager,
184            solver_authority,
185            reclaim_authority,
186            settlement_owned_order_authority,
187        })
188        .data;
189
190        let system_program = fake_account(pubkey_from_seed("system program"));
191        let accounts = [payer, state_pda, native_sol_buffer, system_program];
192
193        let InitializeInput {
194            payer: parsed_payer,
195            state_pda: parsed_state_pda,
196            native_sol_buffer: parsed_native_sol_buffer,
197            manager: parsed_manager,
198            solver_authority: parsed_solver_authority,
199            reclaim_authority: parsed_reclaim_authority,
200            settlement_owned_order_authority: parsed_settlement_owned_order_authority,
201        } = InitializeInput::parse(&data, &accounts).expect("parse should succeed");
202
203        assert_eq!(parsed_payer.address(), payer.address());
204        assert_eq!(parsed_state_pda.address(), state_pda.address());
205        assert_eq!(
206            parsed_native_sol_buffer.address(),
207            native_sol_buffer.address()
208        );
209        assert_eq!(parsed_manager, manager);
210        assert_eq!(parsed_solver_authority, solver_authority);
211        assert_eq!(parsed_reclaim_authority, reclaim_authority);
212        assert_eq!(
213            parsed_settlement_owned_order_authority,
214            settlement_owned_order_authority
215        );
216    }
217
218    #[test]
219    fn initialize_input_rejects_long_data() {
220        let mut data = initialize_data();
221        data.push(0); // trailing byte
222        let accounts = fake_sequential_accounts::<NUM_ACCOUNTS>();
223        assert_eq!(
224            InitializeInput::parse(&data, &accounts).err(),
225            Some(ProgramError::InvalidInstructionData),
226        );
227    }
228
229    #[test]
230    fn initialize_input_rejects_short_data() {
231        let mut data = initialize_data();
232        data.pop(); // one byte short
233        let accounts = fake_sequential_accounts::<NUM_ACCOUNTS>();
234        assert_eq!(
235            InitializeInput::parse(&data, &accounts).err(),
236            Some(ProgramError::InvalidInstructionData),
237        );
238    }
239
240    #[test]
241    fn initialize_input_rejects_missing_accounts() {
242        let data = initialize_data();
243        let mut accounts: Vec<AccountView> = fake_sequential_accounts::<NUM_ACCOUNTS>().into();
244        accounts.pop();
245        assert_eq!(
246            InitializeInput::parse(&data, &accounts).err(),
247            Some(ProgramError::NotEnoughAccountKeys),
248        );
249    }
250
251    #[test]
252    fn instruction_data_has_expected_layout() {
253        let program_id = pubkey_from_seed("program id");
254        let payer = pubkey_from_seed("payer");
255        let state_pda = pubkey_from_seed("state pda");
256        let native_sol_buffer = pubkey_from_seed("native sol buffer");
257        let manager = pubkey_from_seed("manager");
258        let solver_authority = pubkey_from_seed("solver authority");
259        let reclaim_authority = pubkey_from_seed("reclaim authority");
260        let settlement_owned_order_authority = pubkey_from_seed("settlement-owned-order authority");
261
262        let Instruction { data, .. } = Initialize {
263            program_id,
264            payer,
265            state_pda,
266            native_sol_buffer,
267            manager,
268            solver_authority,
269            reclaim_authority,
270            settlement_owned_order_authority,
271        }
272        .into();
273        assert_eq!(data.len(), 1 + 4 * core::mem::size_of::<Pubkey>());
274        assert_eq!(data[0], SettlementInstruction::Initialize.discriminator());
275        assert_eq!(&data[1..33], &manager.to_bytes());
276        assert_eq!(&data[33..65], &solver_authority.to_bytes());
277        assert_eq!(&data[65..97], &reclaim_authority.to_bytes());
278        assert_eq!(&data[97..], &settlement_owned_order_authority.to_bytes());
279    }
280
281    #[test]
282    fn instruction_data_regression() {
283        let manager = Pubkey::new_from_array([0x11; 32]);
284        let solver_authority = Pubkey::new_from_array([0x22; 32]);
285        let reclaim_authority = Pubkey::new_from_array([0x33; 32]);
286        let settlement_owned_order_authority = Pubkey::new_from_array([0x44; 32]);
287
288        let Instruction { data, .. } = Initialize {
289            program_id: pubkey_from_seed("program id"),
290            payer: pubkey_from_seed("payer"),
291            state_pda: pubkey_from_seed("state pda"),
292            native_sol_buffer: pubkey_from_seed("native sol buffer"),
293            manager,
294            solver_authority,
295            reclaim_authority,
296            settlement_owned_order_authority,
297        }
298        .into();
299
300        #[rustfmt::skip]
301        let expected: [u8; 1 + 4 * core::mem::size_of::<Pubkey>()] = [
302            // discriminator (Initialize = 3)
303            0x03,
304            // manager
305            0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
306            0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
307            0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
308            0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
309            // solver_authority
310            0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
311            0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
312            0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
313            0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
314            // reclaim_authority
315            0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33,
316            0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33,
317            0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33,
318            0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33,
319            // settlement_owned_order_authority
320            0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
321            0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
322            0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
323            0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
324        ];
325        assert_eq!(data, expected);
326    }
327
328    #[test]
329    fn instruction_has_expected_accounts() {
330        let program_id = pubkey_from_seed("program id");
331        let payer = pubkey_from_seed("payer");
332        let state_pda = pubkey_from_seed("state pda");
333        let native_sol_buffer = pubkey_from_seed("native sol buffer");
334        let manager = pubkey_from_seed("manager");
335        let solver_authority = pubkey_from_seed("solver authority");
336        let reclaim_authority = pubkey_from_seed("reclaim authority");
337        let settlement_owned_order_authority = pubkey_from_seed("settlement-owned-order authority");
338
339        let Instruction { accounts, .. } = Initialize {
340            program_id,
341            payer,
342            state_pda,
343            native_sol_buffer,
344            manager,
345            solver_authority,
346            reclaim_authority,
347            settlement_owned_order_authority,
348        }
349        .into();
350
351        assert_eq!(accounts.len(), 4);
352        // payer funds the new accounts' rent; state_pda and native_sol_buffer
353        // are signed for by the program via PDA seeds; the system program is
354        // only referenced.
355        assert_writable_signer(&accounts[0], payer);
356        assert_writable_nonsigner(&accounts[1], state_pda);
357        assert_writable_nonsigner(&accounts[2], native_sol_buffer);
358        assert_readonly_nonsigner(&accounts[3], SYSTEM_PROGRAM_ID);
359    }
360}