1use {
5 crate::{error::TransferHookError, get_extra_account_metas_address, instruction},
6 solana_account_info::AccountInfo,
7 solana_cpi::invoke,
8 solana_instruction::{AccountMeta, Instruction},
9 solana_program_error::ProgramResult,
10 solana_pubkey::Pubkey,
11 spl_tlv_account_resolution::state::ExtraAccountMetaList,
12};
13pub fn invoke_execute<'a>(
16 program_id: &Pubkey,
17 source_info: AccountInfo<'a>,
18 mint_info: AccountInfo<'a>,
19 destination_info: AccountInfo<'a>,
20 authority_info: AccountInfo<'a>,
21 additional_accounts: &[AccountInfo<'a>],
22 amount: u64,
23) -> ProgramResult {
24 let mut cpi_instruction = instruction::execute(
25 program_id,
26 source_info.key,
27 mint_info.key,
28 destination_info.key,
29 authority_info.key,
30 amount,
31 );
32
33 let validation_pubkey = get_extra_account_metas_address(mint_info.key, program_id);
34
35 let mut cpi_account_infos = vec![source_info, mint_info, destination_info, authority_info];
36
37 if let Some(validation_info) = additional_accounts
38 .iter()
39 .find(|&x| *x.key == validation_pubkey)
40 {
41 cpi_instruction
42 .accounts
43 .push(AccountMeta::new_readonly(validation_pubkey, false));
44 cpi_account_infos.push(validation_info.clone());
45
46 ExtraAccountMetaList::add_to_cpi_instruction::<instruction::ExecuteInstruction>(
47 &mut cpi_instruction,
48 &mut cpi_account_infos,
49 &validation_info.try_borrow_data()?,
50 additional_accounts,
51 )?;
52 }
53
54 invoke(&cpi_instruction, &cpi_account_infos)
55}
56
57#[allow(clippy::too_many_arguments)]
67pub fn add_extra_accounts_for_execute_cpi<'a>(
68 cpi_instruction: &mut Instruction,
69 cpi_account_infos: &mut Vec<AccountInfo<'a>>,
70 program_id: &Pubkey,
71 source_info: AccountInfo<'a>,
72 mint_info: AccountInfo<'a>,
73 destination_info: AccountInfo<'a>,
74 authority_info: AccountInfo<'a>,
75 amount: u64,
76 additional_accounts: &[AccountInfo<'a>],
77) -> ProgramResult {
78 let validate_state_pubkey = get_extra_account_metas_address(mint_info.key, program_id);
79
80 let program_info = additional_accounts
81 .iter()
82 .find(|&x| x.key == program_id)
83 .ok_or(TransferHookError::IncorrectAccount)?;
84
85 if let Some(validate_state_info) = additional_accounts
86 .iter()
87 .find(|&x| *x.key == validate_state_pubkey)
88 {
89 let mut execute_instruction = instruction::execute(
90 program_id,
91 source_info.key,
92 mint_info.key,
93 destination_info.key,
94 authority_info.key,
95 amount,
96 );
97 execute_instruction
98 .accounts
99 .push(AccountMeta::new_readonly(validate_state_pubkey, false));
100 let mut execute_account_infos = vec![
101 source_info,
102 mint_info,
103 destination_info,
104 authority_info,
105 validate_state_info.clone(),
106 ];
107
108 ExtraAccountMetaList::add_to_cpi_instruction::<instruction::ExecuteInstruction>(
109 &mut execute_instruction,
110 &mut execute_account_infos,
111 &validate_state_info.try_borrow_data()?,
112 additional_accounts,
113 )?;
114
115 cpi_instruction
117 .accounts
118 .extend_from_slice(&execute_instruction.accounts[5..]);
119 cpi_account_infos.extend_from_slice(&execute_account_infos[5..]);
120
121 cpi_instruction
123 .accounts
124 .push(AccountMeta::new_readonly(validate_state_pubkey, false));
125 cpi_account_infos.push(validate_state_info.clone());
126 }
127
128 cpi_instruction
130 .accounts
131 .push(AccountMeta::new_readonly(*program_id, false));
132 cpi_account_infos.push(program_info.clone());
133
134 Ok(())
135}
136
137#[cfg(test)]
138mod tests {
139 use {
140 super::*,
141 crate::instruction::ExecuteInstruction,
142 solana_sdk_ids::{bpf_loader_upgradeable, system_program},
143 spl_tlv_account_resolution::{
144 account::ExtraAccountMeta, error::AccountResolutionError, seeds::Seed,
145 },
146 };
147
148 const EXTRA_META_1: Pubkey = Pubkey::new_from_array([2u8; 32]);
149 const EXTRA_META_2: Pubkey = Pubkey::new_from_array([3u8; 32]);
150
151 fn setup_validation_data() -> Vec<u8> {
152 let extra_metas = vec![
153 ExtraAccountMeta::new_with_pubkey(&EXTRA_META_1, true, false).unwrap(),
154 ExtraAccountMeta::new_with_pubkey(&EXTRA_META_2, true, false).unwrap(),
155 ExtraAccountMeta::new_with_seeds(
156 &[
157 Seed::AccountKey { index: 0 }, Seed::AccountKey { index: 2 }, Seed::AccountKey { index: 4 }, ],
161 false,
162 true,
163 )
164 .unwrap(),
165 ExtraAccountMeta::new_with_seeds(
166 &[
167 Seed::InstructionData {
168 index: 8,
169 length: 8,
170 }, Seed::AccountKey { index: 2 }, Seed::AccountKey { index: 5 }, Seed::AccountKey { index: 7 }, ],
175 false,
176 true,
177 )
178 .unwrap(),
179 ];
180 let account_size = ExtraAccountMetaList::size_of(extra_metas.len()).unwrap();
181 let mut data = vec![0u8; account_size];
182 ExtraAccountMetaList::init::<ExecuteInstruction>(&mut data, &extra_metas).unwrap();
183 data
184 }
185
186 #[test]
187 fn test_add_extra_accounts_for_execute_cpi() {
188 let spl_token_2022_program_id = Pubkey::new_unique(); let transfer_hook_program_id = Pubkey::new_unique();
190
191 let amount = 100u64;
192
193 let source_pubkey = Pubkey::new_unique();
194 let mut source_data = vec![0; 165]; let mut source_lamports = 0; let source_account_info = AccountInfo::new(
197 &source_pubkey,
198 false,
199 true,
200 &mut source_lamports,
201 &mut source_data,
202 &spl_token_2022_program_id,
203 false,
204 );
205
206 let mint_pubkey = Pubkey::new_unique();
207 let mut mint_data = vec![0; 165]; let mut mint_lamports = 0; let mint_account_info = AccountInfo::new(
210 &mint_pubkey,
211 false,
212 true,
213 &mut mint_lamports,
214 &mut mint_data,
215 &spl_token_2022_program_id,
216 false,
217 );
218
219 let destination_pubkey = Pubkey::new_unique();
220 let mut destination_data = vec![0; 165]; let mut destination_lamports = 0; let destination_account_info = AccountInfo::new(
223 &destination_pubkey,
224 false,
225 true,
226 &mut destination_lamports,
227 &mut destination_data,
228 &spl_token_2022_program_id,
229 false,
230 );
231
232 let authority_pubkey = Pubkey::new_unique();
233 let mut authority_data = vec![]; let mut authority_lamports = 0; let authority_account_info = AccountInfo::new(
236 &authority_pubkey,
237 false,
238 true,
239 &mut authority_lamports,
240 &mut authority_data,
241 &system_program::ID,
242 false,
243 );
244
245 let validate_state_pubkey =
246 get_extra_account_metas_address(&mint_pubkey, &transfer_hook_program_id);
247
248 let extra_meta_1_pubkey = EXTRA_META_1;
249 let mut extra_meta_1_data = vec![]; let mut extra_meta_1_lamports = 0; let extra_meta_1_account_info = AccountInfo::new(
252 &extra_meta_1_pubkey,
253 true,
254 false,
255 &mut extra_meta_1_lamports,
256 &mut extra_meta_1_data,
257 &system_program::ID,
258 false,
259 );
260
261 let extra_meta_2_pubkey = EXTRA_META_2;
262 let mut extra_meta_2_data = vec![]; let mut extra_meta_2_lamports = 0; let extra_meta_2_account_info = AccountInfo::new(
265 &extra_meta_2_pubkey,
266 true,
267 false,
268 &mut extra_meta_2_lamports,
269 &mut extra_meta_2_data,
270 &system_program::ID,
271 false,
272 );
273
274 let extra_meta_3_pubkey = Pubkey::find_program_address(
275 &[
276 &source_pubkey.to_bytes(),
277 &destination_pubkey.to_bytes(),
278 &validate_state_pubkey.to_bytes(),
279 ],
280 &transfer_hook_program_id,
281 )
282 .0;
283 let mut extra_meta_3_data = vec![]; let mut extra_meta_3_lamports = 0; let extra_meta_3_account_info = AccountInfo::new(
286 &extra_meta_3_pubkey,
287 false,
288 true,
289 &mut extra_meta_3_lamports,
290 &mut extra_meta_3_data,
291 &transfer_hook_program_id,
292 false,
293 );
294
295 let extra_meta_4_pubkey = Pubkey::find_program_address(
296 &[
297 &amount.to_le_bytes(),
298 &destination_pubkey.to_bytes(),
299 &extra_meta_1_pubkey.to_bytes(),
300 &extra_meta_3_pubkey.to_bytes(),
301 ],
302 &transfer_hook_program_id,
303 )
304 .0;
305 let mut extra_meta_4_data = vec![]; let mut extra_meta_4_lamports = 0; let extra_meta_4_account_info = AccountInfo::new(
308 &extra_meta_4_pubkey,
309 false,
310 true,
311 &mut extra_meta_4_lamports,
312 &mut extra_meta_4_data,
313 &transfer_hook_program_id,
314 false,
315 );
316
317 let mut validate_state_data = setup_validation_data();
318 let mut validate_state_lamports = 0; let validate_state_account_info = AccountInfo::new(
320 &validate_state_pubkey,
321 false,
322 true,
323 &mut validate_state_lamports,
324 &mut validate_state_data,
325 &transfer_hook_program_id,
326 false,
327 );
328
329 let mut transfer_hook_program_data = vec![]; let mut transfer_hook_program_lamports = 0; let transfer_hook_program_account_info = AccountInfo::new(
332 &transfer_hook_program_id,
333 false,
334 true,
335 &mut transfer_hook_program_lamports,
336 &mut transfer_hook_program_data,
337 &bpf_loader_upgradeable::ID,
338 false,
339 );
340
341 let mut cpi_instruction = Instruction::new_with_bytes(
342 spl_token_2022_program_id,
343 &[],
344 vec![
345 AccountMeta::new(source_pubkey, false),
346 AccountMeta::new_readonly(mint_pubkey, false),
347 AccountMeta::new(destination_pubkey, false),
348 AccountMeta::new_readonly(authority_pubkey, true),
349 ],
350 );
351 let mut cpi_account_infos = vec![
352 source_account_info.clone(),
353 mint_account_info.clone(),
354 destination_account_info.clone(),
355 authority_account_info.clone(),
356 ];
357 let additional_account_infos = vec![
358 extra_meta_1_account_info.clone(),
359 extra_meta_2_account_info.clone(),
360 extra_meta_3_account_info.clone(),
361 extra_meta_4_account_info.clone(),
362 transfer_hook_program_account_info.clone(),
363 validate_state_account_info.clone(),
364 ];
365
366 {
368 let additional_account_infos_missing_infos = vec![
369 extra_meta_1_account_info.clone(),
370 extra_meta_2_account_info.clone(),
371 extra_meta_3_account_info.clone(),
372 extra_meta_4_account_info.clone(),
373 transfer_hook_program_account_info.clone(),
375 ];
376 let mut cpi_instruction = cpi_instruction.clone();
377 let mut cpi_account_infos = cpi_account_infos.clone();
378 add_extra_accounts_for_execute_cpi(
379 &mut cpi_instruction,
380 &mut cpi_account_infos,
381 &transfer_hook_program_id,
382 source_account_info.clone(),
383 mint_account_info.clone(),
384 destination_account_info.clone(),
385 authority_account_info.clone(),
386 amount,
387 &additional_account_infos_missing_infos,
388 )
389 .unwrap();
390 let check_metas = [
391 AccountMeta::new(source_pubkey, false),
392 AccountMeta::new_readonly(mint_pubkey, false),
393 AccountMeta::new(destination_pubkey, false),
394 AccountMeta::new_readonly(authority_pubkey, true),
395 AccountMeta::new_readonly(transfer_hook_program_id, false),
396 ];
397
398 let check_account_infos = vec![
399 source_account_info.clone(),
400 mint_account_info.clone(),
401 destination_account_info.clone(),
402 authority_account_info.clone(),
403 transfer_hook_program_account_info.clone(),
404 ];
405
406 assert_eq!(cpi_instruction.accounts, check_metas);
407 for (a, b) in std::iter::zip(cpi_account_infos, check_account_infos) {
408 assert_eq!(a.key, b.key);
409 assert_eq!(a.is_signer, b.is_signer);
410 assert_eq!(a.is_writable, b.is_writable);
411 }
412 }
413
414 let additional_account_infos_missing_infos = vec![
416 extra_meta_1_account_info.clone(),
417 extra_meta_2_account_info.clone(),
418 extra_meta_3_account_info.clone(),
419 extra_meta_4_account_info.clone(),
420 validate_state_account_info.clone(),
421 ];
423 assert_eq!(
424 add_extra_accounts_for_execute_cpi(
425 &mut cpi_instruction,
426 &mut cpi_account_infos,
427 &transfer_hook_program_id,
428 source_account_info.clone(),
429 mint_account_info.clone(),
430 destination_account_info.clone(),
431 authority_account_info.clone(),
432 amount,
433 &additional_account_infos_missing_infos, )
435 .unwrap_err(),
436 TransferHookError::IncorrectAccount.into()
437 );
438
439 let additional_account_infos_missing_infos = vec![
441 extra_meta_1_account_info.clone(),
442 extra_meta_2_account_info.clone(),
443 extra_meta_4_account_info.clone(),
445 validate_state_account_info.clone(),
446 transfer_hook_program_account_info.clone(),
447 ];
448 assert_eq!(
449 add_extra_accounts_for_execute_cpi(
450 &mut cpi_instruction,
451 &mut cpi_account_infos,
452 &transfer_hook_program_id,
453 source_account_info.clone(),
454 mint_account_info.clone(),
455 destination_account_info.clone(),
456 authority_account_info.clone(),
457 amount,
458 &additional_account_infos_missing_infos, )
460 .unwrap_err(),
461 AccountResolutionError::IncorrectAccount.into() );
463
464 add_extra_accounts_for_execute_cpi(
466 &mut cpi_instruction,
467 &mut cpi_account_infos,
468 &transfer_hook_program_id,
469 source_account_info.clone(),
470 mint_account_info.clone(),
471 destination_account_info.clone(),
472 authority_account_info.clone(),
473 amount,
474 &additional_account_infos,
475 )
476 .unwrap();
477
478 let check_metas = [
479 AccountMeta::new(source_pubkey, false),
480 AccountMeta::new_readonly(mint_pubkey, false),
481 AccountMeta::new(destination_pubkey, false),
482 AccountMeta::new_readonly(authority_pubkey, true),
483 AccountMeta::new_readonly(EXTRA_META_1, true),
484 AccountMeta::new_readonly(EXTRA_META_2, true),
485 AccountMeta::new(extra_meta_3_pubkey, false),
486 AccountMeta::new(extra_meta_4_pubkey, false),
487 AccountMeta::new_readonly(validate_state_pubkey, false),
488 AccountMeta::new_readonly(transfer_hook_program_id, false),
489 ];
490
491 let check_account_infos = vec![
492 source_account_info,
493 mint_account_info,
494 destination_account_info,
495 authority_account_info,
496 extra_meta_1_account_info,
497 extra_meta_2_account_info,
498 extra_meta_3_account_info,
499 extra_meta_4_account_info,
500 validate_state_account_info,
501 transfer_hook_program_account_info,
502 ];
503
504 assert_eq!(cpi_instruction.accounts, check_metas);
505 for (a, b) in std::iter::zip(cpi_account_infos, check_account_infos) {
506 assert_eq!(a.key, b.key);
507 assert_eq!(a.is_signer, b.is_signer);
508 assert_eq!(a.is_writable, b.is_writable);
509 }
510 }
511}