1use crate::{
2 constants::{BUMP_CPI_AUTHORITY, NOT_FROZEN, TOKEN_COMPRESSED_ACCOUNT_DISCRIMINATOR},
3 spl_compression::process_compression_or_decompression,
4 token_data::{AccountState, TokenData},
5 ErrorCode, TransferInstruction,
6};
7use account_compression::utils::constants::CPI_AUTHORITY_PDA_SEED;
8use anchor_lang::{prelude::*, solana_program::program_error::ProgramError, AnchorDeserialize};
9use light_hasher::Poseidon;
10use light_heap::{bench_sbf_end, bench_sbf_start};
11use light_system_program::{
12 invoke::processor::CompressedProof,
13 sdk::{
14 accounts::{InvokeAccounts, SignerAccounts},
15 compressed_account::{
16 CompressedAccount, CompressedAccountData, PackedCompressedAccountWithMerkleContext,
17 PackedMerkleContext,
18 },
19 CompressedCpiContext,
20 },
21 InstructionDataInvokeCpi, OutputCompressedAccountWithPackedContext,
22};
23use light_utils::hash_to_bn254_field_size_be;
24
25pub fn process_transfer<'a, 'b, 'c, 'info: 'b + 'c>(
37 ctx: Context<'a, 'b, 'c, 'info, TransferInstruction<'info>>,
38 inputs: Vec<u8>,
39) -> Result<()> {
40 bench_sbf_start!("t_deserialize");
41 let inputs: CompressedTokenInstructionDataTransfer =
42 CompressedTokenInstructionDataTransfer::deserialize(&mut inputs.as_slice())?;
43 bench_sbf_end!("t_deserialize");
44 bench_sbf_start!("t_context_and_check_sig");
45 if inputs.input_token_data_with_context.is_empty()
46 && inputs.compress_or_decompress_amount.is_none()
47 {
48 return err!(crate::ErrorCode::NoInputTokenAccountsProvided);
49 }
50 let (mut compressed_input_accounts, input_token_data, input_lamports) =
51 get_input_compressed_accounts_with_merkle_context_and_check_signer::<NOT_FROZEN>(
52 &ctx.accounts.authority.key(),
53 &inputs.delegated_transfer,
54 ctx.remaining_accounts,
55 &inputs.input_token_data_with_context,
56 &inputs.mint,
57 )?;
58 bench_sbf_end!("t_context_and_check_sig");
59 bench_sbf_start!("t_sum_check");
60 sum_check(
61 &input_token_data,
62 &inputs
63 .output_compressed_accounts
64 .iter()
65 .map(|data| data.amount)
66 .collect::<Vec<u64>>(),
67 inputs.compress_or_decompress_amount.as_ref(),
68 inputs.is_compress,
69 )?;
70 bench_sbf_end!("t_sum_check");
71 bench_sbf_start!("t_process_compression");
72 if inputs.compress_or_decompress_amount.is_some() {
73 process_compression_or_decompression(&inputs, &ctx)?;
74 }
75 bench_sbf_end!("t_process_compression");
76 bench_sbf_start!("t_create_output_compressed_accounts");
77 let hashed_mint = match hash_to_bn254_field_size_be(&inputs.mint.to_bytes()) {
78 Some(hashed_mint) => hashed_mint.0,
79 None => return err!(ErrorCode::HashToFieldError),
80 };
81
82 let mut output_compressed_accounts = vec![
83 OutputCompressedAccountWithPackedContext::default();
84 inputs.output_compressed_accounts.len()
85 ];
86
87 let (is_delegate, delegate) = if let Some(delegated_transfer) = inputs.delegated_transfer {
90 let mut vec = vec![false; inputs.output_compressed_accounts.len()];
91 if let Some(index) = delegated_transfer.delegate_change_account_index {
92 vec[index as usize] = true;
93 (Some(vec), Some(ctx.accounts.authority.key()))
94 } else {
95 (None, None)
96 }
97 } else {
98 (None, None)
99 };
100 inputs.output_compressed_accounts.iter().for_each(|data| {
101 if data.tlv.is_some() {
102 unimplemented!("Tlv is unimplemented");
103 }
104 });
105 let output_lamports = create_output_compressed_accounts(
106 &mut output_compressed_accounts,
107 inputs.mint,
108 inputs
109 .output_compressed_accounts
110 .iter()
111 .map(|data| data.owner)
112 .collect::<Vec<Pubkey>>()
113 .as_slice(),
114 delegate,
115 is_delegate,
116 inputs
117 .output_compressed_accounts
118 .iter()
119 .map(|data: &PackedTokenTransferOutputData| data.amount)
120 .collect::<Vec<u64>>()
121 .as_slice(),
122 Some(
123 inputs
124 .output_compressed_accounts
125 .iter()
126 .map(|data: &PackedTokenTransferOutputData| data.lamports)
127 .collect::<Vec<Option<u64>>>(),
128 ),
129 &hashed_mint,
130 &inputs
131 .output_compressed_accounts
132 .iter()
133 .map(|data| data.merkle_tree_index)
134 .collect::<Vec<u8>>(),
135 )?;
136 bench_sbf_end!("t_create_output_compressed_accounts");
137
138 bench_sbf_start!("t_add_token_data_to_input_compressed_accounts");
139 if !compressed_input_accounts.is_empty() {
140 add_token_data_to_input_compressed_accounts::<false>(
141 &mut compressed_input_accounts,
142 input_token_data.as_slice(),
143 &hashed_mint,
144 )?;
145 }
146 bench_sbf_end!("t_add_token_data_to_input_compressed_accounts");
147
148 let change_lamports = input_lamports - output_lamports;
151 if change_lamports > 0 {
152 let new_len = output_compressed_accounts.len() + 1;
153 output_compressed_accounts.resize(
156 new_len,
157 OutputCompressedAccountWithPackedContext {
158 compressed_account: CompressedAccount {
159 owner: ctx.accounts.authority.key(),
160 lamports: change_lamports,
161 data: None,
162 address: None,
163 },
164 merkle_tree_index: inputs.output_compressed_accounts[0].merkle_tree_index,
165 },
166 );
167 }
168
169 cpi_execute_compressed_transaction_transfer(
170 ctx.accounts,
171 compressed_input_accounts,
172 &output_compressed_accounts,
173 inputs.proof,
174 inputs.cpi_context,
175 ctx.accounts.cpi_authority_pda.to_account_info(),
176 ctx.accounts.light_system_program.to_account_info(),
177 ctx.accounts.self_program.to_account_info(),
178 ctx.remaining_accounts,
179 )
180}
181
182#[allow(clippy::too_many_arguments)]
189pub fn create_output_compressed_accounts(
190 output_compressed_accounts: &mut [OutputCompressedAccountWithPackedContext],
191 mint_pubkey: Pubkey,
192 pubkeys: &[Pubkey],
193 delegate: Option<Pubkey>,
194 is_delegate: Option<Vec<bool>>,
195 amounts: &[u64],
196 lamports: Option<Vec<Option<u64>>>,
197 hashed_mint: &[u8; 32],
198 merkle_tree_indices: &[u8],
199) -> Result<u64> {
200 let mut sum_lamports = 0;
201 let hashed_delegate_store = if let Some(delegate) = delegate {
202 hash_to_bn254_field_size_be(delegate.to_bytes().as_slice())
203 .unwrap()
204 .0
205 } else {
206 [0u8; 32]
207 };
208 for (i, (owner, amount)) in pubkeys.iter().zip(amounts.iter()).enumerate() {
209 let (delegate, hashed_delegate) = if is_delegate
210 .as_ref()
211 .map(|is_delegate| is_delegate[i])
212 .unwrap_or(false)
213 {
214 (
215 delegate.as_ref().map(|delegate_pubkey| *delegate_pubkey),
216 Some(&hashed_delegate_store),
217 )
218 } else {
219 (None, None)
220 };
221 let capacity = if delegate.is_some() { 107 } else { 75 };
229 let mut token_data_bytes = Vec::with_capacity(capacity);
230 let token_data = TokenData {
232 mint: mint_pubkey,
233 owner: *owner,
234 amount: *amount,
235 delegate,
236 state: AccountState::Initialized,
237 tlv: None,
238 };
239 token_data.serialize(&mut token_data_bytes).unwrap();
240 bench_sbf_start!("token_data_hash");
241 let hashed_owner = hash_to_bn254_field_size_be(owner.as_ref()).unwrap().0;
242 let amount_bytes = amount.to_le_bytes();
243 let data_hash = TokenData::hash_with_hashed_values::<Poseidon>(
244 hashed_mint,
245 &hashed_owner,
246 &amount_bytes,
247 &hashed_delegate,
248 )
249 .map_err(ProgramError::from)?;
250 let data: CompressedAccountData = CompressedAccountData {
251 discriminator: TOKEN_COMPRESSED_ACCOUNT_DISCRIMINATOR,
252 data: token_data_bytes,
253 data_hash,
254 };
255
256 bench_sbf_end!("token_data_hash");
257 let lamports = lamports
258 .as_ref()
259 .and_then(|lamports| lamports[i])
260 .unwrap_or(0);
261 sum_lamports += lamports;
262 output_compressed_accounts[i] = OutputCompressedAccountWithPackedContext {
263 compressed_account: CompressedAccount {
264 owner: crate::ID,
265 lamports,
266 data: Some(data),
267 address: None,
268 },
269 merkle_tree_index: merkle_tree_indices[i],
270 };
271 }
272 Ok(sum_lamports)
273}
274
275pub fn add_token_data_to_input_compressed_accounts<const FROZEN_INPUTS: bool>(
279 input_compressed_accounts_with_merkle_context: &mut [PackedCompressedAccountWithMerkleContext],
280 input_token_data: &[TokenData],
281 hashed_mint: &[u8; 32],
282) -> Result<()> {
283 for (i, compressed_account_with_context) in input_compressed_accounts_with_merkle_context
284 .iter_mut()
285 .enumerate()
286 {
287 let hashed_owner = hash_to_bn254_field_size_be(&input_token_data[i].owner.to_bytes())
288 .unwrap()
289 .0;
290 let mut data = Vec::new();
291 input_token_data[i].serialize(&mut data)?;
292 let amount = input_token_data[i].amount.to_le_bytes();
293 let delegate_store;
294 let hashed_delegate = if let Some(delegate) = input_token_data[i].delegate {
295 delegate_store = hash_to_bn254_field_size_be(&delegate.to_bytes()).unwrap().0;
296 Some(&delegate_store)
297 } else {
298 None
299 };
300 compressed_account_with_context.compressed_account.data = if !FROZEN_INPUTS {
301 Some(CompressedAccountData {
302 discriminator: TOKEN_COMPRESSED_ACCOUNT_DISCRIMINATOR,
303 data,
304 data_hash: TokenData::hash_with_hashed_values::<Poseidon>(
305 hashed_mint,
306 &hashed_owner,
307 &amount,
308 &hashed_delegate,
309 )
310 .map_err(ProgramError::from)?,
311 })
312 } else {
313 Some(CompressedAccountData {
314 discriminator: TOKEN_COMPRESSED_ACCOUNT_DISCRIMINATOR,
315 data,
316 data_hash: TokenData::hash_frozen_with_hashed_values::<Poseidon>(
317 hashed_mint,
318 &hashed_owner,
319 &amount,
320 &hashed_delegate,
321 )
322 .map_err(ProgramError::from)?,
323 })
324 };
325 }
326 Ok(())
327}
328
329pub fn get_cpi_signer_seeds() -> [&'static [u8]; 2] {
331 let bump: &[u8; 1] = &[BUMP_CPI_AUTHORITY];
332 let seeds: [&'static [u8]; 2] = [CPI_AUTHORITY_PDA_SEED, bump];
333 seeds
334}
335
336#[inline(never)]
337#[allow(clippy::too_many_arguments)]
338pub fn cpi_execute_compressed_transaction_transfer<
339 'info,
340 A: InvokeAccounts<'info> + SignerAccounts<'info>,
341>(
342 ctx: &A,
343 input_compressed_accounts_with_merkle_context: Vec<PackedCompressedAccountWithMerkleContext>,
344 output_compressed_accounts: &[OutputCompressedAccountWithPackedContext],
345 proof: Option<CompressedProof>,
346 cpi_context: Option<CompressedCpiContext>,
347 cpi_authority_pda: AccountInfo<'info>,
348 system_program_account_info: AccountInfo<'info>,
349 invoking_program_account_info: AccountInfo<'info>,
350 remaining_accounts: &[AccountInfo<'info>],
351) -> Result<()> {
352 bench_sbf_start!("t_cpi_prep");
353
354 let signer_seeds = get_cpi_signer_seeds();
355 let signer_seeds_ref = &[&signer_seeds[..]];
356
357 let cpi_context_account = cpi_context.map(|cpi_context| {
358 remaining_accounts[cpi_context.cpi_context_account_index as usize].to_account_info()
359 });
360 let inputs_struct = light_system_program::invoke_cpi::instruction::InstructionDataInvokeCpi {
361 relay_fee: None,
362 input_compressed_accounts_with_merkle_context,
363 output_compressed_accounts: output_compressed_accounts.to_vec(),
364 proof,
365 new_address_params: Vec::new(),
366 compress_or_decompress_lamports: None,
367 is_compress: false,
368 cpi_context,
369 };
370 let mut inputs = Vec::new();
371 InstructionDataInvokeCpi::serialize(&inputs_struct, &mut inputs).map_err(ProgramError::from)?;
372
373 let cpi_accounts = light_system_program::cpi::accounts::InvokeCpiInstruction {
374 fee_payer: ctx.get_fee_payer().to_account_info(),
375 authority: cpi_authority_pda,
376 registered_program_pda: ctx.get_registered_program_pda().to_account_info(),
377 noop_program: ctx.get_noop_program().to_account_info(),
378 account_compression_authority: ctx.get_account_compression_authority().to_account_info(),
379 account_compression_program: ctx.get_account_compression_program().to_account_info(),
380 invoking_program: invoking_program_account_info,
381 system_program: ctx.get_system_program().to_account_info(),
382 sol_pool_pda: None,
383 decompression_recipient: None,
384 cpi_context_account,
385 };
386 let mut cpi_ctx =
387 CpiContext::new_with_signer(system_program_account_info, cpi_accounts, signer_seeds_ref);
388
389 cpi_ctx.remaining_accounts = remaining_accounts.to_vec();
390 bench_sbf_end!("t_cpi_prep");
391
392 bench_sbf_start!("t_invoke_cpi");
393 light_system_program::cpi::invoke_cpi(cpi_ctx, inputs)?;
394 bench_sbf_end!("t_invoke_cpi");
395
396 Ok(())
397}
398
399pub fn sum_check(
400 input_token_data_elements: &[TokenData],
401 output_amounts: &[u64],
402 compress_or_decompress_amount: Option<&u64>,
403 is_compress: bool,
404) -> Result<()> {
405 let mut sum: u64 = 0;
406 for input_token_data in input_token_data_elements.iter() {
407 sum = sum
408 .checked_add(input_token_data.amount)
409 .ok_or(ProgramError::ArithmeticOverflow)
410 .map_err(|_| ErrorCode::ComputeInputSumFailed)?;
411 }
412
413 if let Some(compress_or_decompress_amount) = compress_or_decompress_amount {
414 if is_compress {
415 sum = sum
416 .checked_add(*compress_or_decompress_amount)
417 .ok_or(ProgramError::ArithmeticOverflow)
418 .map_err(|_| ErrorCode::ComputeCompressSumFailed)?;
419 } else {
420 sum = sum
421 .checked_sub(*compress_or_decompress_amount)
422 .ok_or(ProgramError::ArithmeticOverflow)
423 .map_err(|_| ErrorCode::ComputeDecompressSumFailed)?;
424 }
425 }
426
427 for amount in output_amounts.iter() {
428 sum = sum
429 .checked_sub(*amount)
430 .ok_or(ProgramError::ArithmeticOverflow)
431 .map_err(|_| ErrorCode::ComputeOutputSumFailed)?;
432 }
433
434 if sum == 0 {
435 Ok(())
436 } else {
437 Err(ErrorCode::SumCheckFailed.into())
438 }
439}
440
441#[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize)]
442pub struct InputTokenDataWithContext {
443 pub amount: u64,
444 pub delegate_index: Option<u8>,
445 pub merkle_context: PackedMerkleContext,
446 pub root_index: u16,
447 pub lamports: Option<u64>,
448 pub tlv: Option<Vec<u8>>,
450}
451
452#[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize)]
454pub struct DelegatedTransfer {
455 pub owner: Pubkey,
456 pub delegate_change_account_index: Option<u8>,
461}
462
463#[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize)]
464pub struct CompressedTokenInstructionDataTransfer {
465 pub proof: Option<CompressedProof>,
466 pub mint: Pubkey,
467 pub delegated_transfer: Option<DelegatedTransfer>,
471 pub input_token_data_with_context: Vec<InputTokenDataWithContext>,
472 pub output_compressed_accounts: Vec<PackedTokenTransferOutputData>,
473 pub is_compress: bool,
474 pub compress_or_decompress_amount: Option<u64>,
475 pub cpi_context: Option<CompressedCpiContext>,
476 pub lamports_change_account_merkle_tree_index: Option<u8>,
477}
478
479pub fn get_input_compressed_accounts_with_merkle_context_and_check_signer<const IS_FROZEN: bool>(
480 signer: &Pubkey,
481 signer_is_delegate: &Option<DelegatedTransfer>,
482 remaining_accounts: &[AccountInfo<'_>],
483 input_token_data_with_context: &[InputTokenDataWithContext],
484 mint: &Pubkey,
485) -> Result<(
486 Vec<PackedCompressedAccountWithMerkleContext>,
487 Vec<TokenData>,
488 u64,
489)> {
490 let mut sum_lamports = 0;
494 let mut input_compressed_accounts_with_merkle_context: Vec<
495 PackedCompressedAccountWithMerkleContext,
496 > = Vec::<PackedCompressedAccountWithMerkleContext>::with_capacity(
497 input_token_data_with_context.len(),
498 );
499 let mut input_token_data_vec: Vec<TokenData> =
500 Vec::with_capacity(input_token_data_with_context.len());
501
502 for input_token_data in input_token_data_with_context.iter() {
503 let owner = if input_token_data.delegate_index.is_none() {
504 *signer
505 } else if let Some(signer_is_delegate) = signer_is_delegate {
506 signer_is_delegate.owner
507 } else {
508 *signer
509 };
510 if signer_is_delegate.is_some()
513 && input_token_data.delegate_index.is_some()
514 && *signer
515 != remaining_accounts[input_token_data.delegate_index.unwrap() as usize].key()
516 {
517 msg!(
518 "signer {:?} != delegate in remaining accounts {:?}",
519 signer,
520 remaining_accounts[input_token_data.delegate_index.unwrap() as usize].key()
521 );
522 msg!(
523 "delegate index {:?}",
524 input_token_data.delegate_index.unwrap() as usize
525 );
526 return err!(ErrorCode::DelegateSignerCheckFailed);
527 }
528
529 let compressed_account = CompressedAccount {
530 owner: crate::ID,
531 lamports: input_token_data.lamports.unwrap_or_default(),
532 data: None,
533 address: None,
534 };
535 sum_lamports += compressed_account.lamports;
536 let state = if IS_FROZEN {
537 AccountState::Frozen
538 } else {
539 AccountState::Initialized
540 };
541 if input_token_data.tlv.is_some() {
542 unimplemented!("Tlv is unimplemented.");
543 }
544 let token_data = TokenData {
545 mint: *mint,
546 owner,
547 amount: input_token_data.amount,
548 delegate: input_token_data.delegate_index.map(|_| {
549 remaining_accounts[input_token_data.delegate_index.unwrap() as usize].key()
550 }),
551 state,
552 tlv: None,
553 };
554 input_token_data_vec.push(token_data);
555 input_compressed_accounts_with_merkle_context.push(
556 PackedCompressedAccountWithMerkleContext {
557 compressed_account,
558 merkle_context: input_token_data.merkle_context,
559 root_index: input_token_data.root_index,
560 read_only: false,
561 },
562 );
563 }
564 Ok((
565 input_compressed_accounts_with_merkle_context,
566 input_token_data_vec,
567 sum_lamports,
568 ))
569}
570
571#[derive(Clone, Debug, PartialEq, Eq, AnchorSerialize, AnchorDeserialize)]
572pub struct PackedTokenTransferOutputData {
573 pub owner: Pubkey,
574 pub amount: u64,
575 pub lamports: Option<u64>,
576 pub merkle_tree_index: u8,
577 pub tlv: Option<Vec<u8>>,
579}
580
581#[derive(Clone, Copy, Debug, PartialEq, Eq, AnchorSerialize, AnchorDeserialize)]
582pub struct TokenTransferOutputData {
583 pub owner: Pubkey,
584 pub amount: u64,
585 pub lamports: Option<u64>,
586 pub merkle_tree: Pubkey,
587}
588
589pub fn get_cpi_authority_pda() -> (Pubkey, u8) {
590 Pubkey::find_program_address(&[CPI_AUTHORITY_PDA_SEED], &crate::ID)
591}
592
593#[cfg(not(target_os = "solana"))]
594pub mod transfer_sdk {
595 use std::collections::HashMap;
596
597 use anchor_lang::{AnchorSerialize, Id, InstructionData, ToAccountMetas};
598 use anchor_spl::token::Token;
599 use light_system_program::{
600 invoke::processor::CompressedProof,
601 sdk::compressed_account::{CompressedAccount, MerkleContext, PackedMerkleContext},
602 };
603 use solana_sdk::{
604 instruction::{AccountMeta, Instruction},
605 pubkey::Pubkey,
606 };
607
608 use crate::{token_data::TokenData, CompressedTokenInstructionDataTransfer};
609 use anchor_lang::error_code;
610
611 use super::{
612 DelegatedTransfer, InputTokenDataWithContext, PackedTokenTransferOutputData,
613 TokenTransferOutputData,
614 };
615
616 #[error_code]
617 pub enum TransferSdkError {
618 #[msg("Signer check failed")]
619 SignerCheckFailed,
620 #[msg("Create transfer instruction failed")]
621 CreateTransferInstructionFailed,
622 #[msg("Account not found")]
623 AccountNotFound,
624 #[msg("Serialization error")]
625 SerializationError,
626 }
627
628 #[allow(clippy::too_many_arguments)]
629 pub fn create_transfer_instruction(
630 fee_payer: &Pubkey,
631 owner: &Pubkey,
632 input_merkle_context: &[MerkleContext],
633 output_compressed_accounts: &[TokenTransferOutputData],
634 root_indices: &[u16],
635 proof: &Option<CompressedProof>,
636 input_token_data: &[TokenData],
637 input_compressed_accounts: &[CompressedAccount],
638 mint: Pubkey,
639 delegate: Option<Pubkey>,
640 is_compress: bool,
641 compress_or_decompress_amount: Option<u64>,
642 token_pool_pda: Option<Pubkey>,
643 compress_or_decompress_token_account: Option<Pubkey>,
644 sort: bool,
645 delegate_change_account_index: Option<u8>,
646 lamports_change_account_merkle_tree: Option<Pubkey>,
647 ) -> Result<Instruction, TransferSdkError> {
648 let (remaining_accounts, mut inputs_struct) = create_inputs_and_remaining_accounts(
649 input_token_data,
650 input_compressed_accounts,
651 input_merkle_context,
652 delegate,
653 output_compressed_accounts,
654 root_indices,
655 proof,
656 mint,
657 is_compress,
658 compress_or_decompress_amount,
659 delegate_change_account_index,
660 lamports_change_account_merkle_tree,
661 );
662 if sort {
663 inputs_struct
664 .output_compressed_accounts
665 .sort_by_key(|data| data.merkle_tree_index);
666 }
667 let remaining_accounts = to_account_metas(remaining_accounts);
668 let mut inputs = Vec::new();
669 CompressedTokenInstructionDataTransfer::serialize(&inputs_struct, &mut inputs)
670 .map_err(|_| TransferSdkError::SerializationError)?;
671
672 let (cpi_authority_pda, _) = crate::process_transfer::get_cpi_authority_pda();
673 let instruction_data = crate::instruction::Transfer { inputs };
674 let authority = if let Some(delegate) = delegate {
675 delegate
676 } else {
677 *owner
678 };
679
680 let accounts = crate::accounts::TransferInstruction {
681 fee_payer: *fee_payer,
682 authority,
683 cpi_authority_pda,
684 light_system_program: light_system_program::ID,
685 registered_program_pda: light_system_program::utils::get_registered_program_pda(
686 &light_system_program::ID,
687 ),
688 noop_program: Pubkey::new_from_array(
689 account_compression::utils::constants::NOOP_PUBKEY,
690 ),
691 account_compression_authority: light_system_program::utils::get_cpi_authority_pda(
692 &light_system_program::ID,
693 ),
694 account_compression_program: account_compression::ID,
695 self_program: crate::ID,
696 token_pool_pda,
697 compress_or_decompress_token_account,
698 token_program: token_pool_pda.map(|_| Token::id()),
699 system_program: solana_sdk::system_program::ID,
700 };
701
702 Ok(Instruction {
703 program_id: crate::ID,
704 accounts: [accounts.to_account_metas(Some(true)), remaining_accounts].concat(),
705
706 data: instruction_data.data(),
707 })
708 }
709
710 #[allow(clippy::too_many_arguments)]
711 pub fn create_inputs_and_remaining_accounts_checked(
712 input_token_data: &[TokenData],
713 input_compressed_accounts: &[CompressedAccount],
714 input_merkle_context: &[MerkleContext],
715 owner_if_delegate_is_signer: Option<Pubkey>,
716 output_compressed_accounts: &[TokenTransferOutputData],
717 root_indices: &[u16],
718 proof: &Option<CompressedProof>,
719 mint: Pubkey,
720 owner: &Pubkey,
721 is_compress: bool,
722 compress_or_decompress_amount: Option<u64>,
723 delegate_change_account_index: Option<u8>,
724 lamports_change_account_merkle_tree: Option<Pubkey>,
725 ) -> Result<
726 (
727 HashMap<Pubkey, usize>,
728 CompressedTokenInstructionDataTransfer,
729 ),
730 TransferSdkError,
731 > {
732 for token_data in input_token_data {
733 if token_data.owner != *owner {
735 println!(
736 "owner: {:?}, token_data.owner: {:?}",
737 owner, token_data.owner
738 );
739 return Err(TransferSdkError::SignerCheckFailed);
740 }
741 }
742 let (remaining_accounts, compressed_accounts_ix_data) =
743 create_inputs_and_remaining_accounts(
744 input_token_data,
745 input_compressed_accounts,
746 input_merkle_context,
747 owner_if_delegate_is_signer,
748 output_compressed_accounts,
749 root_indices,
750 proof,
751 mint,
752 is_compress,
753 compress_or_decompress_amount,
754 delegate_change_account_index,
755 lamports_change_account_merkle_tree,
756 );
757 Ok((remaining_accounts, compressed_accounts_ix_data))
758 }
759
760 #[allow(clippy::too_many_arguments)]
761 pub fn create_inputs_and_remaining_accounts(
762 input_token_data: &[TokenData],
763 input_compressed_accounts: &[CompressedAccount],
764 input_merkle_context: &[MerkleContext],
765 delegate: Option<Pubkey>,
766 output_compressed_accounts: &[TokenTransferOutputData],
767 root_indices: &[u16],
768 proof: &Option<CompressedProof>,
769 mint: Pubkey,
770 is_compress: bool,
771 compress_or_decompress_amount: Option<u64>,
772 delegate_change_account_index: Option<u8>,
773 lamports_change_account_merkle_tree: Option<Pubkey>,
774 ) -> (
775 HashMap<Pubkey, usize>,
776 CompressedTokenInstructionDataTransfer,
777 ) {
778 let mut additonal_accounts = Vec::new();
779 if let Some(delegate) = delegate {
780 additonal_accounts.push(delegate);
781 for account in input_token_data.iter() {
782 if account.delegate.is_some() && delegate != account.delegate.unwrap() {
783 println!("delegate: {:?}", delegate);
784 println!("account.delegate: {:?}", account.delegate.unwrap());
785 panic!("Delegate is not the same as the signer");
786 }
787 }
788 }
789 let lamports_change_account_merkle_tree_index = if let Some(
790 lamports_change_account_merkle_tree,
791 ) = lamports_change_account_merkle_tree
792 {
793 additonal_accounts.push(lamports_change_account_merkle_tree);
794 Some(additonal_accounts.len() as u8 - 1)
795 } else {
796 None
797 };
798 let (remaining_accounts, input_token_data_with_context, _output_compressed_accounts) =
799 create_input_output_and_remaining_accounts(
800 additonal_accounts.as_slice(),
801 input_token_data,
802 input_compressed_accounts,
803 input_merkle_context,
804 root_indices,
805 output_compressed_accounts,
806 );
807 let delegated_transfer = if delegate.is_some() {
808 let delegated_transfer = DelegatedTransfer {
809 owner: input_token_data[0].owner,
810 delegate_change_account_index,
811 };
812 Some(delegated_transfer)
813 } else {
814 None
815 };
816 let inputs_struct = CompressedTokenInstructionDataTransfer {
817 output_compressed_accounts: _output_compressed_accounts.to_vec(),
818 proof: proof.clone(),
819 input_token_data_with_context,
820 delegated_transfer,
821 mint,
822 is_compress,
823 compress_or_decompress_amount,
824 cpi_context: None,
825 lamports_change_account_merkle_tree_index,
826 };
827
828 (remaining_accounts, inputs_struct)
829 }
830
831 pub fn create_input_output_and_remaining_accounts(
832 additional_accounts: &[Pubkey],
833 input_token_data: &[TokenData],
834 input_compressed_accounts: &[CompressedAccount],
835 input_merkle_context: &[MerkleContext],
836 root_indices: &[u16],
837 output_compressed_accounts: &[TokenTransferOutputData],
838 ) -> (
839 HashMap<Pubkey, usize>,
840 Vec<InputTokenDataWithContext>,
841 Vec<PackedTokenTransferOutputData>,
842 ) {
843 let mut remaining_accounts = HashMap::<Pubkey, usize>::new();
844
845 let mut index = 0;
846 for account in additional_accounts {
847 match remaining_accounts.get(account) {
848 Some(_) => {}
849 None => {
850 remaining_accounts.insert(*account, index);
851 index += 1;
852 }
853 };
854 }
855 let mut input_token_data_with_context: Vec<InputTokenDataWithContext> = Vec::new();
856
857 for (i, token_data) in input_token_data.iter().enumerate() {
858 match remaining_accounts.get(&input_merkle_context[i].merkle_tree_pubkey) {
859 Some(_) => {}
860 None => {
861 remaining_accounts.insert(input_merkle_context[i].merkle_tree_pubkey, index);
862 index += 1;
863 }
864 };
865 let delegate_index = match token_data.delegate {
866 Some(delegate) => match remaining_accounts.get(&delegate) {
867 Some(delegate_index) => Some(*delegate_index as u8),
868 None => {
869 remaining_accounts.insert(delegate, index);
870 index += 1;
871 Some((index - 1) as u8)
872 }
873 },
874 None => None,
875 };
876 let lamports = if input_compressed_accounts[i].lamports != 0 {
877 Some(input_compressed_accounts[i].lamports)
878 } else {
879 None
880 };
881 let token_data_with_context = InputTokenDataWithContext {
882 amount: token_data.amount,
883 delegate_index,
884 merkle_context: PackedMerkleContext {
885 merkle_tree_pubkey_index: *remaining_accounts
886 .get(&input_merkle_context[i].merkle_tree_pubkey)
887 .unwrap() as u8,
888 nullifier_queue_pubkey_index: 0,
889 leaf_index: input_merkle_context[i].leaf_index,
890 queue_index: None,
891 },
892 root_index: root_indices[i],
893 lamports,
894 tlv: None,
895 };
896 input_token_data_with_context.push(token_data_with_context);
897 }
898 for (i, _) in input_token_data.iter().enumerate() {
899 match remaining_accounts.get(&input_merkle_context[i].nullifier_queue_pubkey) {
900 Some(_) => {}
901 None => {
902 remaining_accounts
903 .insert(input_merkle_context[i].nullifier_queue_pubkey, index);
904 index += 1;
905 }
906 };
907 input_token_data_with_context[i]
908 .merkle_context
909 .nullifier_queue_pubkey_index = *remaining_accounts
910 .get(&input_merkle_context[i].nullifier_queue_pubkey)
911 .unwrap() as u8;
912 }
913 let mut _output_compressed_accounts: Vec<PackedTokenTransferOutputData> =
914 Vec::with_capacity(output_compressed_accounts.len());
915 for (i, mt) in output_compressed_accounts.iter().enumerate() {
916 match remaining_accounts.get(&mt.merkle_tree) {
917 Some(_) => {}
918 None => {
919 remaining_accounts.insert(mt.merkle_tree, index);
920 index += 1;
921 }
922 };
923 _output_compressed_accounts.push(PackedTokenTransferOutputData {
924 owner: output_compressed_accounts[i].owner,
925 amount: output_compressed_accounts[i].amount,
926 lamports: output_compressed_accounts[i].lamports,
927 merkle_tree_index: *remaining_accounts.get(&mt.merkle_tree).unwrap() as u8,
928 tlv: None,
929 });
930 }
931 (
932 remaining_accounts,
933 input_token_data_with_context,
934 _output_compressed_accounts,
935 )
936 }
937
938 pub fn to_account_metas(remaining_accounts: HashMap<Pubkey, usize>) -> Vec<AccountMeta> {
939 let mut remaining_accounts = remaining_accounts
940 .iter()
941 .map(|(k, i)| {
942 (
943 AccountMeta {
944 pubkey: *k,
945 is_signer: false,
946 is_writable: true,
947 },
948 *i,
949 )
950 })
951 .collect::<Vec<(AccountMeta, usize)>>();
952 remaining_accounts.sort_by(|a, b| a.1.cmp(&b.1));
954 let remaining_accounts = remaining_accounts
955 .iter()
956 .map(|(k, _)| k.clone())
957 .collect::<Vec<AccountMeta>>();
958 remaining_accounts
959 }
960}
961
962#[cfg(test)]
963mod test {
964 use crate::token_data::AccountState;
965
966 use super::*;
967
968 #[test]
969 fn test_sum_check() {
970 sum_check_test(&[100, 50], &[150], None, false).unwrap();
972 sum_check_test(&[75, 25, 25], &[25, 25, 25, 25, 12, 13], None, false).unwrap();
973
974 sum_check_test(&[100, 50], &[150 + 1], None, false).unwrap_err();
976 sum_check_test(&[100, 50], &[150 - 1], None, false).unwrap_err();
977 sum_check_test(&[100, 50], &[], None, false).unwrap_err();
978 sum_check_test(&[], &[100, 50], None, false).unwrap_err();
979
980 sum_check_test(&[], &[], None, true).unwrap();
982 sum_check_test(&[], &[], None, false).unwrap();
983 sum_check_test(&[], &[], Some(1), false).unwrap_err();
985 sum_check_test(&[], &[], Some(1), true).unwrap_err();
986
987 sum_check_test(&[100], &[123], Some(23), true).unwrap();
989 sum_check_test(&[], &[150], Some(150), true).unwrap();
990 sum_check_test(&[], &[150], Some(150 - 1), true).unwrap_err();
992 sum_check_test(&[], &[150], Some(150 + 1), true).unwrap_err();
993
994 sum_check_test(&[100, 50], &[100], Some(50), false).unwrap();
996 sum_check_test(&[100, 50], &[], Some(150), false).unwrap();
997 sum_check_test(&[100, 50], &[], Some(150 - 1), false).unwrap_err();
999 sum_check_test(&[100, 50], &[], Some(150 + 1), false).unwrap_err();
1000 }
1001
1002 fn sum_check_test(
1003 input_amounts: &[u64],
1004 output_amounts: &[u64],
1005 compress_or_decompress_amount: Option<u64>,
1006 is_compress: bool,
1007 ) -> Result<()> {
1008 let mut inputs = Vec::new();
1009 for i in input_amounts.iter() {
1010 inputs.push(TokenData {
1011 mint: Pubkey::new_unique(),
1012 owner: Pubkey::new_unique(),
1013 delegate: None,
1014 state: AccountState::Initialized,
1015 amount: *i,
1016 tlv: None,
1017 });
1018 }
1019 let ref_amount;
1020 let compress_or_decompress_amount = match compress_or_decompress_amount {
1021 Some(amount) => {
1022 ref_amount = amount;
1023 Some(&ref_amount)
1024 }
1025 None => None,
1026 };
1027 sum_check(
1028 inputs.as_slice(),
1029 &output_amounts,
1030 compress_or_decompress_amount,
1031 is_compress,
1032 )
1033 }
1034}