light_test_utils/
spl.rs

1use anchor_spl::token::{Mint, TokenAccount};
2use forester_utils::create_account_instruction;
3use forester_utils::indexer::{Indexer, TokenDataWithContext};
4use light_compressed_token::{
5    burn::sdk::{create_burn_instruction, CreateBurnInstructionInputs},
6    delegation::sdk::{
7        create_approve_instruction, create_revoke_instruction, CreateApproveInstructionInputs,
8        CreateRevokeInstructionInputs,
9    },
10    freeze::sdk::{create_instruction, CreateInstructionInputs},
11    get_token_pool_pda,
12    mint_sdk::{create_create_token_pool_instruction, create_mint_to_instruction},
13    process_transfer::{transfer_sdk::create_transfer_instruction, TokenTransferOutputData},
14    token_data::AccountState,
15    TokenData,
16};
17use light_hasher::Poseidon;
18use light_system_program::{
19    invoke::processor::CompressedProof,
20    sdk::{compressed_account::MerkleContext, event::PublicTransactionEvent},
21};
22use solana_program_test::BanksClientError;
23use solana_sdk::{
24    instruction::Instruction,
25    program_pack::Pack,
26    pubkey::Pubkey,
27    signature::{Keypair, Signature, Signer},
28};
29use spl_token::instruction::initialize_mint;
30
31use crate::{
32    assert_compressed_tx::get_merkle_tree_snapshots,
33    assert_token_tx::{assert_create_mint, assert_mint_to, assert_transfer},
34};
35use light_client::rpc::errors::RpcError;
36use light_client::rpc::RpcConnection;
37use light_client::transaction_params::TransactionParams;
38
39pub async fn mint_tokens_helper<R: RpcConnection, I: Indexer<R>>(
40    rpc: &mut R,
41    test_indexer: &mut I,
42    merkle_tree_pubkey: &Pubkey,
43    mint_authority: &Keypair,
44    mint: &Pubkey,
45    amounts: Vec<u64>,
46    recipients: Vec<Pubkey>,
47) {
48    mint_tokens_helper_with_lamports(
49        rpc,
50        test_indexer,
51        merkle_tree_pubkey,
52        mint_authority,
53        mint,
54        amounts,
55        recipients,
56        None,
57    )
58    .await
59}
60#[allow(clippy::too_many_arguments)]
61pub async fn mint_tokens_helper_with_lamports<R: RpcConnection, I: Indexer<R>>(
62    rpc: &mut R,
63    test_indexer: &mut I,
64    merkle_tree_pubkey: &Pubkey,
65    mint_authority: &Keypair,
66    mint: &Pubkey,
67    amounts: Vec<u64>,
68    recipients: Vec<Pubkey>,
69    lamports: Option<u64>,
70) {
71    let payer_pubkey = mint_authority.pubkey();
72    let instruction = create_mint_to_instruction(
73        &payer_pubkey,
74        &payer_pubkey,
75        mint,
76        merkle_tree_pubkey,
77        amounts.clone(),
78        recipients.clone(),
79        lamports,
80    );
81
82    let output_merkle_tree_accounts =
83        test_indexer.get_state_merkle_tree_accounts(&vec![*merkle_tree_pubkey; amounts.len()]);
84
85    let snapshots = get_merkle_tree_snapshots::<R>(rpc, &output_merkle_tree_accounts).await;
86    let previous_mint_supply =
87        spl_token::state::Mint::unpack(&rpc.get_account(*mint).await.unwrap().unwrap().data)
88            .unwrap()
89            .supply;
90
91    let pool: Pubkey = get_token_pool_pda(mint);
92    let previous_pool_amount =
93        spl_token::state::Account::unpack(&rpc.get_account(pool).await.unwrap().unwrap().data)
94            .unwrap()
95            .amount;
96    let (event, _signature, _) = rpc
97        .create_and_send_transaction_with_event::<PublicTransactionEvent>(
98            &[instruction],
99            &payer_pubkey,
100            &[mint_authority],
101            None,
102        )
103        .await
104        .unwrap()
105        .unwrap();
106
107    let (_, created_token_accounts) = test_indexer.add_event_and_compressed_accounts(&event);
108    assert_mint_to(
109        rpc,
110        test_indexer,
111        &recipients,
112        *mint,
113        amounts.as_slice(),
114        &snapshots,
115        &created_token_accounts,
116        previous_mint_supply,
117        previous_pool_amount,
118    )
119    .await;
120}
121
122pub async fn create_token_pool<R: RpcConnection>(
123    rpc: &mut R,
124    payer: &Keypair,
125    mint_authority: &Pubkey,
126    decimals: u8,
127    freeze_authority: Option<&Pubkey>,
128    mint_keypair: Option<&Keypair>,
129) -> Pubkey {
130    let keypair = Keypair::new();
131    let mint_keypair = match mint_keypair {
132        Some(mint_keypair) => mint_keypair,
133        None => &keypair,
134    };
135    let mint_pubkey = (*mint_keypair).pubkey();
136    let mint_rent = rpc
137        .get_minimum_balance_for_rent_exemption(Mint::LEN)
138        .await
139        .unwrap();
140
141    let account_create_ix = create_account_instruction(
142        &payer.pubkey(),
143        Mint::LEN,
144        mint_rent,
145        &light_compressed_token::ID,
146        Some(mint_keypair),
147    );
148
149    let create_mint_ix = spl_token::instruction::initialize_mint2(
150        &spl_token::id(),
151        &mint_pubkey,
152        mint_authority,
153        freeze_authority,
154        decimals,
155    )
156    .unwrap();
157    rpc.create_and_send_transaction(
158        &[account_create_ix, create_mint_ix],
159        &payer.pubkey(),
160        &[payer],
161    )
162    .await
163    .unwrap();
164    mint_pubkey
165}
166
167pub async fn create_mint_helper<R: RpcConnection>(rpc: &mut R, payer: &Keypair) -> Pubkey {
168    let payer_pubkey = payer.pubkey();
169    let rent = rpc
170        .get_minimum_balance_for_rent_exemption(Mint::LEN)
171        .await
172        .unwrap();
173    let mint = Keypair::new();
174
175    let (instructions, pool) =
176        create_initialize_mint_instructions(&payer_pubkey, &payer_pubkey, rent, 2, &mint);
177
178    rpc.create_and_send_transaction(&instructions, &payer_pubkey, &[payer, &mint])
179        .await
180        .unwrap();
181    assert_create_mint(rpc, &payer_pubkey, &mint.pubkey(), &pool).await;
182    mint.pubkey()
183}
184
185pub async fn mint_wrapped_sol<R: RpcConnection>(
186    rpc: &mut R,
187    payer: &Keypair,
188    token_account: &Pubkey,
189    amount: u64,
190) -> Result<Signature, RpcError> {
191    let transfer_ix = anchor_lang::solana_program::system_instruction::transfer(
192        &payer.pubkey(),
193        token_account,
194        amount,
195    );
196    let sync_native_ix = spl_token::instruction::sync_native(&spl_token::ID, token_account)
197        .map_err(|e| RpcError::CustomError(format!("{:?}", e)))?;
198
199    rpc.create_and_send_transaction(&[transfer_ix, sync_native_ix], &payer.pubkey(), &[payer])
200        .await
201}
202
203pub fn create_initialize_mint_instructions(
204    payer: &Pubkey,
205    authority: &Pubkey,
206    rent: u64,
207    decimals: u8,
208    mint_keypair: &Keypair,
209) -> ([Instruction; 4], Pubkey) {
210    let account_create_ix =
211        create_account_instruction(payer, Mint::LEN, rent, &spl_token::ID, Some(mint_keypair));
212
213    let mint_pubkey = mint_keypair.pubkey();
214    let create_mint_instruction = initialize_mint(
215        &spl_token::ID,
216        &mint_keypair.pubkey(),
217        authority,
218        Some(authority),
219        decimals,
220    )
221    .unwrap();
222    let transfer_ix =
223        anchor_lang::solana_program::system_instruction::transfer(payer, &mint_pubkey, rent);
224
225    let instruction = create_create_token_pool_instruction(payer, &mint_pubkey);
226    let pool_pubkey = get_token_pool_pda(&mint_pubkey);
227    (
228        [
229            account_create_ix,
230            create_mint_instruction,
231            transfer_ix,
232            instruction,
233        ],
234        pool_pubkey,
235    )
236}
237
238/// Creates a spl token account and initializes it with the given mint and owner.
239/// This function is useful to create token accounts for spl compression and decompression tests.
240pub async fn create_token_account<R: RpcConnection>(
241    rpc: &mut R,
242    mint: &Pubkey,
243    account_keypair: &Keypair,
244    owner: &Keypair,
245) -> Result<(), BanksClientError> {
246    let rent = rpc
247        .get_minimum_balance_for_rent_exemption(TokenAccount::LEN)
248        .await
249        .unwrap();
250    let account_create_ix = create_account_instruction(
251        &owner.pubkey(),
252        TokenAccount::LEN,
253        rent,
254        &spl_token::ID,
255        Some(account_keypair),
256    );
257    let instruction = spl_token::instruction::initialize_account(
258        &spl_token::ID,
259        &account_keypair.pubkey(),
260        mint,
261        &owner.pubkey(),
262    )
263    .unwrap();
264    rpc.create_and_send_transaction(
265        &[account_create_ix, instruction],
266        &owner.pubkey(),
267        &[account_keypair, owner],
268    )
269    .await
270    .unwrap();
271    Ok(())
272}
273
274#[allow(clippy::too_many_arguments)]
275pub async fn compressed_transfer_test<R: RpcConnection, I: Indexer<R>>(
276    payer: &Keypair,
277    rpc: &mut R,
278    test_indexer: &mut I,
279    mint: &Pubkey,
280    from: &Keypair,
281    recipients: &[Pubkey],
282    amounts: &[u64],
283    mut lamports: Option<Vec<Option<u64>>>,
284    input_compressed_accounts: &[TokenDataWithContext],
285    output_merkle_tree_pubkeys: &[Pubkey],
286    delegate_change_account_index: Option<u8>,
287    delegate_is_signer: bool,
288    transaction_params: Option<TransactionParams>,
289) {
290    if recipients.len() != amounts.len() && amounts.len() != output_merkle_tree_pubkeys.len() {
291        println!("{:?}", recipients);
292        println!("{:?}", amounts);
293        println!("{:?}", output_merkle_tree_pubkeys);
294        panic!("recipients, amounts, and output_merkle_tree_pubkeys must have the same length");
295    }
296    let mut input_merkle_tree_context = Vec::new();
297    let mut input_compressed_account_token_data = Vec::new();
298    let mut input_compressed_account_hashes = Vec::new();
299    let mut sum_input_amounts = 0;
300    for account in input_compressed_accounts {
301        let leaf_index = account.compressed_account.merkle_context.leaf_index;
302        input_compressed_account_token_data.push(account.token_data.clone());
303        input_compressed_account_hashes.push(
304            account
305                .compressed_account
306                .compressed_account
307                .hash::<Poseidon>(
308                    &account.compressed_account.merkle_context.merkle_tree_pubkey,
309                    &leaf_index,
310                )
311                .unwrap(),
312        );
313        sum_input_amounts += account.token_data.amount;
314        input_merkle_tree_context.push(MerkleContext {
315            merkle_tree_pubkey: account.compressed_account.merkle_context.merkle_tree_pubkey,
316            nullifier_queue_pubkey: account
317                .compressed_account
318                .merkle_context
319                .nullifier_queue_pubkey,
320            leaf_index,
321            queue_index: None,
322        });
323    }
324    let output_lamports = lamports
325        .clone()
326        .unwrap_or_else(|| vec![None; recipients.len()]);
327    let mut output_compressed_accounts = Vec::new();
328    for (((recipient, amount), merkle_tree_pubkey), lamports) in recipients
329        .iter()
330        .zip(amounts)
331        .zip(output_merkle_tree_pubkeys)
332        .zip(output_lamports)
333    {
334        let account = TokenTransferOutputData {
335            amount: *amount,
336            owner: *recipient,
337            lamports,
338            merkle_tree: *merkle_tree_pubkey,
339        };
340        sum_input_amounts -= amount;
341        output_compressed_accounts.push(account);
342    }
343    // add change compressed account if tokens are left
344    if sum_input_amounts > 0 {
345        let account = TokenTransferOutputData {
346            amount: sum_input_amounts,
347            owner: from.pubkey(),
348            lamports: None,
349            merkle_tree: *output_merkle_tree_pubkeys.last().unwrap(),
350        };
351        output_compressed_accounts.push(account);
352    }
353    let input_merkle_tree_pubkeys: Vec<Pubkey> = input_merkle_tree_context
354        .iter()
355        .map(|x| x.merkle_tree_pubkey)
356        .collect();
357    println!("{:?}", input_compressed_accounts);
358    println!(
359        "input_compressed_account_hashes: {:?}",
360        input_compressed_account_hashes
361    );
362    let proof_rpc_result = test_indexer
363        .create_proof_for_compressed_accounts(
364            Some(&input_compressed_account_hashes),
365            Some(&input_merkle_tree_pubkeys),
366            None,
367            None,
368            rpc,
369        )
370        .await;
371    output_compressed_accounts.sort_by(|a, b| a.merkle_tree.cmp(&b.merkle_tree));
372
373    let delegate_pubkey = if delegate_is_signer {
374        Some(payer.pubkey())
375    } else {
376        None
377    };
378    let authority_signer = if delegate_is_signer { payer } else { from };
379    let instruction = create_transfer_instruction(
380        &payer.pubkey(),
381        &authority_signer.pubkey(), // authority
382        &input_merkle_tree_context,
383        &output_compressed_accounts,
384        &proof_rpc_result.root_indices,
385        &Some(proof_rpc_result.proof),
386        &input_compressed_account_token_data, // input_token_data
387        &input_compressed_accounts
388            .iter()
389            .map(|x| &x.compressed_account.compressed_account)
390            .cloned()
391            .collect::<Vec<_>>(),
392        *mint,
393        delegate_pubkey, // owner_if_delegate_change_account_index
394        false,           // is_compress
395        None,            // compression_amount
396        None,            // token_pool_pda
397        None,            // compress_or_decompress_token_account
398        true,
399        delegate_change_account_index,
400        None,
401    )
402    .unwrap();
403    let sum_input_lamports = input_compressed_accounts
404        .iter()
405        .map(|x| &x.compressed_account.compressed_account.lamports)
406        .sum::<u64>();
407    let sum_output_lamports = output_compressed_accounts
408        .iter()
409        .map(|x| x.lamports.unwrap_or(0))
410        .sum::<u64>();
411    let sum_output_amounts = output_compressed_accounts
412        .iter()
413        .map(|x| x.amount)
414        .sum::<u64>();
415    let output_merkle_tree_pubkeys = if sum_input_lamports > sum_output_lamports
416        || sum_input_amounts > sum_output_amounts && delegate_is_signer
417    {
418        let mut output_merkle_tree_pubkeys = output_merkle_tree_pubkeys.to_vec();
419        output_merkle_tree_pubkeys.push(*output_merkle_tree_pubkeys.last().unwrap());
420        if let Some(lamports) = &mut lamports {
421            if sum_input_lamports != sum_output_lamports {
422                lamports.push(Some(sum_input_lamports - sum_output_lamports));
423            } else {
424                lamports.push(None);
425            }
426        }
427        output_merkle_tree_pubkeys
428    } else {
429        output_merkle_tree_pubkeys.to_vec()
430    };
431
432    let output_merkle_tree_accounts =
433        test_indexer.get_state_merkle_tree_accounts(output_merkle_tree_pubkeys.as_slice());
434    let input_merkle_tree_accounts =
435        test_indexer.get_state_merkle_tree_accounts(&input_merkle_tree_pubkeys);
436    let snapshots =
437        get_merkle_tree_snapshots::<R>(rpc, output_merkle_tree_accounts.as_slice()).await;
438    let input_snapshots =
439        get_merkle_tree_snapshots::<R>(rpc, input_merkle_tree_accounts.as_slice()).await;
440
441    let (event, _signature, _) = rpc
442        .create_and_send_transaction_with_event::<PublicTransactionEvent>(
443            &[instruction],
444            &payer.pubkey(),
445            &[payer, authority_signer],
446            transaction_params,
447        )
448        .await
449        .unwrap()
450        .unwrap();
451
452    let (created_change_output_account, created_token_output_accounts) =
453        test_indexer.add_event_and_compressed_accounts(&event);
454    let delegates = if let Some(index) = delegate_change_account_index {
455        let mut delegates = vec![None; created_token_output_accounts.len()];
456        delegates[index as usize] = Some(payer.pubkey());
457        Some(delegates)
458    } else {
459        None
460    };
461    let mut created_output_accounts = Vec::new();
462    created_token_output_accounts.iter().for_each(|x| {
463        created_output_accounts.push(x.compressed_account.clone());
464    });
465    created_change_output_account.iter().for_each(|x| {
466        created_output_accounts.push(x.clone());
467    });
468    assert_transfer(
469        rpc,
470        test_indexer,
471        &output_compressed_accounts,
472        created_output_accounts.as_slice(),
473        lamports,
474        &input_compressed_account_hashes,
475        &snapshots,
476        &input_snapshots,
477        &event,
478        delegates,
479    )
480    .await;
481}
482
483#[allow(clippy::too_many_arguments)]
484pub async fn decompress_test<R: RpcConnection, I: Indexer<R>>(
485    payer: &Keypair,
486    rpc: &mut R,
487    test_indexer: &mut I,
488    input_compressed_accounts: Vec<TokenDataWithContext>,
489    amount: u64,
490    output_merkle_tree_pubkey: &Pubkey,
491    recipient_token_account: &Pubkey,
492    transaction_params: Option<TransactionParams>,
493) {
494    let max_amount: u64 = input_compressed_accounts
495        .iter()
496        .map(|x| x.token_data.amount)
497        .sum();
498    let change_out_compressed_account = TokenTransferOutputData {
499        amount: max_amount - amount,
500        owner: payer.pubkey(),
501        lamports: None,
502        merkle_tree: *output_merkle_tree_pubkey,
503    };
504    let input_compressed_account_hashes = input_compressed_accounts
505        .iter()
506        .map(|x| x.compressed_account.hash().unwrap())
507        .collect::<Vec<_>>();
508    let input_merkle_tree_pubkeys = input_compressed_accounts
509        .iter()
510        .map(|x| x.compressed_account.merkle_context.merkle_tree_pubkey)
511        .collect::<Vec<_>>();
512    let proof_rpc_result = test_indexer
513        .create_proof_for_compressed_accounts(
514            Some(&input_compressed_account_hashes),
515            Some(&input_merkle_tree_pubkeys),
516            None,
517            None,
518            rpc,
519        )
520        .await;
521    let mint = input_compressed_accounts[0].token_data.mint;
522    let instruction = create_transfer_instruction(
523        &rpc.get_payer().pubkey(),
524        &payer.pubkey(), // authority
525        &input_compressed_accounts
526            .iter()
527            .map(|x| x.compressed_account.merkle_context)
528            .collect::<Vec<_>>(), // input_compressed_account_merkle_tree_pubkeys
529        &[change_out_compressed_account], // output_compressed_accounts
530        &proof_rpc_result.root_indices, // root_indices
531        &Some(proof_rpc_result.proof),
532        input_compressed_accounts
533            .iter()
534            .map(|x| x.token_data.clone())
535            .collect::<Vec<_>>()
536            .as_slice(), // input_token_data
537        &input_compressed_accounts
538            .iter()
539            .map(|x| &x.compressed_account.compressed_account)
540            .cloned()
541            .collect::<Vec<_>>(),
542        mint,                            // mint
543        None,                            // owner_if_delegate_change_account_index
544        false,                           // is_compress
545        Some(amount),                    // compression_amount
546        Some(get_token_pool_pda(&mint)), // token_pool_pda
547        Some(*recipient_token_account),  // compress_or_decompress_token_account
548        true,
549        None,
550        None,
551    )
552    .unwrap();
553    let output_merkle_tree_pubkeys = vec![*output_merkle_tree_pubkey];
554    let output_merkle_tree_accounts =
555        test_indexer.get_state_merkle_tree_accounts(&output_merkle_tree_pubkeys);
556    let input_merkle_tree_accounts =
557        test_indexer.get_state_merkle_tree_accounts(&input_merkle_tree_pubkeys);
558    let output_merkle_tree_test_snapshots =
559        get_merkle_tree_snapshots::<R>(rpc, output_merkle_tree_accounts.as_slice()).await;
560    let input_merkle_tree_test_snapshots =
561        get_merkle_tree_snapshots::<R>(rpc, input_merkle_tree_accounts.as_slice()).await;
562    let recipient_token_account_data_pre = spl_token::state::Account::unpack(
563        &rpc.get_account(*recipient_token_account)
564            .await
565            .unwrap()
566            .unwrap()
567            .data,
568    )
569    .unwrap();
570    let context_payer = rpc.get_payer().insecure_clone();
571    let (event, _signature, _) = rpc
572        .create_and_send_transaction_with_event::<PublicTransactionEvent>(
573            &[instruction],
574            &context_payer.pubkey(),
575            &[&context_payer, payer],
576            transaction_params,
577        )
578        .await
579        .unwrap()
580        .unwrap();
581
582    let (_, created_output_accounts) = test_indexer.add_event_and_compressed_accounts(&event);
583    assert_transfer(
584        rpc,
585        test_indexer,
586        &[change_out_compressed_account],
587        created_output_accounts
588            .iter()
589            .map(|x| x.compressed_account.clone())
590            .collect::<Vec<_>>()
591            .as_slice(),
592        None,
593        input_compressed_account_hashes.as_slice(),
594        &output_merkle_tree_test_snapshots,
595        &input_merkle_tree_test_snapshots,
596        &event,
597        None,
598    )
599    .await;
600
601    let recipient_token_account_data = spl_token::state::Account::unpack(
602        &rpc.get_account(*recipient_token_account)
603            .await
604            .unwrap()
605            .unwrap()
606            .data,
607    )
608    .unwrap();
609    assert_eq!(
610        recipient_token_account_data.amount,
611        recipient_token_account_data_pre.amount + amount
612    );
613}
614
615#[allow(clippy::too_many_arguments)]
616pub async fn compress_test<R: RpcConnection, I: Indexer<R>>(
617    payer: &Keypair,
618    rpc: &mut R,
619    test_indexer: &mut I,
620    amount: u64,
621    mint: &Pubkey,
622    output_merkle_tree_pubkey: &Pubkey,
623    sender_token_account: &Pubkey,
624    transaction_params: Option<TransactionParams>,
625) {
626    let output_compressed_account = TokenTransferOutputData {
627        amount,
628        owner: payer.pubkey(),
629        lamports: None,
630        merkle_tree: *output_merkle_tree_pubkey,
631    };
632
633    let instruction = create_transfer_instruction(
634        &rpc.get_payer().pubkey(),
635        &payer.pubkey(),              // authority
636        &Vec::new(),                  // input_compressed_account_merkle_tree_pubkeys
637        &[output_compressed_account], // output_compressed_accounts
638        &Vec::new(),                  // root_indices
639        &None,
640        &Vec::new(),                    // input_token_data
641        &Vec::new(),                    // input_compressed_accounts
642        *mint,                          // mint
643        None,                           // owner_if_delegate_is_signer
644        true,                           // is_compress
645        Some(amount),                   // compression_amount
646        Some(get_token_pool_pda(mint)), // token_pool_pda
647        Some(*sender_token_account),    // compress_or_decompress_token_account
648        true,
649        None,
650        None,
651    )
652    .unwrap();
653    let output_merkle_tree_pubkeys = vec![*output_merkle_tree_pubkey];
654    let output_merkle_tree_accounts =
655        test_indexer.get_state_merkle_tree_accounts(&output_merkle_tree_pubkeys);
656    let output_merkle_tree_test_snapshots =
657        get_merkle_tree_snapshots::<R>(rpc, output_merkle_tree_accounts.as_slice()).await;
658    let input_merkle_tree_test_snapshots = Vec::new();
659    let recipient_token_account_data_pre = spl_token::state::Account::unpack(
660        &rpc.get_account(*sender_token_account)
661            .await
662            .unwrap()
663            .unwrap()
664            .data,
665    )
666    .unwrap();
667    let context_payer = rpc.get_payer().insecure_clone();
668    let (event, _signature, _) = rpc
669        .create_and_send_transaction_with_event::<PublicTransactionEvent>(
670            &[instruction],
671            &payer.pubkey(),
672            &[&context_payer, payer],
673            transaction_params,
674        )
675        .await
676        .unwrap()
677        .unwrap();
678
679    let (_, created_output_accounts) = test_indexer.add_event_and_compressed_accounts(&event);
680
681    assert_transfer(
682        rpc,
683        test_indexer,
684        &[output_compressed_account],
685        created_output_accounts
686            .iter()
687            .map(|x| x.compressed_account.clone())
688            .collect::<Vec<_>>()
689            .as_slice(),
690        None,
691        Vec::new().as_slice(),
692        &output_merkle_tree_test_snapshots,
693        &input_merkle_tree_test_snapshots,
694        &event,
695        None,
696    )
697    .await;
698
699    let recipient_token_account_data = spl_token::state::Account::unpack(
700        &rpc.get_account(*sender_token_account)
701            .await
702            .unwrap()
703            .unwrap()
704            .data,
705    )
706    .unwrap();
707    assert_eq!(
708        recipient_token_account_data.amount,
709        recipient_token_account_data_pre.amount - amount
710    );
711}
712
713#[allow(clippy::too_many_arguments)]
714pub async fn approve_test<R: RpcConnection, I: Indexer<R>>(
715    authority: &Keypair,
716    rpc: &mut R,
717    test_indexer: &mut I,
718    input_compressed_accounts: Vec<TokenDataWithContext>,
719    delegated_amount: u64,
720    delegate_lamports: Option<u64>,
721    delegate: &Pubkey,
722    delegated_compressed_account_merkle_tree: &Pubkey,
723    change_compressed_account_merkle_tree: &Pubkey,
724    transaction_params: Option<TransactionParams>,
725) {
726    let input_compressed_account_hashes = input_compressed_accounts
727        .iter()
728        .map(|x| x.compressed_account.hash().unwrap())
729        .collect::<Vec<_>>();
730    let input_merkle_tree_pubkeys = input_compressed_accounts
731        .iter()
732        .map(|x| x.compressed_account.merkle_context.merkle_tree_pubkey)
733        .collect::<Vec<_>>();
734    println!(
735        "input_compressed_account_hashes: {:?}",
736        input_compressed_account_hashes
737    );
738    println!("input compressed accounts: {:?}", input_compressed_accounts);
739    let proof_rpc_result = test_indexer
740        .create_proof_for_compressed_accounts(
741            Some(&input_compressed_account_hashes),
742            Some(&input_merkle_tree_pubkeys),
743            None,
744            None,
745            rpc,
746        )
747        .await;
748    let mint = input_compressed_accounts[0].token_data.mint;
749    let inputs = CreateApproveInstructionInputs {
750        fee_payer: rpc.get_payer().pubkey(),
751        authority: authority.pubkey(),
752        input_merkle_contexts: input_compressed_accounts
753            .iter()
754            .map(|x| x.compressed_account.merkle_context)
755            .collect(),
756        input_token_data: input_compressed_accounts
757            .iter()
758            .map(|x| x.token_data.clone())
759            .collect(),
760        input_compressed_accounts: input_compressed_accounts
761            .iter()
762            .map(|x| &x.compressed_account.compressed_account)
763            .cloned()
764            .collect::<Vec<_>>(),
765        mint,
766        delegated_amount,
767        delegate_lamports,
768        delegated_compressed_account_merkle_tree: *delegated_compressed_account_merkle_tree,
769        change_compressed_account_merkle_tree: *change_compressed_account_merkle_tree,
770        delegate: *delegate,
771        root_indices: proof_rpc_result.root_indices,
772        proof: proof_rpc_result.proof,
773    };
774
775    let instruction = create_approve_instruction(inputs).unwrap();
776    let mut output_merkle_tree_pubkeys = vec![*delegated_compressed_account_merkle_tree];
777    let input_amount = input_compressed_accounts
778        .iter()
779        .map(|x| x.token_data.amount)
780        .sum::<u64>();
781    let change_amount = input_amount - delegated_amount;
782    let input_lamports = input_compressed_accounts
783        .iter()
784        .map(|x| x.compressed_account.compressed_account.lamports)
785        .sum::<u64>();
786    let (change_lamports, change_lamports_greater_zero) =
787        if let Some(delegate_lamports) = delegate_lamports {
788            let change_lamports = input_lamports - delegate_lamports;
789            let option_change_lamports = if change_lamports > 0 {
790                Some(change_lamports)
791            } else {
792                None
793            };
794
795            (
796                Some(vec![Some(delegate_lamports), option_change_lamports]),
797                change_lamports > 0,
798            )
799        } else if input_lamports > 0 {
800            (Some(vec![None, Some(input_lamports)]), true)
801        } else {
802            (None, false)
803        };
804    if change_lamports_greater_zero || change_amount > 0 {
805        output_merkle_tree_pubkeys.push(*change_compressed_account_merkle_tree);
806    }
807    let output_merkle_tree_accounts =
808        test_indexer.get_state_merkle_tree_accounts(&output_merkle_tree_pubkeys);
809
810    let output_merkle_tree_test_snapshots =
811        get_merkle_tree_snapshots::<R>(rpc, output_merkle_tree_accounts.as_slice()).await;
812    let input_merkle_tree_accounts =
813        test_indexer.get_state_merkle_tree_accounts(&input_merkle_tree_pubkeys);
814    let input_merkle_tree_test_snapshots =
815        get_merkle_tree_snapshots::<R>(rpc, input_merkle_tree_accounts.as_slice()).await;
816    let context_payer = rpc.get_payer().insecure_clone();
817    let (event, _signature, _) = rpc
818        .create_and_send_transaction_with_event::<PublicTransactionEvent>(
819            &[instruction],
820            &context_payer.pubkey(),
821            &[&context_payer, authority],
822            transaction_params,
823        )
824        .await
825        .unwrap()
826        .unwrap();
827    let (_, created_output_accounts) = test_indexer.add_event_and_compressed_accounts(&event);
828
829    let expected_delegated_token_data = TokenData {
830        mint,
831        owner: authority.pubkey(),
832        amount: delegated_amount,
833        delegate: Some(*delegate),
834        state: AccountState::Initialized,
835        tlv: None,
836    };
837
838    assert_eq!(
839        expected_delegated_token_data,
840        created_output_accounts[0].token_data
841    );
842    let mut expected_token_data = vec![expected_delegated_token_data];
843    let mut delegates = vec![Some(*delegate)];
844    if delegated_amount != input_amount {
845        let expected_change_token_data = TokenData {
846            mint,
847            owner: authority.pubkey(),
848            amount: change_amount,
849            delegate: None,
850            state: AccountState::Initialized,
851            tlv: None,
852        };
853        assert_eq!(
854            expected_change_token_data,
855            created_output_accounts[1].token_data
856        );
857        expected_token_data.push(expected_change_token_data);
858        delegates.push(None);
859    }
860
861    let expected_compressed_output_accounts =
862        create_expected_token_output_data(expected_token_data, &output_merkle_tree_pubkeys);
863
864    assert_transfer(
865        rpc,
866        test_indexer,
867        expected_compressed_output_accounts.as_slice(),
868        created_output_accounts
869            .iter()
870            .map(|x| x.compressed_account.clone())
871            .collect::<Vec<_>>()
872            .as_slice(),
873        change_lamports,
874        input_compressed_account_hashes.as_slice(),
875        &output_merkle_tree_test_snapshots,
876        &input_merkle_tree_test_snapshots,
877        &event,
878        Some(delegates),
879    )
880    .await;
881}
882
883#[allow(clippy::too_many_arguments)]
884pub async fn revoke_test<R: RpcConnection, I: Indexer<R>>(
885    authority: &Keypair,
886    rpc: &mut R,
887    test_indexer: &mut I,
888    input_compressed_accounts: Vec<TokenDataWithContext>,
889    output_account_merkle_tree: &Pubkey,
890    transaction_params: Option<TransactionParams>,
891) {
892    let input_compressed_account_hashes = input_compressed_accounts
893        .iter()
894        .map(|x| x.compressed_account.hash().unwrap())
895        .collect::<Vec<_>>();
896    let input_merkle_tree_pubkeys = input_compressed_accounts
897        .iter()
898        .map(|x| x.compressed_account.merkle_context.merkle_tree_pubkey)
899        .collect::<Vec<_>>();
900    let proof_rpc_result = test_indexer
901        .create_proof_for_compressed_accounts(
902            Some(&input_compressed_account_hashes),
903            Some(&input_merkle_tree_pubkeys),
904            None,
905            None,
906            rpc,
907        )
908        .await;
909    let mint = input_compressed_accounts[0].token_data.mint;
910    let inputs = CreateRevokeInstructionInputs {
911        fee_payer: rpc.get_payer().pubkey(),
912        authority: authority.pubkey(),
913        input_merkle_contexts: input_compressed_accounts
914            .iter()
915            .map(|x| x.compressed_account.merkle_context)
916            .collect(),
917        input_token_data: input_compressed_accounts
918            .iter()
919            .map(|x| x.token_data.clone())
920            .collect(),
921        input_compressed_accounts: input_compressed_accounts
922            .iter()
923            .map(|x| &x.compressed_account.compressed_account)
924            .cloned()
925            .collect::<Vec<_>>(),
926        mint,
927        output_account_merkle_tree: *output_account_merkle_tree,
928        root_indices: proof_rpc_result.root_indices,
929        proof: proof_rpc_result.proof,
930    };
931
932    let instruction = create_revoke_instruction(inputs).unwrap();
933    let output_merkle_tree_pubkeys = vec![*output_account_merkle_tree];
934    let output_merkle_tree_accounts =
935        test_indexer.get_state_merkle_tree_accounts(&output_merkle_tree_pubkeys);
936    let input_merkle_tree_accounts =
937        test_indexer.get_state_merkle_tree_accounts(&input_merkle_tree_pubkeys);
938    let output_merkle_tree_test_snapshots =
939        get_merkle_tree_snapshots::<R>(rpc, output_merkle_tree_accounts.as_slice()).await;
940    let input_merkle_tree_test_snapshots =
941        get_merkle_tree_snapshots::<R>(rpc, input_merkle_tree_accounts.as_slice()).await;
942    let context_payer = rpc.get_payer().insecure_clone();
943    let (event, _signature, _) = rpc
944        .create_and_send_transaction_with_event::<PublicTransactionEvent>(
945            &[instruction],
946            &context_payer.pubkey(),
947            &[&context_payer, authority],
948            transaction_params,
949        )
950        .await
951        .unwrap()
952        .unwrap();
953    let (_, created_output_accounts) = test_indexer.add_event_and_compressed_accounts(&event);
954    let input_amount = input_compressed_accounts
955        .iter()
956        .map(|x| x.token_data.amount)
957        .sum::<u64>();
958    let expected_token_data = TokenData {
959        mint,
960        owner: authority.pubkey(),
961        amount: input_amount,
962        delegate: None,
963        state: AccountState::Initialized,
964        tlv: None,
965    };
966    assert_eq!(expected_token_data, created_output_accounts[0].token_data);
967    let expected_compressed_output_accounts =
968        create_expected_token_output_data(vec![expected_token_data], &output_merkle_tree_pubkeys);
969    let sum_inputs = input_compressed_accounts
970        .iter()
971        .map(|x| x.compressed_account.compressed_account.lamports)
972        .sum::<u64>();
973    let change_lamports = if sum_inputs > 0 {
974        Some(vec![Some(sum_inputs)])
975    } else {
976        None
977    };
978    assert_transfer(
979        rpc,
980        test_indexer,
981        expected_compressed_output_accounts.as_slice(),
982        created_output_accounts
983            .iter()
984            .map(|x| x.compressed_account.clone())
985            .collect::<Vec<_>>()
986            .as_slice(),
987        change_lamports,
988        input_compressed_account_hashes.as_slice(),
989        &output_merkle_tree_test_snapshots,
990        &input_merkle_tree_test_snapshots,
991        &event,
992        None,
993    )
994    .await;
995}
996
997pub async fn freeze_test<R: RpcConnection, I: Indexer<R>>(
998    authority: &Keypair,
999    rpc: &mut R,
1000    test_indexer: &mut I,
1001    input_compressed_accounts: Vec<TokenDataWithContext>,
1002    outputs_merkle_tree: &Pubkey,
1003    transaction_params: Option<TransactionParams>,
1004) {
1005    freeze_or_thaw_test::<R, true, I>(
1006        authority,
1007        rpc,
1008        test_indexer,
1009        input_compressed_accounts,
1010        outputs_merkle_tree,
1011        transaction_params,
1012    )
1013    .await;
1014}
1015
1016pub async fn thaw_test<R: RpcConnection, I: Indexer<R>>(
1017    authority: &Keypair,
1018    rpc: &mut R,
1019    test_indexer: &mut I,
1020    input_compressed_accounts: Vec<TokenDataWithContext>,
1021    outputs_merkle_tree: &Pubkey,
1022    transaction_params: Option<TransactionParams>,
1023) {
1024    freeze_or_thaw_test::<R, false, I>(
1025        authority,
1026        rpc,
1027        test_indexer,
1028        input_compressed_accounts,
1029        outputs_merkle_tree,
1030        transaction_params,
1031    )
1032    .await;
1033}
1034
1035pub async fn freeze_or_thaw_test<R: RpcConnection, const FREEZE: bool, I: Indexer<R>>(
1036    authority: &Keypair,
1037    rpc: &mut R,
1038    test_indexer: &mut I,
1039    input_compressed_accounts: Vec<TokenDataWithContext>,
1040    outputs_merkle_tree: &Pubkey,
1041    transaction_params: Option<TransactionParams>,
1042) {
1043    let input_compressed_account_hashes = input_compressed_accounts
1044        .iter()
1045        .map(|x| x.compressed_account.hash().unwrap())
1046        .collect::<Vec<_>>();
1047    let input_merkle_tree_pubkeys = input_compressed_accounts
1048        .iter()
1049        .map(|x| x.compressed_account.merkle_context.merkle_tree_pubkey)
1050        .collect::<Vec<_>>();
1051    let proof_rpc_result = test_indexer
1052        .create_proof_for_compressed_accounts(
1053            Some(&input_compressed_account_hashes),
1054            Some(&input_merkle_tree_pubkeys),
1055            None,
1056            None,
1057            rpc,
1058        )
1059        .await;
1060    let mint = input_compressed_accounts[0].token_data.mint;
1061    let inputs = CreateInstructionInputs {
1062        fee_payer: rpc.get_payer().pubkey(),
1063        authority: authority.pubkey(),
1064        input_merkle_contexts: input_compressed_accounts
1065            .iter()
1066            .map(|x| x.compressed_account.merkle_context)
1067            .collect(),
1068        input_token_data: input_compressed_accounts
1069            .iter()
1070            .map(|x| x.token_data.clone())
1071            .collect(),
1072        input_compressed_accounts: input_compressed_accounts
1073            .iter()
1074            .map(|x| &x.compressed_account.compressed_account)
1075            .cloned()
1076            .collect::<Vec<_>>(),
1077        outputs_merkle_tree: *outputs_merkle_tree,
1078        root_indices: proof_rpc_result.root_indices,
1079        proof: proof_rpc_result.proof,
1080    };
1081
1082    let instruction = create_instruction::<FREEZE>(inputs).unwrap();
1083    let output_merkle_tree_pubkeys =
1084        vec![*outputs_merkle_tree; input_compressed_account_hashes.len()];
1085    let output_merkle_tree_accounts =
1086        test_indexer.get_state_merkle_tree_accounts(&output_merkle_tree_pubkeys);
1087    let input_merkle_tree_accounts =
1088        test_indexer.get_state_merkle_tree_accounts(&input_merkle_tree_pubkeys);
1089    let output_merkle_tree_test_snapshots =
1090        get_merkle_tree_snapshots::<R>(rpc, output_merkle_tree_accounts.as_slice()).await;
1091    let input_merkle_tree_test_snapshots =
1092        get_merkle_tree_snapshots::<R>(rpc, input_merkle_tree_accounts.as_slice()).await;
1093    let context_payer = rpc.get_payer().insecure_clone();
1094    let (event, _signature, _) = rpc
1095        .create_and_send_transaction_with_event::<PublicTransactionEvent>(
1096            &[instruction],
1097            &context_payer.pubkey(),
1098            &[&context_payer, authority],
1099            transaction_params,
1100        )
1101        .await
1102        .unwrap()
1103        .unwrap();
1104    let (_, created_output_accounts) = test_indexer.add_event_and_compressed_accounts(&event);
1105
1106    let mut delegates = Vec::new();
1107    let mut expected_output_accounts = Vec::new();
1108    for account in input_compressed_accounts.iter() {
1109        let state = if FREEZE {
1110            AccountState::Frozen
1111        } else {
1112            AccountState::Initialized
1113        };
1114        let expected_token_data = TokenData {
1115            mint,
1116            owner: input_compressed_accounts[0].token_data.owner,
1117            amount: account.token_data.amount,
1118            delegate: account.token_data.delegate,
1119            state,
1120            tlv: None,
1121        };
1122        if let Some(delegate) = account.token_data.delegate {
1123            delegates.push(Some(delegate));
1124        } else {
1125            delegates.push(None);
1126        }
1127        expected_output_accounts.push(expected_token_data);
1128    }
1129    let expected_compressed_output_accounts =
1130        create_expected_token_output_data(expected_output_accounts, &output_merkle_tree_pubkeys);
1131    let sum_inputs = input_compressed_accounts
1132        .iter()
1133        .map(|x| x.compressed_account.compressed_account.lamports)
1134        .sum::<u64>();
1135    let change_lamports = if sum_inputs > 0 {
1136        let mut change_lamports = Vec::new();
1137        for account in input_compressed_accounts.iter() {
1138            if account.compressed_account.compressed_account.lamports > 0 {
1139                change_lamports.push(Some(account.compressed_account.compressed_account.lamports));
1140            } else {
1141                change_lamports.push(None);
1142            }
1143        }
1144        Some(change_lamports)
1145    } else {
1146        None
1147    };
1148    assert_transfer(
1149        rpc,
1150        test_indexer,
1151        expected_compressed_output_accounts.as_slice(),
1152        created_output_accounts
1153            .iter()
1154            .map(|x| x.compressed_account.clone())
1155            .collect::<Vec<_>>()
1156            .as_slice(),
1157        change_lamports,
1158        input_compressed_account_hashes.as_slice(),
1159        &output_merkle_tree_test_snapshots,
1160        &input_merkle_tree_test_snapshots,
1161        &event,
1162        Some(delegates),
1163    )
1164    .await;
1165}
1166
1167#[allow(clippy::too_many_arguments)]
1168pub async fn burn_test<R: RpcConnection, I: Indexer<R>>(
1169    authority: &Keypair,
1170    rpc: &mut R,
1171    test_indexer: &mut I,
1172    input_compressed_accounts: Vec<TokenDataWithContext>,
1173    change_account_merkle_tree: &Pubkey,
1174    burn_amount: u64,
1175    signer_is_delegate: bool,
1176    transaction_params: Option<TransactionParams>,
1177) {
1178    let (
1179        input_compressed_account_hashes,
1180        input_merkle_tree_pubkeys,
1181        mint,
1182        output_amount,
1183        instruction,
1184    ) = create_burn_test_instruction(
1185        authority,
1186        rpc,
1187        test_indexer,
1188        &input_compressed_accounts,
1189        change_account_merkle_tree,
1190        burn_amount,
1191        signer_is_delegate,
1192        BurnInstructionMode::Normal,
1193    )
1194    .await;
1195    let output_merkle_tree_pubkeys = vec![*change_account_merkle_tree; 1];
1196    let output_merkle_tree_test_snapshots = if output_amount > 0 {
1197        let output_merkle_tree_accounts =
1198            test_indexer.get_state_merkle_tree_accounts(&output_merkle_tree_pubkeys);
1199
1200        get_merkle_tree_snapshots::<R>(rpc, output_merkle_tree_accounts.as_slice()).await
1201    } else {
1202        Vec::new()
1203    };
1204
1205    let token_pool_pda_address = get_token_pool_pda(&mint);
1206    let pre_token_pool_account = rpc
1207        .get_account(token_pool_pda_address)
1208        .await
1209        .unwrap()
1210        .unwrap();
1211    let pre_token_pool_balance = spl_token::state::Account::unpack(&pre_token_pool_account.data)
1212        .unwrap()
1213        .amount;
1214
1215    let input_merkle_tree_accounts =
1216        test_indexer.get_state_merkle_tree_accounts(&input_merkle_tree_pubkeys);
1217    let input_merkle_tree_test_snapshots =
1218        get_merkle_tree_snapshots::<R>(rpc, input_merkle_tree_accounts.as_slice()).await;
1219    let context_payer = rpc.get_payer().insecure_clone();
1220    let (event, _signature, _) = rpc
1221        .create_and_send_transaction_with_event::<PublicTransactionEvent>(
1222            &[instruction],
1223            &context_payer.pubkey(),
1224            &[&context_payer, authority],
1225            transaction_params,
1226        )
1227        .await
1228        .unwrap()
1229        .unwrap();
1230    let (_, created_output_accounts) = test_indexer.add_event_and_compressed_accounts(&event);
1231    let mut delegates = Vec::new();
1232    let mut expected_output_accounts = Vec::new();
1233
1234    let delegate = if signer_is_delegate {
1235        Some(authority.pubkey())
1236    } else {
1237        None
1238    };
1239    if output_amount > 0 {
1240        let expected_token_data = TokenData {
1241            mint,
1242            owner: input_compressed_accounts[0].token_data.owner,
1243            amount: output_amount,
1244            delegate,
1245            state: AccountState::Initialized,
1246            tlv: None,
1247        };
1248        if let Some(delegate) = expected_token_data.delegate {
1249            delegates.push(Some(delegate));
1250        } else {
1251            delegates.push(None);
1252        }
1253        expected_output_accounts.push(expected_token_data);
1254    }
1255    let expected_compressed_output_accounts =
1256        create_expected_token_output_data(expected_output_accounts, &output_merkle_tree_pubkeys);
1257    let sum_inputs = input_compressed_accounts
1258        .iter()
1259        .map(|x| x.compressed_account.compressed_account.lamports)
1260        .sum::<u64>();
1261    let change_lamports = if sum_inputs > 0 {
1262        Some(vec![Some(sum_inputs)])
1263    } else {
1264        None
1265    };
1266    assert_transfer(
1267        rpc,
1268        test_indexer,
1269        expected_compressed_output_accounts.as_slice(),
1270        created_output_accounts
1271            .iter()
1272            .map(|x| x.compressed_account.clone())
1273            .collect::<Vec<_>>()
1274            .as_slice(),
1275        change_lamports,
1276        input_compressed_account_hashes.as_slice(),
1277        &output_merkle_tree_test_snapshots,
1278        &input_merkle_tree_test_snapshots,
1279        &event,
1280        Some(delegates),
1281    )
1282    .await;
1283    let post_token_pool_account = rpc
1284        .get_account(token_pool_pda_address)
1285        .await
1286        .unwrap()
1287        .unwrap();
1288    let post_token_pool_balance = spl_token::state::Account::unpack(&post_token_pool_account.data)
1289        .unwrap()
1290        .amount;
1291    assert_eq!(
1292        post_token_pool_balance,
1293        pre_token_pool_balance - burn_amount
1294    );
1295}
1296
1297#[derive(Debug, Clone, PartialEq)]
1298pub enum BurnInstructionMode {
1299    Normal,
1300    InvalidProof,
1301    InvalidMint,
1302}
1303
1304#[allow(clippy::too_many_arguments)]
1305pub async fn create_burn_test_instruction<R: RpcConnection, I: Indexer<R>>(
1306    authority: &Keypair,
1307    rpc: &mut R,
1308    test_indexer: &mut I,
1309    input_compressed_accounts: &[TokenDataWithContext],
1310    change_account_merkle_tree: &Pubkey,
1311    burn_amount: u64,
1312    signer_is_delegate: bool,
1313    mode: BurnInstructionMode,
1314) -> (Vec<[u8; 32]>, Vec<Pubkey>, Pubkey, u64, Instruction) {
1315    let input_compressed_account_hashes = input_compressed_accounts
1316        .iter()
1317        .map(|x| x.compressed_account.hash().unwrap())
1318        .collect::<Vec<_>>();
1319    let input_merkle_tree_pubkeys = input_compressed_accounts
1320        .iter()
1321        .map(|x| x.compressed_account.merkle_context.merkle_tree_pubkey)
1322        .collect::<Vec<_>>();
1323    let proof_rpc_result = test_indexer
1324        .create_proof_for_compressed_accounts(
1325            Some(&input_compressed_account_hashes),
1326            Some(&input_merkle_tree_pubkeys),
1327            None,
1328            None,
1329            rpc,
1330        )
1331        .await;
1332    let mint = if mode == BurnInstructionMode::InvalidMint {
1333        Pubkey::new_unique()
1334    } else {
1335        input_compressed_accounts[0].token_data.mint
1336    };
1337    let proof = if mode == BurnInstructionMode::InvalidProof {
1338        CompressedProof {
1339            a: proof_rpc_result.proof.a,
1340            b: proof_rpc_result.proof.b,
1341            c: proof_rpc_result.proof.a, // flip c to make proof invalid but not run into decompress errors
1342        }
1343    } else {
1344        proof_rpc_result.proof
1345    };
1346    let inputs = CreateBurnInstructionInputs {
1347        fee_payer: rpc.get_payer().pubkey(),
1348        authority: authority.pubkey(),
1349        input_merkle_contexts: input_compressed_accounts
1350            .iter()
1351            .map(|x| x.compressed_account.merkle_context)
1352            .collect(),
1353        input_token_data: input_compressed_accounts
1354            .iter()
1355            .map(|x| x.token_data.clone())
1356            .collect(),
1357        input_compressed_accounts: input_compressed_accounts
1358            .iter()
1359            .map(|x| &x.compressed_account.compressed_account)
1360            .cloned()
1361            .collect::<Vec<_>>(),
1362        change_account_merkle_tree: *change_account_merkle_tree,
1363        root_indices: proof_rpc_result.root_indices,
1364        proof,
1365        mint,
1366        signer_is_delegate,
1367        burn_amount,
1368    };
1369    let input_amount_sum = input_compressed_accounts
1370        .iter()
1371        .map(|x| x.token_data.amount)
1372        .sum::<u64>();
1373    let output_amount = input_amount_sum - burn_amount;
1374    let instruction = create_burn_instruction(inputs).unwrap();
1375    (
1376        input_compressed_account_hashes,
1377        input_merkle_tree_pubkeys,
1378        mint,
1379        output_amount,
1380        instruction,
1381    )
1382}
1383
1384pub fn create_expected_token_output_data(
1385    expected_token_data: Vec<TokenData>,
1386    merkle_tree_pubkeys: &[Pubkey],
1387) -> Vec<TokenTransferOutputData> {
1388    let mut expected_compressed_output_accounts = Vec::new();
1389    for (token_data, merkle_tree_pubkey) in
1390        expected_token_data.iter().zip(merkle_tree_pubkeys.iter())
1391    {
1392        expected_compressed_output_accounts.push(TokenTransferOutputData {
1393            owner: token_data.owner,
1394            amount: token_data.amount,
1395            merkle_tree: *merkle_tree_pubkey,
1396            lamports: None,
1397        });
1398    }
1399    expected_compressed_output_accounts
1400}