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