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