1use 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
17pub 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
66pub 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 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#[cfg(any(test, feature = "test-fixtures"))]
105pub mod fixtures {
106 use solana_address::Address;
107
108 use super::{Initialize, Instruction};
109
110 pub const NUM_ACCOUNTS: usize = 3;
112
113 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::data::state::EncodedStateAccount;
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 crate::tests::pubkey_from_seed;
138 use solana_account_view::AccountView;
139 use solana_address::Address;
140
141 #[test]
142 fn initialize_input_parses_valid_input() {
143 let program_id = Address::new_unique();
144 let payer = fake_account(pubkey_from_seed("payer"));
145 let state_pda = fake_account(pubkey_from_seed("state pda"));
146 let reclaim_authority = pubkey_from_seed("reclaim authority");
147 let manager = pubkey_from_seed("manager");
148 let data = Instruction::from(Initialize {
149 program_id,
150 payer: *payer.address(),
151 state_pda: *state_pda.address(),
152 manager,
153 reclaim_authority,
154 })
155 .data;
156
157 let system_program = fake_account(pubkey_from_seed("system program"));
158 let accounts = [payer, state_pda, system_program];
159
160 let InitializeInput {
161 payer: parsed_payer,
162 state_pda: parsed_state_pda,
163 manager: parsed_manager,
164 reclaim_authority: parsed_reclaim_authority,
165 } = InitializeInput::parse(&data, &accounts).expect("parse should succeed");
166
167 assert_eq!(parsed_payer.address(), payer.address());
168 assert_eq!(parsed_state_pda.address(), state_pda.address());
169 assert_eq!(parsed_manager, manager);
170 assert_eq!(parsed_reclaim_authority, reclaim_authority);
171 }
172
173 #[test]
174 fn initialize_input_rejects_long_data() {
175 let mut data = initialize_data();
176 data.push(0); let accounts = fake_sequential_accounts::<NUM_ACCOUNTS>();
178 assert_eq!(
179 InitializeInput::parse(&data, &accounts).err(),
180 Some(ProgramError::InvalidInstructionData),
181 );
182 }
183
184 #[test]
185 fn initialize_input_rejects_short_data() {
186 let mut data = initialize_data();
187 data.pop(); let accounts = fake_sequential_accounts::<NUM_ACCOUNTS>();
189 assert_eq!(
190 InitializeInput::parse(&data, &accounts).err(),
191 Some(ProgramError::InvalidInstructionData),
192 );
193 }
194
195 #[test]
196 fn initialize_input_rejects_missing_accounts() {
197 let data = initialize_data();
198 let mut accounts: Vec<AccountView> = fake_sequential_accounts::<NUM_ACCOUNTS>().into();
199 accounts.pop();
200 assert_eq!(
201 InitializeInput::parse(&data, &accounts).err(),
202 Some(ProgramError::NotEnoughAccountKeys),
203 );
204 }
205
206 #[test]
207 fn instruction_data_has_expected_layout() {
208 let program_id = pubkey_from_seed("program id");
209 let payer = pubkey_from_seed("payer");
210 let state_pda = pubkey_from_seed("state pda");
211 let reclaim_authority = pubkey_from_seed("reclaim authority");
212 let manager = pubkey_from_seed("manager");
213
214 let Instruction { data, .. } = Initialize {
215 program_id,
216 payer,
217 state_pda,
218 reclaim_authority,
219 manager,
220 }
221 .into();
222 assert_eq!(data.len(), EncodedStateAccount::SIZE);
223 assert_eq!(data[0], SettlementInstruction::Initialize.discriminator());
224 assert_eq!(&data[1..33], &manager.to_bytes());
225 assert_eq!(&data[33..], &reclaim_authority.to_bytes());
226 }
227
228 #[test]
229 fn instruction_data_regression() {
230 let manager = Pubkey::new_from_array([0x11; 32]);
231 let reclaim_authority = Pubkey::new_from_array([0x22; 32]);
232
233 let Instruction { data, .. } = Initialize {
234 program_id: pubkey_from_seed("program id"),
235 payer: pubkey_from_seed("payer"),
236 state_pda: pubkey_from_seed("state pda"),
237 manager,
238 reclaim_authority,
239 }
240 .into();
241
242 #[rustfmt::skip]
243 let expected: [u8; EncodedStateAccount::SIZE] = [
244 0x03,
246 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 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
251 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 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
256 ];
257 assert_eq!(data, expected);
258 }
259
260 #[test]
261 fn instruction_has_expected_accounts() {
262 let program_id = pubkey_from_seed("program id");
263 let payer = pubkey_from_seed("payer");
264 let state_pda = pubkey_from_seed("state pda");
265 let manager = pubkey_from_seed("manager");
266 let reclaim_authority = pubkey_from_seed("reclaim authority");
267
268 let Instruction { accounts, .. } = Initialize {
269 program_id,
270 payer,
271 state_pda,
272 reclaim_authority,
273 manager,
274 }
275 .into();
276
277 assert_eq!(accounts.len(), 3);
278 assert_writable_signer(&accounts[0], payer);
281 assert_writable_nonsigner(&accounts[1], state_pda);
282 assert_readonly_nonsigner(&accounts[2], SYSTEM_PROGRAM_ID);
283 }
284}