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#[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 pub delegate_merkle_tree_index: u8,
32 pub change_account_merkle_tree_index: u8,
34 pub delegate_lamports: Option<u64>,
35}
36
37pub 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 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 #[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), 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), 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), 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), 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}