1use account_compression::program::AccountCompression;
2use anchor_lang::prelude::*;
3use anchor_spl::token_interface::{TokenAccount, TokenInterface};
4use light_compressed_account::{
5 instruction_data::data::OutputCompressedAccountWithPackedContext, pubkey::AsPubkey,
6};
7use light_system_program::program::LightSystemProgram;
8use light_zero_copy::num_trait::ZeroCopyNumTrait;
9#[cfg(target_os = "solana")]
10use {
11 crate::{
12 check_spl_token_pool_derivation_with_index,
13 process_transfer::create_output_compressed_accounts,
14 process_transfer::get_cpi_signer_seeds, spl_compression::spl_token_transfer,
15 },
16 light_compressed_account::hash_to_bn254_field_size_be,
17 light_heap::{bench_sbf_end, bench_sbf_start, GLOBAL_ALLOCATOR},
18};
19
20use crate::{check_spl_token_pool_derivation, program::LightCompressedToken};
21
22pub const COMPRESS: bool = false;
23pub const MINT_TO: bool = true;
24
25#[allow(unused_variables)]
38pub fn process_mint_to_or_compress<'info, const IS_MINT_TO: bool>(
39 ctx: Context<'_, '_, '_, 'info, MintToInstruction<'info>>,
40 recipient_pubkeys: &[impl AsPubkey],
41 amounts: &[impl ZeroCopyNumTrait],
42 lamports: Option<u64>,
43 index: Option<u8>,
44 bump: Option<u8>,
45) -> Result<()> {
46 if recipient_pubkeys.len() != amounts.len() {
47 msg!(
48 "recipient_pubkeys.len() {} != {} amounts.len()",
49 recipient_pubkeys.len(),
50 amounts.len()
51 );
52 return err!(crate::ErrorCode::PublicKeyAmountMissmatch);
53 } else if recipient_pubkeys.is_empty() {
54 msg!("recipient_pubkeys is empty");
55 return err!(crate::ErrorCode::NoInputsProvided);
56 }
57
58 #[cfg(target_os = "solana")]
59 {
60 let option_compression_lamports = if lamports.unwrap_or(0) == 0 { 0 } else { 8 };
61 let inputs_len =
62 1 + 4 + 4 + 4 + amounts.len() * 162 + 1 + 1 + 1 + 1 + option_compression_lamports;
63 let mut inputs = Vec::<u8>::with_capacity(inputs_len);
73 let pre_compressed_acounts_pos = GLOBAL_ALLOCATOR.get_heap_pos();
76 bench_sbf_start!("tm_mint_spl_to_pool_pda");
77
78 let mint = if IS_MINT_TO {
79 mint_spl_to_pool_pda(&ctx, &amounts)?;
81 ctx.accounts.mint.as_ref().unwrap().key()
82 } else {
83 let mut amount = 0u64;
84 for a in amounts {
85 amount += (*a).into();
86 }
87 let index = index.unwrap();
89 let from_account_info = &ctx.remaining_accounts[0];
90
91 let mint =
92 Pubkey::new_from_array(from_account_info.data.borrow()[..32].try_into().unwrap());
93 check_spl_token_pool_derivation_with_index(
94 &ctx.accounts.token_pool_pda.key(),
95 &mint,
96 index,
97 bump,
98 )?;
99 spl_token_transfer(
100 from_account_info.to_account_info(),
101 ctx.accounts.token_pool_pda.to_account_info(),
102 ctx.accounts.authority.to_account_info(),
103 ctx.accounts.token_program.to_account_info(),
104 amount,
105 )?;
106 mint
107 };
108 let hashed_mint = hash_to_bn254_field_size_be(mint.as_ref());
109
110 let mut output_compressed_accounts =
111 vec![OutputCompressedAccountWithPackedContext::default(); recipient_pubkeys.len()];
112 let lamports_vec = lamports.map(|_| vec![lamports; amounts.len()]);
113 create_output_compressed_accounts(
114 &mut output_compressed_accounts,
115 mint,
116 recipient_pubkeys,
117 None,
118 None,
119 &amounts,
120 lamports_vec,
121 &hashed_mint,
122 &vec![0; amounts.len()],
125 &[ctx.accounts.merkle_tree.to_account_info()],
126 )?;
127 bench_sbf_end!("tm_output_compressed_accounts");
128
129 cpi_execute_compressed_transaction_mint_to(
130 &ctx,
131 output_compressed_accounts,
132 &mut inputs,
133 pre_compressed_acounts_pos,
134 )?;
135
136 if inputs.len() != inputs_len {
139 msg!(
140 "Used memory {} is unequal allocated {} memory",
141 inputs.len(),
142 inputs_len
143 );
144 return err!(crate::ErrorCode::HeapMemoryCheckFailed);
145 }
146 }
147 Ok(())
148}
149
150#[cfg(target_os = "solana")]
151#[inline(never)]
152pub fn cpi_execute_compressed_transaction_mint_to<'info>(
153 ctx: &Context<'_, '_, '_, 'info, MintToInstruction>,
154 output_compressed_accounts: Vec<OutputCompressedAccountWithPackedContext>,
155 inputs: &mut Vec<u8>,
156 pre_compressed_acounts_pos: usize,
157) -> Result<()> {
158 bench_sbf_start!("tm_cpi");
159
160 let signer_seeds = get_cpi_signer_seeds();
161
162 serialize_mint_to_cpi_instruction_data(inputs, &output_compressed_accounts);
166
167 GLOBAL_ALLOCATOR.free_heap(pre_compressed_acounts_pos)?;
168
169 use anchor_lang::InstructionData;
170
171 let instructiondata = light_system_program::instruction::InvokeCpi {
173 inputs: inputs.to_owned(),
174 };
175 let (sol_pool_pda, is_writable) = if let Some(pool_pda) = ctx.accounts.sol_pool_pda.as_ref() {
176 (pool_pda.to_account_info(), true)
178 } else {
179 (ctx.accounts.light_system_program.to_account_info(), false)
181 };
182
183 let account_infos = vec![
185 ctx.accounts.fee_payer.to_account_info(),
186 ctx.accounts.cpi_authority_pda.to_account_info(),
187 ctx.accounts.registered_program_pda.to_account_info(),
188 ctx.accounts.noop_program.to_account_info(),
189 ctx.accounts.account_compression_authority.to_account_info(),
190 ctx.accounts.account_compression_program.to_account_info(),
191 ctx.accounts.self_program.to_account_info(),
192 sol_pool_pda,
193 ctx.accounts.light_system_program.to_account_info(), ctx.accounts.system_program.to_account_info(),
195 ctx.accounts.light_system_program.to_account_info(), ctx.accounts.merkle_tree.to_account_info(), ];
198
199 let accounts = vec![
201 AccountMeta {
202 pubkey: account_infos[0].key(),
203 is_signer: true,
204 is_writable: true,
205 },
206 AccountMeta {
207 pubkey: account_infos[1].key(),
208 is_signer: true,
209 is_writable: false,
210 },
211 AccountMeta {
212 pubkey: account_infos[2].key(),
213 is_signer: false,
214 is_writable: false,
215 },
216 AccountMeta {
217 pubkey: account_infos[3].key(),
218 is_signer: false,
219 is_writable: false,
220 },
221 AccountMeta {
222 pubkey: account_infos[4].key(),
223 is_signer: false,
224 is_writable: false,
225 },
226 AccountMeta {
227 pubkey: account_infos[5].key(),
228 is_signer: false,
229 is_writable: false,
230 },
231 AccountMeta {
232 pubkey: account_infos[6].key(),
233 is_signer: false,
234 is_writable: false,
235 },
236 AccountMeta {
237 pubkey: account_infos[7].key(),
238 is_signer: false,
239 is_writable,
240 },
241 AccountMeta {
242 pubkey: account_infos[8].key(),
243 is_signer: false,
244 is_writable: false,
245 },
246 AccountMeta::new_readonly(account_infos[9].key(), false),
247 AccountMeta {
248 pubkey: account_infos[10].key(),
249 is_signer: false,
250 is_writable: false,
251 },
252 AccountMeta {
253 pubkey: account_infos[11].key(),
254 is_signer: false,
255 is_writable: true,
256 },
257 ];
258
259 let instruction = anchor_lang::solana_program::instruction::Instruction {
260 program_id: light_system_program::ID,
261 accounts,
262 data: instructiondata.data(),
263 };
264
265 bench_sbf_end!("tm_cpi");
266 bench_sbf_start!("tm_invoke");
267 anchor_lang::solana_program::program::invoke_signed(
268 &instruction,
269 account_infos.as_slice(),
270 &[&signer_seeds[..]],
271 )?;
272 bench_sbf_end!("tm_invoke");
273 Ok(())
274}
275
276#[inline(never)]
277pub fn serialize_mint_to_cpi_instruction_data(
278 inputs: &mut Vec<u8>,
279 output_compressed_accounts: &[OutputCompressedAccountWithPackedContext],
280) {
281 let len = output_compressed_accounts.len();
282 inputs.extend_from_slice(&[0u8]);
284 inputs.extend_from_slice(&[0u8; 8]);
287 inputs.extend_from_slice(&[(len as u8), 0, 0, 0]);
289 let mut sum_lamports = 0u64;
290 for compressed_account in output_compressed_accounts.iter() {
292 compressed_account.serialize(inputs).unwrap();
293 sum_lamports = sum_lamports
294 .checked_add(compressed_account.compressed_account.lamports)
295 .unwrap();
296 }
297 inputs.extend_from_slice(&[0u8; 1]);
299
300 if sum_lamports != 0 {
301 inputs.extend_from_slice(&[1u8; 1]);
302 inputs.extend_from_slice(&sum_lamports.to_le_bytes());
303 inputs.extend_from_slice(&[1u8; 1]); } else {
305 inputs.extend_from_slice(&[0u8; 2]); }
307
308 inputs.extend_from_slice(&[0u8]);
310}
311
312#[inline(never)]
313pub fn mint_spl_to_pool_pda(
314 ctx: &Context<MintToInstruction>,
315 amounts: &[impl ZeroCopyNumTrait],
316) -> Result<()> {
317 check_spl_token_pool_derivation(
318 &ctx.accounts.token_pool_pda.key(),
319 &ctx.accounts.mint.as_ref().unwrap().key(),
320 )?;
321 let mut mint_amount: u64 = 0;
322 for amount in amounts.iter() {
323 mint_amount = mint_amount
324 .checked_add((*amount).into())
325 .ok_or(crate::ErrorCode::MintTooLarge)?;
326 }
327
328 let pre_token_balance = TokenAccount::try_deserialize(
329 &mut &ctx.accounts.token_pool_pda.to_account_info().data.borrow()[..],
330 )?
331 .amount;
332 let cpi_accounts = anchor_spl::token_interface::MintTo {
333 mint: ctx.accounts.mint.as_ref().unwrap().to_account_info(),
334 to: ctx.accounts.token_pool_pda.to_account_info(),
335 authority: ctx.accounts.authority.to_account_info(),
336 };
337
338 let cpi_ctx = CpiContext::new(ctx.accounts.token_program.to_account_info(), cpi_accounts);
339 anchor_spl::token_interface::mint_to(cpi_ctx, mint_amount)?;
340
341 let post_token_balance = TokenAccount::try_deserialize(
342 &mut &ctx.accounts.token_pool_pda.to_account_info().data.borrow()[..],
343 )?
344 .amount;
345 if post_token_balance != pre_token_balance + mint_amount {
347 msg!(
348 "post_token_balance {} != pre_token_balance {} + mint_amount {}",
349 post_token_balance,
350 pre_token_balance,
351 mint_amount
352 );
353 return err!(crate::ErrorCode::SplTokenSupplyMismatch);
354 }
355 Ok(())
356}
357
358#[derive(Accounts)]
359pub struct MintToInstruction<'info> {
360 #[account(mut)]
362 pub fee_payer: Signer<'info>,
363 pub authority: Signer<'info>,
365 pub cpi_authority_pda: UncheckedAccount<'info>,
367 #[account(mut)]
369 pub mint: Option<UncheckedAccount<'info>>,
370 #[account(mut)]
372 pub token_pool_pda: UncheckedAccount<'info>,
373 pub token_program: Interface<'info, TokenInterface>,
374 pub light_system_program: Program<'info, LightSystemProgram>,
375 pub registered_program_pda: UncheckedAccount<'info>,
377 pub noop_program: UncheckedAccount<'info>,
380 pub account_compression_authority: UncheckedAccount<'info>,
382 pub account_compression_program: Program<'info, AccountCompression>,
384 #[account(mut)]
386 pub merkle_tree: UncheckedAccount<'info>,
387 pub self_program: Program<'info, LightCompressedToken>,
389 pub system_program: Program<'info, System>,
390 #[account(mut)]
392 pub sol_pool_pda: Option<AccountInfo<'info>>,
393}
394
395#[cfg(not(target_os = "solana"))]
396pub mod mint_sdk {
397 use anchor_lang::{system_program, InstructionData, ToAccountMetas};
398 use light_system_program::utils::get_sol_pool_pda;
399 use solana_sdk::{instruction::Instruction, pubkey::Pubkey};
400
401 use crate::{
402 get_token_pool_pda, get_token_pool_pda_with_index, process_transfer::get_cpi_authority_pda,
403 };
404
405 pub fn create_create_token_pool_instruction(
406 fee_payer: &Pubkey,
407 mint: &Pubkey,
408 is_token_22: bool,
409 ) -> Instruction {
410 let token_pool_pda = get_token_pool_pda(mint);
411 let instruction_data = crate::instruction::CreateTokenPool {};
412
413 let token_program: Pubkey = if is_token_22 {
414 anchor_spl::token_2022::ID
415 } else {
416 anchor_spl::token::ID
417 };
418 let accounts = crate::accounts::CreateTokenPoolInstruction {
419 fee_payer: *fee_payer,
420 token_pool_pda,
421 system_program: system_program::ID,
422 mint: *mint,
423 token_program,
424 cpi_authority_pda: get_cpi_authority_pda().0,
425 };
426
427 Instruction {
428 program_id: crate::ID,
429 accounts: accounts.to_account_metas(Some(true)),
430 data: instruction_data.data(),
431 }
432 }
433
434 pub fn create_add_token_pool_instruction(
435 fee_payer: &Pubkey,
436 mint: &Pubkey,
437 token_pool_index: u8,
438 is_token_22: bool,
439 ) -> Instruction {
440 let token_pool_pda = get_token_pool_pda_with_index(mint, token_pool_index);
441 let existing_token_pool_pda =
442 get_token_pool_pda_with_index(mint, token_pool_index.saturating_sub(1));
443 let instruction_data = crate::instruction::AddTokenPool { token_pool_index };
444
445 let token_program: Pubkey = if is_token_22 {
446 anchor_spl::token_2022::ID
447 } else {
448 anchor_spl::token::ID
449 };
450 let accounts = crate::accounts::AddTokenPoolInstruction {
451 fee_payer: *fee_payer,
452 token_pool_pda,
453 system_program: system_program::ID,
454 mint: *mint,
455 token_program,
456 cpi_authority_pda: get_cpi_authority_pda().0,
457 existing_token_pool_pda,
458 };
459
460 Instruction {
461 program_id: crate::ID,
462 accounts: accounts.to_account_metas(Some(true)),
463 data: instruction_data.data(),
464 }
465 }
466
467 #[allow(clippy::too_many_arguments)]
468 pub fn create_mint_to_instruction(
469 fee_payer: &Pubkey,
470 authority: &Pubkey,
471 mint: &Pubkey,
472 merkle_tree: &Pubkey,
473 amounts: Vec<u64>,
474 public_keys: Vec<Pubkey>,
475 lamports: Option<u64>,
476 token_2022: bool,
477 token_pool_index: u8,
478 ) -> Instruction {
479 let token_pool_pda = get_token_pool_pda_with_index(mint, token_pool_index);
480
481 let instruction_data = crate::instruction::MintTo {
482 amounts,
483 public_keys,
484 lamports,
485 };
486 let sol_pool_pda = if lamports.is_some() {
487 Some(get_sol_pool_pda())
488 } else {
489 None
490 };
491 let token_program = if token_2022 {
492 anchor_spl::token_2022::ID
493 } else {
494 anchor_spl::token::ID
495 };
496
497 let accounts = crate::accounts::MintToInstruction {
498 fee_payer: *fee_payer,
499 authority: *authority,
500 cpi_authority_pda: get_cpi_authority_pda().0,
501 mint: Some(*mint),
502 token_pool_pda,
503 token_program,
504 light_system_program: light_system_program::ID,
505 registered_program_pda: light_system_program::utils::get_registered_program_pda(
506 &light_system_program::ID,
507 ),
508 noop_program: Pubkey::new_from_array(
509 account_compression::utils::constants::NOOP_PUBKEY,
510 ),
511 account_compression_authority: light_system_program::utils::get_cpi_authority_pda(
512 &light_system_program::ID,
513 ),
514 account_compression_program: account_compression::ID,
515 merkle_tree: *merkle_tree,
516 self_program: crate::ID,
517 system_program: system_program::ID,
518 sol_pool_pda,
519 };
520
521 Instruction {
522 program_id: crate::ID,
523 accounts: accounts.to_account_metas(Some(true)),
524 data: instruction_data.data(),
525 }
526 }
527}
528
529#[cfg(test)]
530mod test {
531 use light_compressed_account::{
532 compressed_account::{CompressedAccount, CompressedAccountData},
533 instruction_data::{
534 data::OutputCompressedAccountWithPackedContext, invoke_cpi::InstructionDataInvokeCpi,
535 },
536 };
537
538 use super::*;
539 use crate::{
540 constants::TOKEN_COMPRESSED_ACCOUNT_DISCRIMINATOR,
541 token_data::{AccountState, TokenData},
542 };
543
544 #[test]
545 fn test_manual_ix_data_serialization_borsh_compat() {
546 let pubkeys = [Pubkey::new_unique(), Pubkey::new_unique()];
547 let amounts = [1, 2];
548 let mint_pubkey = Pubkey::new_unique();
549 let mut output_compressed_accounts =
550 vec![OutputCompressedAccountWithPackedContext::default(); pubkeys.len()];
551 for (i, (pubkey, amount)) in pubkeys.iter().zip(amounts.iter()).enumerate() {
552 let mut token_data_bytes = Vec::with_capacity(std::mem::size_of::<TokenData>());
553 let token_data = TokenData {
554 mint: mint_pubkey,
555 owner: *pubkey,
556 amount: *amount,
557 delegate: None,
558 state: AccountState::Initialized,
559 tlv: None,
560 };
561
562 token_data.serialize(&mut token_data_bytes).unwrap();
563
564 let data = CompressedAccountData {
565 discriminator: TOKEN_COMPRESSED_ACCOUNT_DISCRIMINATOR,
566 data: token_data_bytes,
567 data_hash: token_data.hash_legacy().unwrap(),
568 };
569 let lamports = 0;
570
571 output_compressed_accounts[i] = OutputCompressedAccountWithPackedContext {
572 compressed_account: CompressedAccount {
573 owner: crate::ID.into(),
574 lamports,
575 data: Some(data),
576 address: None,
577 },
578 merkle_tree_index: 0,
579 };
580 }
581
582 let mut inputs = Vec::<u8>::new();
583 serialize_mint_to_cpi_instruction_data(&mut inputs, &output_compressed_accounts);
584 let inputs_struct = InstructionDataInvokeCpi {
585 relay_fee: None,
586 input_compressed_accounts_with_merkle_context: Vec::with_capacity(0),
587 output_compressed_accounts: output_compressed_accounts.clone(),
588 proof: None,
589 new_address_params: Vec::with_capacity(0),
590 compress_or_decompress_lamports: None,
591 is_compress: false,
592 cpi_context: None,
593 };
594 let mut reference = Vec::<u8>::new();
595 inputs_struct.serialize(&mut reference).unwrap();
596
597 assert_eq!(inputs.len(), reference.len());
598 for (j, i) in inputs.iter().zip(reference.iter()).enumerate() {
599 println!("j: {} i: {} {}", j, i.0, i.1);
600 assert_eq!(i.0, i.1);
601 }
602 assert_eq!(inputs, reference);
603 }
604
605 #[test]
606 fn test_manual_ix_data_serialization_borsh_compat_random() {
607 use rand::Rng;
608
609 for _ in 0..10000 {
610 let mut rng = rand::thread_rng();
611 let pubkeys = [Pubkey::new_unique(), Pubkey::new_unique()];
612 let amounts = [rng.gen_range(0..1_000_000_000_000), rng.gen_range(1..100)];
613 let mint_pubkey = Pubkey::new_unique();
614 let mut output_compressed_accounts =
615 vec![OutputCompressedAccountWithPackedContext::default(); pubkeys.len()];
616 for (i, (pubkey, amount)) in pubkeys.iter().zip(amounts.iter()).enumerate() {
617 let mut token_data_bytes = Vec::with_capacity(std::mem::size_of::<TokenData>());
618 let token_data = TokenData {
619 mint: mint_pubkey,
620 owner: *pubkey,
621 amount: *amount,
622 delegate: None,
623 state: AccountState::Initialized,
624 tlv: None,
625 };
626
627 token_data.serialize(&mut token_data_bytes).unwrap();
628
629 let data = CompressedAccountData {
630 discriminator: TOKEN_COMPRESSED_ACCOUNT_DISCRIMINATOR,
631 data: token_data_bytes,
632 data_hash: token_data.hash_legacy().unwrap(),
633 };
634 let lamports = rng.gen_range(0..1_000_000_000_000);
635
636 output_compressed_accounts[i] = OutputCompressedAccountWithPackedContext {
637 compressed_account: CompressedAccount {
638 owner: crate::ID.into(),
639 lamports,
640 data: Some(data),
641 address: None,
642 },
643 merkle_tree_index: 0,
644 };
645 }
646 let mut inputs = Vec::<u8>::new();
647 serialize_mint_to_cpi_instruction_data(&mut inputs, &output_compressed_accounts);
648 let sum = output_compressed_accounts
649 .iter()
650 .map(|x| x.compressed_account.lamports)
651 .sum::<u64>();
652 let inputs_struct = InstructionDataInvokeCpi {
653 relay_fee: None,
654 input_compressed_accounts_with_merkle_context: Vec::with_capacity(0),
655 output_compressed_accounts: output_compressed_accounts.clone(),
656 proof: None,
657 new_address_params: Vec::with_capacity(0),
658 compress_or_decompress_lamports: Some(sum),
659 is_compress: true,
660 cpi_context: None,
661 };
662 let mut reference = Vec::<u8>::new();
663 inputs_struct.serialize(&mut reference).unwrap();
664
665 assert_eq!(inputs.len(), reference.len());
666 for i in inputs.iter().zip(reference.iter()) {
667 assert_eq!(i.0, i.1);
668 }
669 assert_eq!(inputs, reference);
670 }
671 }
672}