light_compressed_token/
delegation.rs

1use anchor_lang::prelude::*;
2use light_compressed_account::{
3    hash_to_bn254_field_size_be,
4    instruction_data::{
5        compressed_proof::CompressedProof, cpi_context::CompressedCpiContext,
6        data::OutputCompressedAccountWithPackedContext, with_readonly::InAccount,
7    },
8};
9
10use crate::{
11    constants::NOT_FROZEN,
12    process_transfer::{
13        add_data_hash_to_input_compressed_accounts, cpi_execute_compressed_transaction_transfer,
14        create_output_compressed_accounts,
15        get_input_compressed_accounts_with_merkle_context_and_check_signer,
16        InputTokenDataWithContext,
17    },
18    ErrorCode, GenericInstruction,
19};
20
21// TODO: add instruction which accepts an optional proof
22#[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize)]
23pub struct CompressedTokenInstructionDataApprove {
24    pub proof: CompressedProof,
25    pub mint: Pubkey,
26    pub input_token_data_with_context: Vec<InputTokenDataWithContext>,
27    pub cpi_context: Option<CompressedCpiContext>,
28    pub delegate: Pubkey,
29    pub delegated_amount: u64,
30    /// Index in remaining accounts.
31    pub delegate_merkle_tree_index: u8,
32    /// Index in remaining accounts.
33    pub change_account_merkle_tree_index: u8,
34    pub delegate_lamports: Option<u64>,
35}
36
37/// Processes an approve instruction.
38/// - creates an output compressed acount which is delegated to the delegate.
39/// - creates a change account for the remaining amount (sum inputs - delegated amount).
40/// - ignores prior delegations.
41/// 1. unpack instruction data and input compressed accounts
42/// 2. calculate change amount
43/// 3. create output compressed accounts
44/// 4. pack token data into input compressed accounts
45/// 5. execute compressed transaction
46pub fn process_approve<'a, 'b, 'c, 'info: 'b + 'c>(
47    ctx: Context<'a, 'b, 'c, 'info, GenericInstruction<'info>>,
48    inputs: Vec<u8>,
49) -> Result<()> {
50    let inputs: CompressedTokenInstructionDataApprove =
51        CompressedTokenInstructionDataApprove::deserialize(&mut inputs.as_slice())?;
52    let (compressed_input_accounts, output_compressed_accounts) =
53        create_input_and_output_accounts_approve(
54            &inputs,
55            &ctx.accounts.authority.key(),
56            ctx.remaining_accounts,
57        )?;
58    let proof = if inputs.proof == CompressedProof::default() {
59        None
60    } else {
61        Some(inputs.proof)
62    };
63    cpi_execute_compressed_transaction_transfer(
64        ctx.accounts,
65        compressed_input_accounts,
66        output_compressed_accounts,
67        false,
68        proof,
69        inputs.cpi_context,
70        ctx.accounts.cpi_authority_pda.to_account_info(),
71        ctx.accounts.light_system_program.to_account_info(),
72        ctx.accounts.self_program.to_account_info(),
73        ctx.remaining_accounts,
74    )
75}
76
77pub fn create_input_and_output_accounts_approve(
78    inputs: &CompressedTokenInstructionDataApprove,
79    authority: &Pubkey,
80    remaining_accounts: &[AccountInfo<'_>],
81) -> Result<(
82    Vec<InAccount>,
83    Vec<OutputCompressedAccountWithPackedContext>,
84)> {
85    if inputs.input_token_data_with_context.is_empty() {
86        return err!(ErrorCode::NoInputTokenAccountsProvided);
87    }
88    let (mut compressed_input_accounts, input_token_data, sum_lamports) =
89        get_input_compressed_accounts_with_merkle_context_and_check_signer::<NOT_FROZEN>(
90            authority,
91            &None,
92            remaining_accounts,
93            &inputs.input_token_data_with_context,
94            &inputs.mint,
95        )?;
96    let sum_inputs = input_token_data.iter().map(|x| x.amount).sum::<u64>();
97    let change_amount = match sum_inputs.checked_sub(inputs.delegated_amount) {
98        Some(change_amount) => change_amount,
99        None => return err!(ErrorCode::ArithmeticUnderflow),
100    };
101
102    let delegated_lamports = inputs.delegate_lamports.unwrap_or(0);
103    let change_lamports = match sum_lamports.checked_sub(delegated_lamports) {
104        Some(change_lamports) => change_lamports,
105        None => return err!(ErrorCode::ArithmeticUnderflow),
106    };
107
108    let hashed_mint = hash_to_bn254_field_size_be(&inputs.mint.to_bytes());
109
110    let lamports = if sum_lamports != 0 {
111        let change_lamports = if change_lamports != 0 {
112            Some(change_lamports)
113        } else {
114            None
115        };
116        Some(vec![inputs.delegate_lamports, change_lamports])
117    } else {
118        None
119    };
120    // Only create outputs if the change amount is not zero.
121    let (
122        mut output_compressed_accounts,
123        pubkeys,
124        is_delegate,
125        amounts,
126        lamports,
127        merkle_tree_indices,
128    ) = if change_amount > 0 || change_lamports > 0 {
129        (
130            vec![OutputCompressedAccountWithPackedContext::default(); 2],
131            vec![*authority, *authority],
132            Some(vec![true, false]),
133            vec![inputs.delegated_amount, change_amount],
134            lamports,
135            vec![
136                inputs.delegate_merkle_tree_index,
137                inputs.change_account_merkle_tree_index,
138            ],
139        )
140    } else {
141        (
142            vec![OutputCompressedAccountWithPackedContext::default(); 1],
143            vec![*authority],
144            Some(vec![true]),
145            vec![inputs.delegated_amount],
146            lamports,
147            vec![inputs.delegate_merkle_tree_index],
148        )
149    };
150    create_output_compressed_accounts(
151        &mut output_compressed_accounts,
152        inputs.mint,
153        pubkeys.as_slice(),
154        Some(inputs.delegate),
155        is_delegate,
156        amounts.as_slice(),
157        lamports,
158        &hashed_mint,
159        &merkle_tree_indices,
160        remaining_accounts,
161    )?;
162    add_data_hash_to_input_compressed_accounts::<NOT_FROZEN>(
163        &mut compressed_input_accounts,
164        input_token_data.as_slice(),
165        &hashed_mint,
166        remaining_accounts,
167    )?;
168    Ok((compressed_input_accounts, output_compressed_accounts))
169}
170
171#[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize)]
172pub struct CompressedTokenInstructionDataRevoke {
173    pub proof: CompressedProof,
174    pub mint: Pubkey,
175    pub input_token_data_with_context: Vec<InputTokenDataWithContext>,
176    pub cpi_context: Option<CompressedCpiContext>,
177    pub output_account_merkle_tree_index: u8,
178}
179
180pub fn process_revoke<'a, 'b, 'c, 'info: 'b + 'c>(
181    ctx: Context<'a, 'b, 'c, 'info, GenericInstruction<'info>>,
182    inputs: Vec<u8>,
183) -> Result<()> {
184    let inputs: CompressedTokenInstructionDataRevoke =
185        CompressedTokenInstructionDataRevoke::deserialize(&mut inputs.as_slice())?;
186    let (compressed_input_accounts, output_compressed_accounts) =
187        create_input_and_output_accounts_revoke(
188            &inputs,
189            &ctx.accounts.authority.key(),
190            ctx.remaining_accounts,
191        )?;
192    let proof = if inputs.proof == CompressedProof::default() {
193        None
194    } else {
195        Some(inputs.proof)
196    };
197    cpi_execute_compressed_transaction_transfer(
198        ctx.accounts,
199        compressed_input_accounts,
200        output_compressed_accounts,
201        false,
202        proof,
203        inputs.cpi_context,
204        ctx.accounts.cpi_authority_pda.to_account_info(),
205        ctx.accounts.light_system_program.to_account_info(),
206        ctx.accounts.self_program.to_account_info(),
207        ctx.remaining_accounts,
208    )?;
209    Ok(())
210}
211
212pub fn create_input_and_output_accounts_revoke(
213    inputs: &CompressedTokenInstructionDataRevoke,
214    authority: &Pubkey,
215    remaining_accounts: &[AccountInfo<'_>],
216) -> Result<(
217    Vec<InAccount>,
218    Vec<OutputCompressedAccountWithPackedContext>,
219)> {
220    if inputs.input_token_data_with_context.is_empty() {
221        return err!(ErrorCode::NoInputTokenAccountsProvided);
222    }
223    let (mut compressed_input_accounts, input_token_data, sum_lamports) =
224        get_input_compressed_accounts_with_merkle_context_and_check_signer::<NOT_FROZEN>(
225            authority,
226            &None,
227            remaining_accounts,
228            &inputs.input_token_data_with_context,
229            &inputs.mint,
230        )?;
231    let sum_inputs = input_token_data.iter().map(|x| x.amount).sum::<u64>();
232    let lamports = if sum_lamports != 0 {
233        Some(vec![Some(sum_lamports)])
234    } else {
235        None
236    };
237    let mut output_compressed_accounts =
238        vec![OutputCompressedAccountWithPackedContext::default(); 1];
239    let hashed_mint = hash_to_bn254_field_size_be(&inputs.mint.to_bytes());
240
241    create_output_compressed_accounts(
242        &mut output_compressed_accounts,
243        inputs.mint,
244        &[*authority; 1],
245        None,
246        None,
247        &[sum_inputs],
248        lamports,
249        &hashed_mint,
250        &[inputs.output_account_merkle_tree_index],
251        remaining_accounts,
252    )?;
253    add_data_hash_to_input_compressed_accounts::<NOT_FROZEN>(
254        &mut compressed_input_accounts,
255        input_token_data.as_slice(),
256        &hashed_mint,
257        remaining_accounts,
258    )?;
259    Ok((compressed_input_accounts, output_compressed_accounts))
260}
261
262#[cfg(not(target_os = "solana"))]
263pub mod sdk {
264
265    use std::result::Result;
266
267    use anchor_lang::{AnchorSerialize, InstructionData, ToAccountMetas};
268    use light_compressed_account::compressed_account::{CompressedAccount, MerkleContext};
269    use solana_sdk::{instruction::Instruction, pubkey::Pubkey};
270
271    use super::*;
272    use crate::{
273        process_transfer::{
274            get_cpi_authority_pda,
275            transfer_sdk::{
276                create_input_output_and_remaining_accounts, to_account_metas, TransferSdkError,
277            },
278        },
279        token_data::TokenData,
280    };
281
282    pub struct CreateApproveInstructionInputs {
283        pub fee_payer: Pubkey,
284        pub authority: Pubkey,
285        pub root_indices: Vec<Option<u16>>,
286        pub proof: CompressedProof,
287        pub input_token_data: Vec<TokenData>,
288        pub input_compressed_accounts: Vec<CompressedAccount>,
289        pub input_merkle_contexts: Vec<MerkleContext>,
290        pub mint: Pubkey,
291        pub delegated_amount: u64,
292        pub delegate_lamports: Option<u64>,
293        pub delegated_compressed_account_merkle_tree: Pubkey,
294        pub change_compressed_account_merkle_tree: Pubkey,
295        pub delegate: Pubkey,
296    }
297
298    pub fn create_approve_instruction(
299        inputs: CreateApproveInstructionInputs,
300    ) -> Result<Instruction, TransferSdkError> {
301        let (remaining_accounts, input_token_data_with_context, _) =
302            create_input_output_and_remaining_accounts(
303                &[
304                    inputs.delegated_compressed_account_merkle_tree,
305                    inputs.change_compressed_account_merkle_tree,
306                ],
307                &inputs.input_token_data,
308                &inputs.input_compressed_accounts,
309                &inputs.input_merkle_contexts,
310                &inputs.root_indices,
311                &Vec::new(),
312            );
313        let delegated_merkle_tree_index =
314            match remaining_accounts.get(&inputs.delegated_compressed_account_merkle_tree) {
315                Some(delegated_merkle_tree_index) => delegated_merkle_tree_index,
316                None => return Err(TransferSdkError::AccountNotFound),
317            };
318        let change_account_merkle_tree_index =
319            match remaining_accounts.get(&inputs.change_compressed_account_merkle_tree) {
320                Some(change_account_merkle_tree_index) => change_account_merkle_tree_index,
321                None => return Err(TransferSdkError::AccountNotFound),
322            };
323        let inputs_struct = CompressedTokenInstructionDataApprove {
324            proof: inputs.proof,
325            mint: inputs.mint,
326            input_token_data_with_context,
327            cpi_context: None,
328            delegate: inputs.delegate,
329            delegated_amount: inputs.delegated_amount,
330            delegate_merkle_tree_index: *delegated_merkle_tree_index as u8,
331            change_account_merkle_tree_index: *change_account_merkle_tree_index as u8,
332            delegate_lamports: inputs.delegate_lamports,
333        };
334        let remaining_accounts = to_account_metas(remaining_accounts);
335        let mut serialized_ix_data = Vec::new();
336        CompressedTokenInstructionDataApprove::serialize(&inputs_struct, &mut serialized_ix_data)
337            .map_err(|_| TransferSdkError::SerializationError)?;
338
339        let (cpi_authority_pda, _) = get_cpi_authority_pda();
340        let instruction_data = crate::instruction::Approve {
341            inputs: serialized_ix_data,
342        };
343
344        let accounts = crate::accounts::GenericInstruction {
345            fee_payer: inputs.fee_payer,
346            authority: inputs.authority,
347            cpi_authority_pda,
348            light_system_program: light_system_program::ID,
349            registered_program_pda: light_system_program::utils::get_registered_program_pda(
350                &light_system_program::ID,
351            ),
352            noop_program: Pubkey::new_from_array(
353                account_compression::utils::constants::NOOP_PUBKEY,
354            ),
355            account_compression_authority: light_system_program::utils::get_cpi_authority_pda(
356                &light_system_program::ID,
357            ),
358            account_compression_program: account_compression::ID,
359            self_program: crate::ID,
360            system_program: solana_sdk::system_program::ID,
361        };
362
363        Ok(Instruction {
364            program_id: crate::ID,
365            accounts: [accounts.to_account_metas(Some(true)), remaining_accounts].concat(),
366
367            data: instruction_data.data(),
368        })
369    }
370
371    pub struct CreateRevokeInstructionInputs {
372        pub fee_payer: Pubkey,
373        pub authority: Pubkey,
374        pub root_indices: Vec<Option<u16>>,
375        pub proof: CompressedProof,
376        pub input_token_data: Vec<TokenData>,
377        pub input_compressed_accounts: Vec<CompressedAccount>,
378        pub input_merkle_contexts: Vec<MerkleContext>,
379        pub mint: Pubkey,
380        pub output_account_merkle_tree: Pubkey,
381    }
382
383    pub fn create_revoke_instruction(
384        inputs: CreateRevokeInstructionInputs,
385    ) -> Result<Instruction, TransferSdkError> {
386        let (remaining_accounts, input_token_data_with_context, _) =
387            create_input_output_and_remaining_accounts(
388                &[inputs.output_account_merkle_tree],
389                &inputs.input_token_data,
390                &inputs.input_compressed_accounts,
391                &inputs.input_merkle_contexts,
392                &inputs.root_indices,
393                &Vec::new(),
394            );
395        let output_account_merkle_tree_index =
396            match remaining_accounts.get(&inputs.output_account_merkle_tree) {
397                Some(output_account_merkle_tree_index) => output_account_merkle_tree_index,
398                None => return Err(TransferSdkError::AccountNotFound),
399            };
400
401        let inputs_struct = CompressedTokenInstructionDataRevoke {
402            proof: inputs.proof,
403            mint: inputs.mint,
404            input_token_data_with_context,
405            cpi_context: None,
406            output_account_merkle_tree_index: *output_account_merkle_tree_index as u8,
407        };
408        let remaining_accounts = to_account_metas(remaining_accounts);
409        let mut serialized_ix_data = Vec::new();
410        CompressedTokenInstructionDataRevoke::serialize(&inputs_struct, &mut serialized_ix_data)
411            .map_err(|_| TransferSdkError::SerializationError)?;
412
413        let (cpi_authority_pda, _) = get_cpi_authority_pda();
414        let instruction_data = crate::instruction::Revoke {
415            inputs: serialized_ix_data,
416        };
417
418        let accounts = crate::accounts::GenericInstruction {
419            fee_payer: inputs.fee_payer,
420            authority: inputs.authority,
421            cpi_authority_pda,
422            light_system_program: light_system_program::ID,
423            registered_program_pda: light_system_program::utils::get_registered_program_pda(
424                &light_system_program::ID,
425            ),
426            noop_program: Pubkey::new_from_array(
427                account_compression::utils::constants::NOOP_PUBKEY,
428            ),
429            account_compression_authority: light_system_program::utils::get_cpi_authority_pda(
430                &light_system_program::ID,
431            ),
432            account_compression_program: account_compression::ID,
433            self_program: crate::ID,
434            system_program: solana_sdk::system_program::ID,
435        };
436
437        Ok(Instruction {
438            program_id: crate::ID,
439            accounts: [accounts.to_account_metas(Some(true)), remaining_accounts].concat(),
440
441            data: instruction_data.data(),
442        })
443    }
444}
445
446#[cfg(test)]
447mod test {
448    use account_compression::StateMerkleTreeAccount;
449    use anchor_lang::{solana_program::account_info::AccountInfo, Discriminator};
450    use light_compressed_account::compressed_account::PackedMerkleContext;
451
452    use super::*;
453    use crate::{
454        freeze::test_freeze::create_expected_token_output_accounts, token_data::AccountState,
455        TokenData,
456    };
457
458    // TODO: add randomized and edge case tests
459    #[test]
460    fn test_approve() {
461        let merkle_tree_pubkey = Pubkey::new_unique();
462        let mut merkle_tree_account_lamports = 0;
463        let mut merkle_tree_account_data = StateMerkleTreeAccount::DISCRIMINATOR.to_vec();
464        let merkle_tree_pubkey_1 = Pubkey::new_unique();
465        let mut merkle_tree_account_lamports_1 = 0;
466        let mut merkle_tree_account_data_1 = StateMerkleTreeAccount::DISCRIMINATOR.to_vec();
467        let nullifier_queue_pubkey = Pubkey::new_unique();
468        let mut nullifier_queue_account_lamports = 0;
469        let mut nullifier_queue_account_data = Vec::new();
470        let remaining_accounts = vec![
471            AccountInfo::new(
472                &merkle_tree_pubkey,
473                false,
474                false,
475                &mut merkle_tree_account_lamports,
476                &mut merkle_tree_account_data,
477                &account_compression::ID,
478                false,
479                0,
480            ),
481            AccountInfo::new(
482                &nullifier_queue_pubkey,
483                false,
484                false,
485                &mut nullifier_queue_account_lamports,
486                &mut nullifier_queue_account_data,
487                &account_compression::ID,
488                false,
489                0,
490            ),
491            AccountInfo::new(
492                &merkle_tree_pubkey_1,
493                false,
494                false,
495                &mut merkle_tree_account_lamports_1,
496                &mut merkle_tree_account_data_1,
497                &account_compression::ID,
498                false,
499                0,
500            ),
501        ];
502        let authority = Pubkey::new_unique();
503        let mint = Pubkey::new_unique();
504        let delegate = Pubkey::new_unique();
505        let input_token_data_with_context = vec![
506            InputTokenDataWithContext {
507                amount: 100,
508                merkle_context: PackedMerkleContext {
509                    merkle_tree_pubkey_index: 0,
510                    queue_pubkey_index: 1,
511                    leaf_index: 1,
512                    prove_by_index: false,
513                },
514                root_index: 0,
515                delegate_index: Some(1),
516                lamports: None,
517                tlv: None,
518            },
519            InputTokenDataWithContext {
520                amount: 101,
521                merkle_context: PackedMerkleContext {
522                    merkle_tree_pubkey_index: 0,
523                    queue_pubkey_index: 1,
524                    leaf_index: 2,
525                    prove_by_index: false,
526                },
527                root_index: 0,
528                delegate_index: None,
529                lamports: None,
530                tlv: None,
531            },
532        ];
533        let inputs = CompressedTokenInstructionDataApprove {
534            proof: CompressedProof::default(),
535            mint,
536            input_token_data_with_context,
537            cpi_context: None,
538            delegate,
539            delegated_amount: 50,
540            delegate_merkle_tree_index: 0,
541            change_account_merkle_tree_index: 2,
542            delegate_lamports: None,
543        };
544        let (compressed_input_accounts, output_compressed_accounts) =
545            create_input_and_output_accounts_approve(&inputs, &authority, &remaining_accounts)
546                .unwrap();
547        assert_eq!(compressed_input_accounts.len(), 2);
548        assert_eq!(output_compressed_accounts.len(), 2);
549        let expected_change_token_data = TokenData {
550            mint,
551            owner: authority,
552            amount: 151,
553            delegate: None,
554            state: AccountState::Initialized,
555            tlv: None,
556        };
557        let expected_delegated_token_data = TokenData {
558            mint,
559            owner: authority,
560            amount: 50,
561            delegate: Some(delegate),
562            state: AccountState::Initialized,
563            tlv: None,
564        };
565        let expected_compressed_output_accounts = create_expected_token_output_accounts(
566            vec![expected_delegated_token_data, expected_change_token_data],
567            vec![0, 2],
568        );
569
570        assert_eq!(
571            output_compressed_accounts,
572            expected_compressed_output_accounts
573        );
574    }
575
576    #[test]
577    fn test_revoke() {
578        let merkle_tree_pubkey = Pubkey::new_unique();
579        let mut merkle_tree_account_lamports = 0;
580        let mut merkle_tree_account_data = StateMerkleTreeAccount::DISCRIMINATOR.to_vec();
581        let nullifier_queue_pubkey = Pubkey::new_unique();
582        let mut nullifier_queue_account_lamports = 0;
583        let mut nullifier_queue_account_data = Vec::new();
584        let merkle_tree_pubkey_1 = Pubkey::new_unique();
585        let mut merkle_tree_account_lamports_1 = 0;
586        let mut merkle_tree_account_data_1 = StateMerkleTreeAccount::DISCRIMINATOR.to_vec();
587        let remaining_accounts = vec![
588            AccountInfo::new(
589                &merkle_tree_pubkey,
590                false,
591                false,
592                &mut merkle_tree_account_lamports,
593                &mut merkle_tree_account_data,
594                &account_compression::ID,
595                false,
596                0,
597            ),
598            AccountInfo::new(
599                &nullifier_queue_pubkey,
600                false,
601                false,
602                &mut nullifier_queue_account_lamports,
603                &mut nullifier_queue_account_data,
604                &account_compression::ID,
605                false,
606                0,
607            ),
608            AccountInfo::new(
609                &merkle_tree_pubkey_1,
610                false,
611                false,
612                &mut merkle_tree_account_lamports_1,
613                &mut merkle_tree_account_data_1,
614                &account_compression::ID,
615                false,
616                0,
617            ),
618        ];
619        let authority = Pubkey::new_unique();
620        let mint = Pubkey::new_unique();
621
622        let input_token_data_with_context = vec![
623            InputTokenDataWithContext {
624                amount: 100,
625
626                merkle_context: PackedMerkleContext {
627                    merkle_tree_pubkey_index: 0,
628                    queue_pubkey_index: 1,
629                    leaf_index: 1,
630                    prove_by_index: false,
631                },
632                root_index: 0,
633                delegate_index: Some(1), // Doesn't matter it is not checked if the proof is not verified
634                lamports: None,
635                tlv: None,
636            },
637            InputTokenDataWithContext {
638                amount: 101,
639
640                merkle_context: PackedMerkleContext {
641                    merkle_tree_pubkey_index: 0,
642                    queue_pubkey_index: 1,
643                    leaf_index: 2,
644                    prove_by_index: false,
645                },
646                root_index: 0,
647                delegate_index: Some(1), // Doesn't matter it is not checked if the proof is not verified
648                lamports: None,
649                tlv: None,
650            },
651        ];
652        let inputs = CompressedTokenInstructionDataRevoke {
653            proof: CompressedProof::default(),
654            mint,
655            input_token_data_with_context,
656            cpi_context: None,
657            output_account_merkle_tree_index: 2,
658        };
659        let (compressed_input_accounts, output_compressed_accounts) =
660            create_input_and_output_accounts_revoke(&inputs, &authority, &remaining_accounts)
661                .unwrap();
662        assert_eq!(compressed_input_accounts.len(), 2);
663        assert_eq!(output_compressed_accounts.len(), 1);
664        let expected_change_token_data = TokenData {
665            mint,
666            owner: authority,
667            amount: 201,
668            delegate: None,
669            state: AccountState::Initialized,
670            tlv: None,
671        };
672        let expected_compressed_output_accounts =
673            create_expected_token_output_accounts(vec![expected_change_token_data], vec![2]);
674        assert_eq!(
675            output_compressed_accounts,
676            expected_compressed_output_accounts
677        );
678
679        let lamports_amount = 2;
680
681        let input_token_data_with_context = vec![
682            InputTokenDataWithContext {
683                amount: 100,
684
685                merkle_context: PackedMerkleContext {
686                    merkle_tree_pubkey_index: 0,
687                    queue_pubkey_index: 1,
688                    leaf_index: 1,
689                    prove_by_index: false,
690                },
691                root_index: 0,
692                delegate_index: Some(1), // Doesn't matter it is not checked if the proof is not verified
693                lamports: Some(lamports_amount / 2),
694                tlv: None,
695            },
696            InputTokenDataWithContext {
697                amount: 101,
698
699                merkle_context: PackedMerkleContext {
700                    merkle_tree_pubkey_index: 0,
701                    queue_pubkey_index: 1,
702                    leaf_index: 2,
703                    prove_by_index: false,
704                },
705                root_index: 0,
706                delegate_index: Some(1), // Doesn't matter it is not checked if the proof is not verified
707                lamports: Some(lamports_amount / 2),
708                tlv: None,
709            },
710        ];
711        let inputs = CompressedTokenInstructionDataRevoke {
712            proof: CompressedProof::default(),
713            mint,
714            input_token_data_with_context,
715            cpi_context: None,
716            output_account_merkle_tree_index: 2,
717        };
718        let (compressed_input_accounts, output_compressed_accounts) =
719            create_input_and_output_accounts_revoke(&inputs, &authority, &remaining_accounts)
720                .unwrap();
721        assert_eq!(compressed_input_accounts.len(), 2);
722        assert_eq!(output_compressed_accounts.len(), 1);
723        let expected_change_token_data = TokenData {
724            mint,
725            owner: authority,
726            amount: 201,
727            delegate: None,
728            state: AccountState::Initialized,
729            tlv: None,
730        };
731        let mut expected_compressed_output_accounts =
732            create_expected_token_output_accounts(vec![expected_change_token_data], vec![2]);
733        expected_compressed_output_accounts[0]
734            .compressed_account
735            .lamports = lamports_amount;
736        assert_eq!(
737            output_compressed_accounts,
738            expected_compressed_output_accounts
739        );
740    }
741}