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