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::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); 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(); 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 0x03,
245 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 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 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}