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_program::{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 0,
205 );
206
207 let mint_pubkey = Pubkey::new_unique();
208 let mut mint_data = vec![0; 165]; let mut mint_lamports = 0; let mint_account_info = AccountInfo::new(
211 &mint_pubkey,
212 false,
213 true,
214 &mut mint_lamports,
215 &mut mint_data,
216 &spl_token_2022_program_id,
217 false,
218 0,
219 );
220
221 let destination_pubkey = Pubkey::new_unique();
222 let mut destination_data = vec![0; 165]; let mut destination_lamports = 0; let destination_account_info = AccountInfo::new(
225 &destination_pubkey,
226 false,
227 true,
228 &mut destination_lamports,
229 &mut destination_data,
230 &spl_token_2022_program_id,
231 false,
232 0,
233 );
234
235 let authority_pubkey = Pubkey::new_unique();
236 let mut authority_data = vec![]; let mut authority_lamports = 0; let authority_account_info = AccountInfo::new(
239 &authority_pubkey,
240 false,
241 true,
242 &mut authority_lamports,
243 &mut authority_data,
244 &system_program::ID,
245 false,
246 0,
247 );
248
249 let validate_state_pubkey =
250 get_extra_account_metas_address(&mint_pubkey, &transfer_hook_program_id);
251
252 let extra_meta_1_pubkey = EXTRA_META_1;
253 let mut extra_meta_1_data = vec![]; let mut extra_meta_1_lamports = 0; let extra_meta_1_account_info = AccountInfo::new(
256 &extra_meta_1_pubkey,
257 true,
258 false,
259 &mut extra_meta_1_lamports,
260 &mut extra_meta_1_data,
261 &system_program::ID,
262 false,
263 0,
264 );
265
266 let extra_meta_2_pubkey = EXTRA_META_2;
267 let mut extra_meta_2_data = vec![]; let mut extra_meta_2_lamports = 0; let extra_meta_2_account_info = AccountInfo::new(
270 &extra_meta_2_pubkey,
271 true,
272 false,
273 &mut extra_meta_2_lamports,
274 &mut extra_meta_2_data,
275 &system_program::ID,
276 false,
277 0,
278 );
279
280 let extra_meta_3_pubkey = Pubkey::find_program_address(
281 &[
282 &source_pubkey.to_bytes(),
283 &destination_pubkey.to_bytes(),
284 &validate_state_pubkey.to_bytes(),
285 ],
286 &transfer_hook_program_id,
287 )
288 .0;
289 let mut extra_meta_3_data = vec![]; let mut extra_meta_3_lamports = 0; let extra_meta_3_account_info = AccountInfo::new(
292 &extra_meta_3_pubkey,
293 false,
294 true,
295 &mut extra_meta_3_lamports,
296 &mut extra_meta_3_data,
297 &transfer_hook_program_id,
298 false,
299 0,
300 );
301
302 let extra_meta_4_pubkey = Pubkey::find_program_address(
303 &[
304 &amount.to_le_bytes(),
305 &destination_pubkey.to_bytes(),
306 &extra_meta_1_pubkey.to_bytes(),
307 &extra_meta_3_pubkey.to_bytes(),
308 ],
309 &transfer_hook_program_id,
310 )
311 .0;
312 let mut extra_meta_4_data = vec![]; let mut extra_meta_4_lamports = 0; let extra_meta_4_account_info = AccountInfo::new(
315 &extra_meta_4_pubkey,
316 false,
317 true,
318 &mut extra_meta_4_lamports,
319 &mut extra_meta_4_data,
320 &transfer_hook_program_id,
321 false,
322 0,
323 );
324
325 let mut validate_state_data = setup_validation_data();
326 let mut validate_state_lamports = 0; let validate_state_account_info = AccountInfo::new(
328 &validate_state_pubkey,
329 false,
330 true,
331 &mut validate_state_lamports,
332 &mut validate_state_data,
333 &transfer_hook_program_id,
334 false,
335 0,
336 );
337
338 let mut transfer_hook_program_data = vec![]; let mut transfer_hook_program_lamports = 0; let transfer_hook_program_account_info = AccountInfo::new(
341 &transfer_hook_program_id,
342 false,
343 true,
344 &mut transfer_hook_program_lamports,
345 &mut transfer_hook_program_data,
346 &bpf_loader_upgradeable::ID,
347 false,
348 0,
349 );
350
351 let mut cpi_instruction = Instruction::new_with_bytes(
352 spl_token_2022_program_id,
353 &[],
354 vec![
355 AccountMeta::new(source_pubkey, false),
356 AccountMeta::new_readonly(mint_pubkey, false),
357 AccountMeta::new(destination_pubkey, false),
358 AccountMeta::new_readonly(authority_pubkey, true),
359 ],
360 );
361 let mut cpi_account_infos = vec![
362 source_account_info.clone(),
363 mint_account_info.clone(),
364 destination_account_info.clone(),
365 authority_account_info.clone(),
366 ];
367 let additional_account_infos = vec![
368 extra_meta_1_account_info.clone(),
369 extra_meta_2_account_info.clone(),
370 extra_meta_3_account_info.clone(),
371 extra_meta_4_account_info.clone(),
372 transfer_hook_program_account_info.clone(),
373 validate_state_account_info.clone(),
374 ];
375
376 {
378 let additional_account_infos_missing_infos = vec![
379 extra_meta_1_account_info.clone(),
380 extra_meta_2_account_info.clone(),
381 extra_meta_3_account_info.clone(),
382 extra_meta_4_account_info.clone(),
383 transfer_hook_program_account_info.clone(),
385 ];
386 let mut cpi_instruction = cpi_instruction.clone();
387 let mut cpi_account_infos = cpi_account_infos.clone();
388 add_extra_accounts_for_execute_cpi(
389 &mut cpi_instruction,
390 &mut cpi_account_infos,
391 &transfer_hook_program_id,
392 source_account_info.clone(),
393 mint_account_info.clone(),
394 destination_account_info.clone(),
395 authority_account_info.clone(),
396 amount,
397 &additional_account_infos_missing_infos,
398 )
399 .unwrap();
400 let check_metas = [
401 AccountMeta::new(source_pubkey, false),
402 AccountMeta::new_readonly(mint_pubkey, false),
403 AccountMeta::new(destination_pubkey, false),
404 AccountMeta::new_readonly(authority_pubkey, true),
405 AccountMeta::new_readonly(transfer_hook_program_id, false),
406 ];
407
408 let check_account_infos = vec![
409 source_account_info.clone(),
410 mint_account_info.clone(),
411 destination_account_info.clone(),
412 authority_account_info.clone(),
413 transfer_hook_program_account_info.clone(),
414 ];
415
416 assert_eq!(cpi_instruction.accounts, check_metas);
417 for (a, b) in std::iter::zip(cpi_account_infos, check_account_infos) {
418 assert_eq!(a.key, b.key);
419 assert_eq!(a.is_signer, b.is_signer);
420 assert_eq!(a.is_writable, b.is_writable);
421 }
422 }
423
424 let additional_account_infos_missing_infos = vec![
426 extra_meta_1_account_info.clone(),
427 extra_meta_2_account_info.clone(),
428 extra_meta_3_account_info.clone(),
429 extra_meta_4_account_info.clone(),
430 validate_state_account_info.clone(),
431 ];
433 assert_eq!(
434 add_extra_accounts_for_execute_cpi(
435 &mut cpi_instruction,
436 &mut cpi_account_infos,
437 &transfer_hook_program_id,
438 source_account_info.clone(),
439 mint_account_info.clone(),
440 destination_account_info.clone(),
441 authority_account_info.clone(),
442 amount,
443 &additional_account_infos_missing_infos, )
445 .unwrap_err(),
446 TransferHookError::IncorrectAccount.into()
447 );
448
449 let additional_account_infos_missing_infos = vec![
451 extra_meta_1_account_info.clone(),
452 extra_meta_2_account_info.clone(),
453 extra_meta_4_account_info.clone(),
455 validate_state_account_info.clone(),
456 transfer_hook_program_account_info.clone(),
457 ];
458 assert_eq!(
459 add_extra_accounts_for_execute_cpi(
460 &mut cpi_instruction,
461 &mut cpi_account_infos,
462 &transfer_hook_program_id,
463 source_account_info.clone(),
464 mint_account_info.clone(),
465 destination_account_info.clone(),
466 authority_account_info.clone(),
467 amount,
468 &additional_account_infos_missing_infos, )
470 .unwrap_err(),
471 AccountResolutionError::IncorrectAccount.into() );
473
474 add_extra_accounts_for_execute_cpi(
476 &mut cpi_instruction,
477 &mut cpi_account_infos,
478 &transfer_hook_program_id,
479 source_account_info.clone(),
480 mint_account_info.clone(),
481 destination_account_info.clone(),
482 authority_account_info.clone(),
483 amount,
484 &additional_account_infos,
485 )
486 .unwrap();
487
488 let check_metas = [
489 AccountMeta::new(source_pubkey, false),
490 AccountMeta::new_readonly(mint_pubkey, false),
491 AccountMeta::new(destination_pubkey, false),
492 AccountMeta::new_readonly(authority_pubkey, true),
493 AccountMeta::new_readonly(EXTRA_META_1, true),
494 AccountMeta::new_readonly(EXTRA_META_2, true),
495 AccountMeta::new(extra_meta_3_pubkey, false),
496 AccountMeta::new(extra_meta_4_pubkey, false),
497 AccountMeta::new_readonly(validate_state_pubkey, false),
498 AccountMeta::new_readonly(transfer_hook_program_id, false),
499 ];
500
501 let check_account_infos = vec![
502 source_account_info,
503 mint_account_info,
504 destination_account_info,
505 authority_account_info,
506 extra_meta_1_account_info,
507 extra_meta_2_account_info,
508 extra_meta_3_account_info,
509 extra_meta_4_account_info,
510 validate_state_account_info,
511 transfer_hook_program_account_info,
512 ];
513
514 assert_eq!(cpi_instruction.accounts, check_metas);
515 for (a, b) in std::iter::zip(cpi_account_infos, check_account_infos) {
516 assert_eq!(a.key, b.key);
517 assert_eq!(a.is_signer, b.is_signer);
518 assert_eq!(a.is_writable, b.is_writable);
519 }
520 }
521}