spl_token_2022/
onchain.rs

1//! On-chain program invoke helper to perform on-chain `transfer_checked` with
2//! correct accounts
3
4use {
5    crate::{
6        extension::{transfer_fee, transfer_hook, StateWithExtensions},
7        instruction,
8        state::Mint,
9    },
10    solana_program::{
11        account_info::AccountInfo, entrypoint::ProgramResult, instruction::AccountMeta,
12        program::invoke_signed, pubkey::Pubkey,
13    },
14    spl_transfer_hook_interface::onchain::add_extra_accounts_for_execute_cpi,
15};
16
17/// Helper to CPI into token-2022 on-chain, looking through the additional
18/// account infos to create the proper instruction with the proper account infos
19#[allow(clippy::too_many_arguments)]
20pub fn invoke_transfer_checked<'a>(
21    token_program_id: &Pubkey,
22    source_info: AccountInfo<'a>,
23    mint_info: AccountInfo<'a>,
24    destination_info: AccountInfo<'a>,
25    authority_info: AccountInfo<'a>,
26    additional_accounts: &[AccountInfo<'a>],
27    amount: u64,
28    decimals: u8,
29    seeds: &[&[&[u8]]],
30) -> ProgramResult {
31    let mut cpi_instruction = instruction::transfer_checked(
32        token_program_id,
33        source_info.key,
34        mint_info.key,
35        destination_info.key,
36        authority_info.key,
37        &[], // add them later, to avoid unnecessary clones
38        amount,
39        decimals,
40    )?;
41
42    let mut cpi_account_infos = vec![
43        source_info.clone(),
44        mint_info.clone(),
45        destination_info.clone(),
46        authority_info.clone(),
47    ];
48
49    // if it's a signer, it might be a multisig signer, throw it in!
50    additional_accounts
51        .iter()
52        .filter(|ai| ai.is_signer)
53        .for_each(|ai| {
54            cpi_account_infos.push(ai.clone());
55            cpi_instruction
56                .accounts
57                .push(AccountMeta::new_readonly(*ai.key, ai.is_signer));
58        });
59
60    // scope the borrowing to avoid a double-borrow during CPI
61    {
62        let mint_data = mint_info.try_borrow_data()?;
63        let mint = StateWithExtensions::<Mint>::unpack(&mint_data)?;
64        if let Some(program_id) = transfer_hook::get_program_id(&mint) {
65            add_extra_accounts_for_execute_cpi(
66                &mut cpi_instruction,
67                &mut cpi_account_infos,
68                &program_id,
69                source_info,
70                mint_info.clone(),
71                destination_info,
72                authority_info,
73                amount,
74                additional_accounts,
75            )?;
76        }
77    }
78
79    invoke_signed(&cpi_instruction, &cpi_account_infos, seeds)
80}
81
82/// Helper to CPI into token-2022 on-chain, looking through the additional
83/// account infos to create the proper instruction with the fee
84/// and proper account infos
85#[allow(clippy::too_many_arguments)]
86pub fn invoke_transfer_checked_with_fee<'a>(
87    token_program_id: &Pubkey,
88    source_info: AccountInfo<'a>,
89    mint_info: AccountInfo<'a>,
90    destination_info: AccountInfo<'a>,
91    authority_info: AccountInfo<'a>,
92    additional_accounts: &[AccountInfo<'a>],
93    amount: u64,
94    decimals: u8,
95    fee: u64,
96    seeds: &[&[&[u8]]],
97) -> ProgramResult {
98    let mut cpi_instruction = transfer_fee::instruction::transfer_checked_with_fee(
99        token_program_id,
100        source_info.key,
101        mint_info.key,
102        destination_info.key,
103        authority_info.key,
104        &[], // add them later, to avoid unnecessary clones
105        amount,
106        decimals,
107        fee,
108    )?;
109
110    let mut cpi_account_infos = vec![
111        source_info.clone(),
112        mint_info.clone(),
113        destination_info.clone(),
114        authority_info.clone(),
115    ];
116
117    // if it's a signer, it might be a multisig signer, throw it in!
118    additional_accounts
119        .iter()
120        .filter(|ai| ai.is_signer)
121        .for_each(|ai| {
122            cpi_account_infos.push(ai.clone());
123            cpi_instruction
124                .accounts
125                .push(AccountMeta::new_readonly(*ai.key, ai.is_signer));
126        });
127
128    // scope the borrowing to avoid a double-borrow during CPI
129    {
130        let mint_data = mint_info.try_borrow_data()?;
131        let mint = StateWithExtensions::<Mint>::unpack(&mint_data)?;
132        if let Some(program_id) = transfer_hook::get_program_id(&mint) {
133            add_extra_accounts_for_execute_cpi(
134                &mut cpi_instruction,
135                &mut cpi_account_infos,
136                &program_id,
137                source_info,
138                mint_info.clone(),
139                destination_info,
140                authority_info,
141                amount,
142                additional_accounts,
143            )?;
144        }
145    }
146
147    invoke_signed(&cpi_instruction, &cpi_account_infos, seeds)
148}