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