light_compressed_token/
process_mint.rs

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