1#![allow(deprecated)]
2use anchor_lang::{prelude::*, solana_program::account_info::AccountInfo};
3use anchor_spl::{token::TokenAccount, token_interface};
4
5use crate::{
6 check_spl_token_pool_derivation,
7 constants::{NUM_MAX_POOL_ACCOUNTS, POOL_SEED},
8 process_transfer::{get_cpi_signer_seeds, CompressedTokenInstructionDataTransfer},
9 ErrorCode, TransferInstruction,
10};
11
12pub fn process_compression_or_decompression<'info>(
13 inputs: &CompressedTokenInstructionDataTransfer,
14 ctx: &Context<'_, '_, '_, 'info, TransferInstruction<'info>>,
15) -> Result<()> {
16 if inputs.is_compress {
17 compress_spl_tokens(inputs, ctx)
18 } else {
19 decompress_spl_tokens(inputs, ctx)
20 }
21}
22
23pub fn check_spl_token_pool_derivation_with_index(
24 mint_bytes: &[u8],
25 token_pool_pubkey: &Pubkey,
26 pool_index: &[u8],
27) -> Result<()> {
28 if is_valid_token_pool_pda(mint_bytes, token_pool_pubkey, pool_index, None)? {
29 Ok(())
30 } else {
31 err!(ErrorCode::InvalidTokenPoolPda)
32 }
33}
34
35pub fn is_valid_token_pool_pda(
36 mint_bytes: &[u8],
37 token_pool_pubkey: &Pubkey,
38 pool_index: &[u8],
39 bump: Option<u8>,
40) -> Result<bool> {
41 let pool_index = if pool_index[0] == 0 { &[] } else { pool_index };
42 let pda = if let Some(bump) = bump {
43 let seeds = [POOL_SEED, mint_bytes, pool_index, &[bump]];
44 Pubkey::create_program_address(&seeds[..], &crate::ID)
45 .map_err(|_| crate::ErrorCode::NoMatchingBumpFound)?
46 } else {
47 let seeds = [POOL_SEED, mint_bytes, pool_index];
48 Pubkey::find_program_address(&seeds[..], &crate::ID).0
49 };
50 Ok(pda == *token_pool_pubkey)
51}
52
53pub fn decompress_spl_tokens<'info>(
54 inputs: &CompressedTokenInstructionDataTransfer,
55 ctx: &Context<'_, '_, '_, 'info, TransferInstruction<'info>>,
56) -> Result<()> {
57 let recipient = match ctx.accounts.compress_or_decompress_token_account.as_ref() {
58 Some(compression_recipient) => compression_recipient.to_account_info(),
59 None => return err!(ErrorCode::DecompressRecipientUndefinedForDecompress),
60 };
61 let token_pool_pda = match ctx.accounts.token_pool_pda.as_ref() {
62 Some(token_pool_pda) => token_pool_pda.to_account_info(),
63 None => return err!(ErrorCode::CompressedPdaUndefinedForDecompress),
64 };
65 let amount = match inputs.compress_or_decompress_amount {
66 Some(amount) => amount,
67 None => return err!(ErrorCode::DeCompressAmountUndefinedForDecompress),
68 };
69 invoke_token_program_with_multiple_token_pool_accounts::<false>(
70 ctx.remaining_accounts,
71 &inputs.mint.key().to_bytes(),
72 None,
73 Some(recipient),
74 ctx.accounts.cpi_authority_pda.to_account_info(),
75 ctx.accounts
76 .token_program
77 .as_ref()
78 .unwrap()
79 .to_account_info(),
80 token_pool_pda,
81 amount,
82 )
83}
84
85#[allow(clippy::too_many_arguments)]
102pub fn invoke_token_program_with_multiple_token_pool_accounts<'info, const IS_BURN: bool>(
103 remaining_accounts: &[AccountInfo<'info>],
104 mint_bytes: &[u8; 32],
105 mint: Option<AccountInfo<'info>>,
106 recipient: Option<AccountInfo<'info>>,
107 cpi_authority_pda: AccountInfo<'info>,
108 token_program: AccountInfo<'info>,
109 mut token_pool_pda: AccountInfo<'info>,
110 mut amount: u64,
111) -> Result<()> {
112 let mut token_pool_indices: Vec<u8> = (0..NUM_MAX_POOL_ACCOUNTS).collect();
113 for i in 0..NUM_MAX_POOL_ACCOUNTS {
115 if i != 0 {
117 token_pool_pda = remaining_accounts[i as usize - 1].to_account_info();
118 }
119 let token_pool_amount =
120 TokenAccount::try_deserialize(&mut &token_pool_pda.data.borrow()[..])
121 .map_err(|_| ErrorCode::InvalidTokenPoolPda)?
122 .amount;
123 let action_amount = std::cmp::min(amount, token_pool_amount);
125 if action_amount == 0 {
127 continue;
128 }
129 for (index, i) in token_pool_indices.iter().enumerate() {
131 if is_valid_token_pool_pda(mint_bytes.as_slice(), &token_pool_pda.key(), &[*i], None)? {
132 if IS_BURN {
134 crate::burn::spl_burn_cpi(
135 mint.clone().unwrap(),
136 cpi_authority_pda.to_account_info(),
137 token_pool_pda.to_account_info(),
138 token_program.to_account_info(),
139 action_amount,
140 token_pool_amount,
141 )?;
142 } else {
143 crate::spl_compression::spl_token_transfer_cpi_with_signer(
144 token_pool_pda.to_account_info(),
145 recipient.clone().unwrap(),
146 cpi_authority_pda.to_account_info(),
147 token_program.to_account_info(),
148 action_amount,
149 )?;
150 }
151 token_pool_indices.remove(index);
153 amount = amount.saturating_sub(action_amount);
155 break;
156 } else if index == token_pool_indices.len() - 1 {
157 return err!(crate::ErrorCode::NoMatchingBumpFound);
160 }
161 }
162
163 if amount == 0 {
166 return Ok(());
167 }
168 }
169
170 msg!("Remaining amount: {}.", amount);
172 if IS_BURN {
173 msg!("Token pool account balances insufficient for burn. \nTry to pass more token pool accounts.");
174 err!(ErrorCode::FailedToBurnSplTokensFromTokenPool)
175 } else {
176 msg!("Token pool account balances insufficient for decompression. \nTry to pass more token pool accounts.");
177 err!(ErrorCode::FailedToDecompress)
178 }
179}
180
181pub fn compress_spl_tokens<'info>(
182 inputs: &CompressedTokenInstructionDataTransfer,
183 ctx: &Context<'_, '_, '_, 'info, TransferInstruction<'info>>,
184) -> Result<()> {
185 let recipient_token_pool = match ctx.accounts.token_pool_pda.as_ref() {
186 Some(token_pool_pda) => token_pool_pda.to_account_info(),
187 None => return err!(ErrorCode::CompressedPdaUndefinedForCompress),
188 };
189 let amount = match inputs.compress_or_decompress_amount {
190 Some(amount) => amount,
191 None => return err!(ErrorCode::DeCompressAmountUndefinedForCompress),
192 };
193
194 check_spl_token_pool_derivation(&recipient_token_pool.key(), &inputs.mint)?;
195 spl_token_transfer(
196 ctx.accounts
197 .compress_or_decompress_token_account
198 .as_ref()
199 .unwrap()
200 .to_account_info(),
201 recipient_token_pool.to_account_info(),
202 ctx.accounts.authority.to_account_info(),
203 ctx.accounts
204 .token_program
205 .as_ref()
206 .unwrap()
207 .to_account_info(),
208 amount,
209 )
210}
211
212pub fn spl_token_transfer_cpi_with_signer<'info>(
215 from: AccountInfo<'info>,
216 to: AccountInfo<'info>,
217 authority: AccountInfo<'info>,
218 token_program: AccountInfo<'info>,
219 amount: u64,
220) -> Result<()> {
221 let signer_seeds = get_cpi_signer_seeds();
222 let signer_seeds_ref = &[&signer_seeds[..]];
223
224 let accounts = token_interface::Transfer {
225 from,
226 to,
227 authority,
228 };
229 let cpi_ctx = CpiContext::new_with_signer(token_program, accounts, signer_seeds_ref);
230 anchor_spl::token_interface::transfer(cpi_ctx, amount)
231}
232
233pub fn spl_token_transfer<'info>(
236 from: AccountInfo<'info>,
237 to: AccountInfo<'info>,
238 authority: AccountInfo<'info>,
239 token_program: AccountInfo<'info>,
240 amount: u64,
241) -> Result<()> {
242 let instruction = match *token_program.key {
243 spl_token_2022::ID => spl_token_2022::instruction::transfer(
244 token_program.key,
245 from.key,
246 to.key,
247 authority.key,
248 &[],
249 amount,
250 ),
251 spl_token::ID => spl_token::instruction::transfer(
252 token_program.key,
253 from.key,
254 to.key,
255 authority.key,
256 &[],
257 amount,
258 ),
259 _ => return Err(anchor_lang::error::ErrorCode::InvalidProgramId.into()),
260 }?;
261
262 anchor_lang::solana_program::program::invoke(
263 &instruction,
264 &[from, to, authority, token_program],
265 )?;
266 Ok(())
267}