light_compressed_account/instruction_data/
with_account_info.rs

1use core::ops::{Deref, DerefMut};
2
3use light_zero_copy::{errors::ZeroCopyError, slice::ZeroCopySliceBorsh, traits::ZeroCopyAt};
4use zerocopy::{
5    little_endian::{U16, U32, U64},
6    FromBytes, Immutable, IntoBytes, KnownLayout, Ref, Unaligned,
7};
8
9use super::{
10    compressed_proof::CompressedProof,
11    cpi_context::CompressedCpiContext,
12    data::{NewAddressParamsAssignedPacked, PackedReadOnlyAddress},
13    traits::{AccountOptions, InputAccount, InstructionData, NewAddress, OutputAccount},
14    with_readonly::ZInstructionDataInvokeCpiWithReadOnlyMeta,
15    zero_copy::{
16        ZNewAddressParamsAssignedPacked, ZPackedMerkleContext, ZPackedReadOnlyAddress,
17        ZPackedReadOnlyCompressedAccount,
18    },
19};
20use crate::{
21    compressed_account::{
22        hash_with_hashed_values, CompressedAccount, CompressedAccountData, PackedMerkleContext,
23        PackedReadOnlyCompressedAccount,
24    },
25    discriminators::INVOKE_CPI_WITH_ACCOUNT_INFO_INSTRUCTION,
26    instruction_data::{
27        data::OutputCompressedAccountWithPackedContext, traits::LightInstructionData,
28        with_readonly::InAccount,
29    },
30    pubkey::Pubkey,
31    CompressedAccountError, InstructionDiscriminator, Vec,
32};
33
34#[cfg_attr(
35    all(feature = "std", feature = "anchor"),
36    derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
37)]
38#[cfg_attr(
39    not(feature = "anchor"),
40    derive(borsh::BorshDeserialize, borsh::BorshSerialize)
41)]
42#[derive(Debug, Default, PartialEq, Clone)]
43pub struct InAccountInfo {
44    pub discriminator: [u8; 8],
45    /// Data hash
46    pub data_hash: [u8; 32],
47    /// Merkle tree context.
48    pub merkle_context: PackedMerkleContext,
49    /// Root index.
50    pub root_index: u16,
51    /// Lamports.
52    pub lamports: u64,
53}
54
55impl From<InAccount> for InAccountInfo {
56    fn from(account: InAccount) -> Self {
57        Self {
58            discriminator: account.discriminator,
59            data_hash: account.data_hash,
60            merkle_context: account.merkle_context,
61            root_index: account.root_index,
62            lamports: account.lamports,
63        }
64    }
65}
66
67impl InAccountInfo {
68    pub fn into_in_account(&self, address: Option<[u8; 32]>) -> InAccount {
69        InAccount {
70            discriminator: self.discriminator,
71            data_hash: self.data_hash,
72            merkle_context: self.merkle_context,
73            root_index: self.root_index,
74            lamports: self.lamports,
75            address,
76        }
77    }
78}
79
80#[repr(C)]
81#[derive(
82    Debug, Default, PartialEq, Clone, Copy, FromBytes, IntoBytes, Unaligned, Immutable, KnownLayout,
83)]
84pub struct ZInAccountInfo {
85    pub discriminator: [u8; 8],
86    /// Data hash
87    pub data_hash: [u8; 32],
88    /// Merkle tree context.
89    pub merkle_context: ZPackedMerkleContext,
90    /// Root index.
91    pub root_index: U16,
92    /// Lamports.
93    pub lamports: U64,
94}
95
96#[cfg_attr(
97    all(feature = "std", feature = "anchor"),
98    derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
99)]
100#[cfg_attr(
101    not(feature = "anchor"),
102    derive(borsh::BorshDeserialize, borsh::BorshSerialize)
103)]
104#[derive(Debug, Default, PartialEq, Clone)]
105pub struct OutAccountInfo {
106    pub discriminator: [u8; 8],
107    /// Data hash
108    pub data_hash: [u8; 32],
109    pub output_merkle_tree_index: u8,
110    /// Lamports.
111    pub lamports: u64,
112    /// Account data.
113    pub data: Vec<u8>,
114}
115
116impl TryFrom<OutputCompressedAccountWithPackedContext> for OutAccountInfo {
117    type Error = CompressedAccountError;
118
119    fn try_from(output: OutputCompressedAccountWithPackedContext) -> Result<Self, Self::Error> {
120        let data = output
121            .compressed_account
122            .data
123            .as_ref()
124            .ok_or(CompressedAccountError::ExpectedDataHash)?;
125
126        Ok(Self {
127            discriminator: data.discriminator,
128            data_hash: data.data_hash,
129            output_merkle_tree_index: output.merkle_tree_index,
130            lamports: output.compressed_account.lamports,
131            data: data.data.clone(),
132        })
133    }
134}
135
136impl OutputCompressedAccountWithPackedContext {
137    pub fn from_with_owner(
138        info: &OutAccountInfo,
139        owner: Pubkey,
140        address: Option<[u8; 32]>,
141    ) -> Self {
142        Self {
143            compressed_account: CompressedAccount {
144                owner,
145                lamports: info.lamports,
146                address,
147                data: Some(CompressedAccountData {
148                    discriminator: info.discriminator,
149                    data: info.data.to_vec(),
150                    data_hash: info.data_hash,
151                }),
152            },
153            merkle_tree_index: info.output_merkle_tree_index,
154        }
155    }
156}
157
158impl<'a> InputAccount<'a> for ZCompressedAccountInfo<'a> {
159    fn owner(&self) -> &Pubkey {
160        &self.owner
161    }
162
163    fn skip(&self) -> bool {
164        self.input.is_none()
165    }
166
167    fn lamports(&self) -> u64 {
168        self.input.as_ref().unwrap().lamports.into()
169    }
170    fn address(&self) -> Option<[u8; 32]> {
171        self.address.map(|x| *x)
172    }
173
174    fn merkle_context(&self) -> ZPackedMerkleContext {
175        self.input.as_ref().unwrap().merkle_context
176    }
177
178    fn root_index(&self) -> u16 {
179        self.input.as_ref().unwrap().root_index.into()
180    }
181
182    fn has_data(&self) -> bool {
183        true
184    }
185
186    fn data(&self) -> Option<CompressedAccountData> {
187        Some(CompressedAccountData {
188            data_hash: self.input.unwrap().data_hash,
189            discriminator: self.input.unwrap().discriminator,
190            data: Vec::new(),
191        })
192    }
193
194    fn hash_with_hashed_values(
195        &self,
196        owner_hashed: &[u8; 32],
197        merkle_tree_hashed: &[u8; 32],
198        leaf_index: &u32,
199        is_batched: bool,
200    ) -> Result<[u8; 32], CompressedAccountError> {
201        let input = self.input.as_ref().unwrap();
202        let address_slice = self.address.as_ref().map(|x| x.as_slice());
203        hash_with_hashed_values(
204            &input.lamports.into(),
205            address_slice,
206            Some((input.discriminator.as_slice(), input.data_hash.as_slice())),
207            owner_hashed,
208            merkle_tree_hashed,
209            leaf_index,
210            is_batched,
211        )
212    }
213}
214
215impl<'a> OutputAccount<'a> for ZCompressedAccountInfo<'a> {
216    fn lamports(&self) -> u64 {
217        self.output.as_ref().unwrap().lamports.into()
218    }
219
220    fn address(&self) -> Option<[u8; 32]> {
221        self.address.map(|x| *x)
222    }
223
224    fn skip(&self) -> bool {
225        self.output.is_none()
226    }
227
228    fn owner(&self) -> Pubkey {
229        self.owner
230    }
231
232    fn merkle_tree_index(&self) -> u8 {
233        self.output.as_ref().unwrap().output_merkle_tree_index
234    }
235
236    fn has_data(&self) -> bool {
237        true
238    }
239
240    fn data(&self) -> Option<CompressedAccountData> {
241        Some(CompressedAccountData {
242            discriminator: self.output.as_ref().unwrap().discriminator,
243            data_hash: self.output.as_ref().unwrap().data_hash,
244            data: self.output.as_ref().unwrap().data.to_vec(),
245        })
246    }
247
248    fn hash_with_hashed_values(
249        &self,
250        owner_hashed: &[u8; 32],
251        merkle_tree_hashed: &[u8; 32],
252        leaf_index: &u32,
253        is_batched: bool,
254    ) -> Result<[u8; 32], CompressedAccountError> {
255        let output = self.output.as_ref().unwrap();
256        let address_slice = self.address.as_ref().map(|x| x.as_slice());
257        hash_with_hashed_values(
258            &output.lamports.into(),
259            address_slice,
260            Some((output.discriminator.as_slice(), output.data_hash.as_slice())),
261            owner_hashed,
262            merkle_tree_hashed,
263            leaf_index,
264            is_batched,
265        )
266    }
267}
268
269#[repr(C)]
270#[derive(
271    Debug, Default, PartialEq, Clone, FromBytes, IntoBytes, Unaligned, Immutable, KnownLayout,
272)]
273pub struct ZOutAccountInfoMeta {
274    pub discriminator: [u8; 8],
275    /// Data hash
276    pub data_hash: [u8; 32],
277    pub output_merkle_tree_index: u8,
278    /// Lamports.
279    pub lamports: U64,
280}
281
282#[derive(Debug, PartialEq, Clone)]
283pub struct ZOutAccountInfo<'a> {
284    meta: Ref<&'a [u8], ZOutAccountInfoMeta>,
285    /// Account data.
286    pub data: &'a [u8],
287}
288
289impl<'a> ZeroCopyAt<'a> for ZOutAccountInfo<'a> {
290    type ZeroCopyAt = ZOutAccountInfo<'a>;
291
292    fn zero_copy_at(bytes: &'a [u8]) -> Result<(Self::ZeroCopyAt, &'a [u8]), ZeroCopyError> {
293        let (meta, bytes) = Ref::<&[u8], ZOutAccountInfoMeta>::from_prefix(bytes)?;
294        let (len, bytes) = Ref::<&'a [u8], U32>::from_prefix(bytes)?;
295        let (data, bytes) = bytes.split_at(u64::from(*len) as usize);
296        Ok((Self { meta, data }, bytes))
297    }
298}
299
300impl Deref for ZOutAccountInfo<'_> {
301    type Target = ZOutAccountInfoMeta;
302
303    fn deref(&self) -> &Self::Target {
304        &self.meta
305    }
306}
307
308#[derive(Debug, PartialEq)]
309pub struct ZOutAccountInfoMut<'a> {
310    meta: Ref<&'a mut [u8], ZOutAccountInfoMeta>,
311    /// Account data.
312    pub data: &'a mut [u8],
313}
314
315impl Deref for ZOutAccountInfoMut<'_> {
316    type Target = ZOutAccountInfoMeta;
317
318    fn deref(&self) -> &Self::Target {
319        &self.meta
320    }
321}
322
323impl DerefMut for ZOutAccountInfoMut<'_> {
324    fn deref_mut(&mut self) -> &mut Self::Target {
325        &mut self.meta
326    }
327}
328
329#[cfg_attr(
330    all(feature = "std", feature = "anchor"),
331    derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
332)]
333#[cfg_attr(
334    not(feature = "anchor"),
335    derive(borsh::BorshDeserialize, borsh::BorshSerialize)
336)]
337#[derive(Debug, PartialEq, Clone, Default)]
338pub struct CompressedAccountInfo {
339    /// Address.
340    pub address: Option<[u8; 32]>,
341    /// Input account.
342    pub input: Option<InAccountInfo>,
343    /// Output account.
344    pub output: Option<OutAccountInfo>,
345}
346
347#[derive(Debug, PartialEq)]
348pub struct ZCompressedAccountInfo<'a> {
349    pub owner: Pubkey,
350    /// Address.
351    pub address: Option<Ref<&'a [u8], [u8; 32]>>,
352    /// Input account.
353    pub input: Option<Ref<&'a [u8], ZInAccountInfo>>,
354    /// Output account.
355    pub output: Option<ZOutAccountInfo<'a>>,
356}
357
358impl<'a> CompressedAccountInfo {
359    pub fn zero_copy_at_with_owner(
360        bytes: &'a [u8],
361        owner: Pubkey,
362    ) -> Result<(ZCompressedAccountInfo<'a>, &'a [u8]), ZeroCopyError> {
363        let (address, bytes) = Option::<Ref<&[u8], [u8; 32]>>::zero_copy_at(bytes)?;
364        let (input, bytes) = Option::<Ref<&[u8], ZInAccountInfo>>::zero_copy_at(bytes)?;
365        let (output, bytes) = Option::<ZOutAccountInfo<'a>>::zero_copy_at(bytes)?;
366        Ok((
367            ZCompressedAccountInfo {
368                owner,
369                address,
370                input,
371                output,
372            },
373            bytes,
374        ))
375    }
376}
377
378#[cfg_attr(
379    all(feature = "std", feature = "anchor"),
380    derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
381)]
382#[cfg_attr(
383    not(feature = "anchor"),
384    derive(borsh::BorshDeserialize, borsh::BorshSerialize)
385)]
386#[derive(Debug, PartialEq, Default, Clone)]
387pub struct InstructionDataInvokeCpiWithAccountInfo {
388    /// 0 V1 instruction accounts.
389    /// 1 Optimized V2 instruction accounts.
390    pub mode: u8,
391    pub bump: u8,
392    pub invoking_program_id: Pubkey,
393    /// If compress_or_decompress_lamports > 0 -> expect sol_pool_pda
394    pub compress_or_decompress_lamports: u64,
395    /// -> expect account decompression_recipient
396    pub is_compress: bool,
397    pub with_cpi_context: bool,
398    pub with_transaction_hash: bool,
399    pub cpi_context: CompressedCpiContext,
400    pub proof: Option<CompressedProof>,
401    pub new_address_params: Vec<NewAddressParamsAssignedPacked>,
402    pub account_infos: Vec<CompressedAccountInfo>,
403    pub read_only_addresses: Vec<PackedReadOnlyAddress>,
404    pub read_only_accounts: Vec<PackedReadOnlyCompressedAccount>,
405}
406
407impl InstructionDataInvokeCpiWithAccountInfo {
408    pub fn new(invoking_program_id: Pubkey, bump: u8, proof: Option<CompressedProof>) -> Self {
409        Self {
410            invoking_program_id,
411            bump,
412            proof,
413            mode: 1,
414            ..Default::default()
415        }
416    }
417
418    #[must_use = "mode_v1 returns a new value"]
419    pub fn mode_v1(mut self) -> Self {
420        self.mode = 0;
421        self
422    }
423
424    #[must_use = "write_to_cpi_context_set returns a new value"]
425    pub fn write_to_cpi_context_set(mut self) -> Self {
426        self.with_cpi_context = true;
427        self.cpi_context = CompressedCpiContext::set();
428        self
429    }
430
431    #[must_use = "write_to_cpi_context_first returns a new value"]
432    pub fn write_to_cpi_context_first(mut self) -> Self {
433        self.with_cpi_context = true;
434        self.cpi_context = CompressedCpiContext::first();
435        self
436    }
437
438    #[must_use = "execute_with_cpi_context returns a new value"]
439    pub fn execute_with_cpi_context(mut self) -> Self {
440        self.with_cpi_context = true;
441        self
442    }
443
444    #[must_use = "with_cpi_context returns a new value"]
445    pub fn with_cpi_context(mut self, cpi_context: CompressedCpiContext) -> Self {
446        self.cpi_context = cpi_context;
447        self
448    }
449
450    #[must_use = "with_with_transaction_hash returns a new value"]
451    pub fn with_with_transaction_hash(mut self, with_transaction_hash: bool) -> Self {
452        self.with_transaction_hash = with_transaction_hash;
453        self
454    }
455
456    #[must_use = "compress_lamports returns a new value"]
457    pub fn compress_lamports(mut self, lamports: u64) -> Self {
458        self.compress_or_decompress_lamports = lamports;
459        self.is_compress = true;
460        self
461    }
462
463    #[must_use = "decompress_lamports returns a new value"]
464    pub fn decompress_lamports(mut self, lamports: u64) -> Self {
465        self.compress_or_decompress_lamports = lamports;
466        self.is_compress = false;
467        self
468    }
469
470    #[must_use = "with_new_addresses returns a new value"]
471    pub fn with_new_addresses(
472        mut self,
473        new_address_params: &[NewAddressParamsAssignedPacked],
474    ) -> Self {
475        if !new_address_params.is_empty() {
476            self.new_address_params
477                .extend_from_slice(new_address_params);
478        }
479        self
480    }
481
482    #[must_use = "with_account_infos returns a new value"]
483    pub fn with_account_infos(mut self, account_infos: &[CompressedAccountInfo]) -> Self {
484        if !account_infos.is_empty() {
485            self.account_infos.extend_from_slice(account_infos);
486        }
487        self
488    }
489
490    #[must_use = "with_read_only_addresses returns a new value"]
491    pub fn with_read_only_addresses(
492        mut self,
493        read_only_addresses: &[PackedReadOnlyAddress],
494    ) -> Self {
495        if !read_only_addresses.is_empty() {
496            self.read_only_addresses
497                .extend_from_slice(read_only_addresses);
498        }
499        self
500    }
501
502    #[must_use = "with_read_only_accounts returns a new value"]
503    pub fn with_read_only_accounts(
504        mut self,
505        read_only_accounts: &[PackedReadOnlyCompressedAccount],
506    ) -> Self {
507        if !read_only_accounts.is_empty() {
508            self.read_only_accounts
509                .extend_from_slice(read_only_accounts);
510        }
511        self
512    }
513}
514
515impl InstructionDiscriminator for InstructionDataInvokeCpiWithAccountInfo {
516    fn discriminator(&self) -> &'static [u8] {
517        &INVOKE_CPI_WITH_ACCOUNT_INFO_INSTRUCTION
518    }
519}
520
521impl LightInstructionData for InstructionDataInvokeCpiWithAccountInfo {}
522
523impl<'a> InstructionData<'a> for ZInstructionDataInvokeCpiWithAccountInfo<'a> {
524    fn bump(&self) -> Option<u8> {
525        Some(self.bump)
526    }
527
528    fn account_option_config(
529        &self,
530    ) -> Result<super::traits::AccountOptions, CompressedAccountError> {
531        let sol_pool_pda = self.compress_or_decompress_lamports().is_some();
532        let decompression_recipient = sol_pool_pda && !self.is_compress();
533        let cpi_context_account = self.cpi_context().is_some();
534        let write_to_cpi_context =
535            self.cpi_context.first_set_context() || self.cpi_context.set_context();
536
537        // Validate: if we want to write to CPI context, we must have a CPI context
538        if write_to_cpi_context && !cpi_context_account {
539            return Err(CompressedAccountError::InvalidCpiContext);
540        }
541
542        Ok(AccountOptions {
543            sol_pool_pda,
544            decompression_recipient,
545            cpi_context_account,
546            write_to_cpi_context,
547        })
548    }
549
550    fn with_transaction_hash(&self) -> bool {
551        self.meta.with_transaction_hash()
552    }
553
554    fn read_only_accounts(&self) -> Option<&[ZPackedReadOnlyCompressedAccount]> {
555        Some(self.read_only_accounts.as_slice())
556    }
557
558    fn read_only_addresses(&self) -> Option<&[ZPackedReadOnlyAddress]> {
559        Some(self.read_only_addresses.as_slice())
560    }
561
562    fn owner(&self) -> Pubkey {
563        self.meta.invoking_program_id
564    }
565
566    fn new_addresses(&self) -> &[impl NewAddress<'a>] {
567        self.new_address_params.as_slice()
568    }
569
570    fn proof(&self) -> Option<Ref<&'a [u8], CompressedProof>> {
571        self.proof
572    }
573
574    fn cpi_context(&self) -> Option<CompressedCpiContext> {
575        if self.meta.with_cpi_context() {
576            Some(CompressedCpiContext {
577                set_context: self.meta.cpi_context.set_context(),
578                first_set_context: self.meta.cpi_context.first_set_context(),
579                cpi_context_account_index: self.meta.cpi_context.cpi_context_account_index,
580            })
581        } else {
582            None
583        }
584    }
585
586    fn is_compress(&self) -> bool {
587        self.meta.is_compress()
588    }
589
590    fn input_accounts(&self) -> &[impl InputAccount<'a>] {
591        self.account_infos.as_slice()
592    }
593
594    fn output_accounts(&self) -> &[impl super::traits::OutputAccount<'a>] {
595        self.account_infos.as_slice()
596    }
597
598    fn compress_or_decompress_lamports(&self) -> Option<u64> {
599        if self.meta.compress_or_decompress_lamports != U64::from(0) {
600            Some(self.meta.compress_or_decompress_lamports.into())
601        } else {
602            None
603        }
604    }
605}
606
607pub struct ZInstructionDataInvokeCpiWithAccountInfo<'a> {
608    meta: Ref<&'a [u8], ZInstructionDataInvokeCpiWithReadOnlyMeta>,
609    pub proof: Option<Ref<&'a [u8], CompressedProof>>,
610    pub new_address_params: ZeroCopySliceBorsh<'a, ZNewAddressParamsAssignedPacked>,
611    pub account_infos: Vec<ZCompressedAccountInfo<'a>>,
612    pub read_only_addresses: ZeroCopySliceBorsh<'a, ZPackedReadOnlyAddress>,
613    pub read_only_accounts: ZeroCopySliceBorsh<'a, ZPackedReadOnlyCompressedAccount>,
614}
615
616impl<'a> Deref for ZInstructionDataInvokeCpiWithAccountInfo<'a> {
617    type Target = Ref<&'a [u8], ZInstructionDataInvokeCpiWithReadOnlyMeta>;
618
619    fn deref(&self) -> &Self::Target {
620        &self.meta
621    }
622}
623
624impl<'a> ZeroCopyAt<'a> for InstructionDataInvokeCpiWithAccountInfo {
625    type ZeroCopyAt = ZInstructionDataInvokeCpiWithAccountInfo<'a>;
626    fn zero_copy_at(bytes: &'a [u8]) -> Result<(Self::ZeroCopyAt, &'a [u8]), ZeroCopyError> {
627        let (meta, bytes) =
628            Ref::<&[u8], ZInstructionDataInvokeCpiWithReadOnlyMeta>::from_prefix(bytes)?;
629        let (proof, bytes) = Option::<Ref<&[u8], CompressedProof>>::zero_copy_at(bytes)?;
630        let (new_address_params, bytes) =
631            ZeroCopySliceBorsh::<'a, ZNewAddressParamsAssignedPacked>::from_bytes_at(bytes)?;
632        let (account_infos, bytes) = {
633            let (num_slices, mut bytes) = Ref::<&[u8], U32>::from_prefix(bytes)?;
634            let num_slices = u32::from(*num_slices) as usize;
635            let mut slices = Vec::with_capacity(num_slices);
636            if bytes.len() < num_slices {
637                return Err(ZeroCopyError::InsufficientMemoryAllocated(
638                    bytes.len(),
639                    num_slices,
640                ));
641            }
642            for _ in 0..num_slices {
643                let (slice, _bytes) = CompressedAccountInfo::zero_copy_at_with_owner(
644                    bytes,
645                    meta.invoking_program_id,
646                )?;
647                bytes = _bytes;
648                slices.push(slice);
649            }
650            (slices, bytes)
651        };
652        let (read_only_addresses, bytes) =
653            ZeroCopySliceBorsh::<'a, ZPackedReadOnlyAddress>::from_bytes_at(bytes)?;
654        let (read_only_accounts, bytes) =
655            ZeroCopySliceBorsh::<'a, ZPackedReadOnlyCompressedAccount>::from_bytes_at(bytes)?;
656        Ok((
657            ZInstructionDataInvokeCpiWithAccountInfo {
658                meta,
659                proof,
660                new_address_params,
661                account_infos,
662                read_only_addresses,
663                read_only_accounts,
664            },
665            bytes,
666        ))
667    }
668}
669
670#[cfg(all(not(feature = "pinocchio"), feature = "new-unique"))]
671#[cfg(test)]
672pub mod test {
673    use borsh::BorshSerialize;
674    use rand::{
675        rngs::{StdRng, ThreadRng},
676        Rng, SeedableRng,
677    };
678
679    use super::*;
680    use crate::{
681        compressed_account::{PackedMerkleContext, PackedReadOnlyCompressedAccount},
682        instruction_data::{
683            compressed_proof::CompressedProof, cpi_context::CompressedCpiContext,
684            data::NewAddressParamsAssignedPacked,
685        },
686        CompressedAccountError,
687    };
688
689    fn get_rnd_instruction_data_invoke_cpi_with_account_info(
690        rng: &mut StdRng,
691    ) -> InstructionDataInvokeCpiWithAccountInfo {
692        let with_cpi_context = rng.gen();
693        InstructionDataInvokeCpiWithAccountInfo {
694            mode: rng.gen_range(0..2),
695            bump: rng.gen(),
696            invoking_program_id: Pubkey::new_unique(),
697            compress_or_decompress_lamports: rng.gen(),
698            is_compress: rng.gen(),
699            with_cpi_context,
700            with_transaction_hash: rng.gen(),
701            cpi_context: get_rnd_cpi_context(rng, with_cpi_context),
702            proof: Some(CompressedProof {
703                a: rng.gen(),
704                b: (0..64)
705                    .map(|_| rng.gen())
706                    .collect::<Vec<u8>>()
707                    .try_into()
708                    .unwrap(),
709                c: rng.gen(),
710            }),
711            new_address_params: vec![
712                get_rnd_new_address_params_assigned(rng);
713                rng.gen_range(0..10)
714            ],
715            account_infos: vec![get_rnd_test_account_info(rng); rng.gen_range(0..10)],
716            read_only_addresses: vec![get_rnd_read_only_address(rng); rng.gen_range(0..10)],
717            read_only_accounts: vec![get_rnd_read_only_account(rng); rng.gen_range(0..10)],
718        }
719    }
720
721    fn get_rnd_cpi_context(rng: &mut StdRng, with_cpi_context: bool) -> CompressedCpiContext {
722        CompressedCpiContext {
723            first_set_context: rng.gen() && with_cpi_context,
724            set_context: rng.gen() && with_cpi_context,
725            cpi_context_account_index: rng.gen(),
726        }
727    }
728
729    fn get_rnd_read_only_address(rng: &mut StdRng) -> PackedReadOnlyAddress {
730        PackedReadOnlyAddress {
731            address: rng.gen(),
732            address_merkle_tree_root_index: rng.gen(),
733            address_merkle_tree_account_index: rng.gen(),
734        }
735    }
736
737    fn get_rnd_read_only_account(rng: &mut StdRng) -> PackedReadOnlyCompressedAccount {
738        PackedReadOnlyCompressedAccount {
739            account_hash: rng.gen(),
740            merkle_context: PackedMerkleContext {
741                merkle_tree_pubkey_index: rng.gen(),
742                queue_pubkey_index: rng.gen(),
743                leaf_index: rng.gen(),
744                prove_by_index: rng.gen(),
745            },
746            root_index: rng.gen(),
747        }
748    }
749
750    fn get_rnd_in_account_info(rng: &mut StdRng) -> InAccountInfo {
751        InAccountInfo {
752            discriminator: rng.gen(),
753            data_hash: rng.gen(),
754            merkle_context: PackedMerkleContext {
755                merkle_tree_pubkey_index: rng.gen(),
756                queue_pubkey_index: rng.gen(),
757                leaf_index: rng.gen(),
758                prove_by_index: rng.gen(),
759            },
760            root_index: rng.gen(),
761            lamports: rng.gen(),
762        }
763    }
764
765    pub fn get_rnd_out_account_info(rng: &mut StdRng) -> OutAccountInfo {
766        OutAccountInfo {
767            discriminator: rng.gen(),
768            data_hash: rng.gen(),
769            output_merkle_tree_index: rng.gen(),
770            lamports: rng.gen(),
771            data: (0..rng.gen_range(0..100)).map(|_| rng.gen()).collect(),
772        }
773    }
774
775    pub fn get_rnd_test_account_info(rng: &mut StdRng) -> CompressedAccountInfo {
776        CompressedAccountInfo {
777            address: if rng.gen() { Some(rng.gen()) } else { None },
778            input: if rng.gen() {
779                Some(get_rnd_in_account_info(rng))
780            } else {
781                None
782            },
783            output: if rng.gen() {
784                Some(get_rnd_out_account_info(rng))
785            } else {
786                None
787            },
788        }
789    }
790
791    pub fn get_rnd_new_address_params_assigned(rng: &mut StdRng) -> NewAddressParamsAssignedPacked {
792        NewAddressParamsAssignedPacked {
793            seed: rng.gen(),
794            address_queue_account_index: rng.gen(),
795            address_merkle_tree_account_index: rng.gen(),
796            address_merkle_tree_root_index: rng.gen(),
797            assigned_to_account: rng.gen(),
798            assigned_account_index: rng.gen(),
799        }
800    }
801
802    fn compare_invoke_cpi_with_account_info(
803        reference: &InstructionDataInvokeCpiWithAccountInfo,
804        z_copy: &ZInstructionDataInvokeCpiWithAccountInfo,
805    ) -> Result<(), CompressedAccountError> {
806        // Basic field comparisons
807        if reference.mode != z_copy.meta.mode {
808            return Err(CompressedAccountError::InvalidArgument);
809        }
810        if reference.bump != z_copy.meta.bump {
811            return Err(CompressedAccountError::InvalidArgument);
812        }
813        if reference.invoking_program_id != z_copy.meta.invoking_program_id {
814            return Err(CompressedAccountError::InvalidArgument);
815        }
816        if reference.compress_or_decompress_lamports
817            != u64::from(z_copy.meta.compress_or_decompress_lamports)
818        {
819            return Err(CompressedAccountError::InvalidArgument);
820        }
821        if reference.is_compress != z_copy.meta.is_compress() {
822            return Err(CompressedAccountError::InvalidArgument);
823        }
824        if reference.with_cpi_context != z_copy.meta.with_cpi_context() {
825            return Err(CompressedAccountError::InvalidArgument);
826        }
827        if reference.with_transaction_hash != z_copy.meta.with_transaction_hash() {
828            return Err(CompressedAccountError::InvalidArgument);
829        }
830
831        // CPI context comparisons
832        if reference.cpi_context.first_set_context != z_copy.meta.cpi_context.first_set_context() {
833            return Err(CompressedAccountError::InvalidArgument);
834        }
835        if reference.cpi_context.set_context != z_copy.meta.cpi_context.set_context() {
836            return Err(CompressedAccountError::InvalidArgument);
837        }
838        if reference.cpi_context.cpi_context_account_index
839            != z_copy.meta.cpi_context.cpi_context_account_index
840        {
841            return Err(CompressedAccountError::InvalidArgument);
842        }
843
844        // Proof comparisons
845        if reference.proof.is_some() && z_copy.proof.is_none() {
846            return Err(CompressedAccountError::InvalidArgument);
847        }
848        if reference.proof.is_none() && z_copy.proof.is_some() {
849            return Err(CompressedAccountError::InvalidArgument);
850        }
851        if reference.proof.is_some() && z_copy.proof.is_some() {
852            let ref_proof = reference.proof.as_ref().unwrap();
853            let z_proof = *z_copy.proof.as_ref().unwrap();
854            if ref_proof.a != z_proof.a || ref_proof.b != z_proof.b || ref_proof.c != z_proof.c {
855                return Err(CompressedAccountError::InvalidArgument);
856            }
857        }
858
859        // New address params comparisons - detailed comparison of contents
860        if reference.new_address_params.len() != z_copy.new_address_params.len() {
861            return Err(CompressedAccountError::InvalidArgument);
862        }
863        for i in 0..reference
864            .new_address_params
865            .len()
866            .min(z_copy.new_address_params.len())
867        {
868            let ref_param = &reference.new_address_params[i];
869            let z_param = &z_copy.new_address_params[i];
870
871            if ref_param.seed != z_param.seed {
872                return Err(CompressedAccountError::InvalidArgument);
873            }
874            if ref_param.address_queue_account_index != z_param.address_queue_account_index {
875                return Err(CompressedAccountError::InvalidArgument);
876            }
877            if ref_param.address_merkle_tree_account_index
878                != z_param.address_merkle_tree_account_index
879            {
880                return Err(CompressedAccountError::InvalidArgument);
881            }
882            if ref_param.address_merkle_tree_root_index
883                != u16::from(z_param.address_merkle_tree_root_index)
884            {
885                return Err(CompressedAccountError::InvalidArgument);
886            }
887            // For ZNewAddressParamsAssignedPacked, assigned_to_account is a u8 (0 or 1)
888            // For NewAddressParamsAssignedPacked, it's a bool
889            let z_assigned_to_account_bool = z_param.assigned_to_account > 0;
890            if ref_param.assigned_to_account != z_assigned_to_account_bool {
891                return Err(CompressedAccountError::InvalidArgument);
892            }
893            if ref_param.assigned_account_index != z_param.assigned_account_index {
894                return Err(CompressedAccountError::InvalidArgument);
895            }
896        }
897
898        // Account infos comparison - check the length first
899        if reference.account_infos.len() != z_copy.account_infos.len() {
900            return Err(CompressedAccountError::InvalidArgument);
901        }
902        // We could do more detailed comparison of account_infos here if needed
903        // but it's complex due to the ZCompressedAccountInfo structure
904
905        // Read-only addresses comparison
906        if reference.read_only_addresses.len() != z_copy.read_only_addresses.len() {
907            return Err(CompressedAccountError::InvalidArgument);
908        }
909        for i in 0..reference
910            .read_only_addresses
911            .len()
912            .min(z_copy.read_only_addresses.len())
913        {
914            let ref_addr = &reference.read_only_addresses[i];
915            let z_addr = &z_copy.read_only_addresses[i];
916
917            if ref_addr.address != z_addr.address {
918                return Err(CompressedAccountError::InvalidArgument);
919            }
920            if ref_addr.address_merkle_tree_account_index
921                != z_addr.address_merkle_tree_account_index
922            {
923                return Err(CompressedAccountError::InvalidArgument);
924            }
925            if ref_addr.address_merkle_tree_root_index
926                != u16::from(z_addr.address_merkle_tree_root_index)
927            {
928                return Err(CompressedAccountError::InvalidArgument);
929            }
930        }
931
932        // Read-only accounts comparison
933        if reference.read_only_accounts.len() != z_copy.read_only_accounts.len() {
934            return Err(CompressedAccountError::InvalidArgument);
935        }
936        for i in 0..reference
937            .read_only_accounts
938            .len()
939            .min(z_copy.read_only_accounts.len())
940        {
941            let ref_acc = &reference.read_only_accounts[i];
942            let z_acc = &z_copy.read_only_accounts[i];
943
944            if ref_acc.account_hash != z_acc.account_hash {
945                return Err(CompressedAccountError::InvalidArgument);
946            }
947
948            // Compare merkle_context
949            if ref_acc.merkle_context.merkle_tree_pubkey_index
950                != z_acc.merkle_context.merkle_tree_pubkey_index
951            {
952                return Err(CompressedAccountError::InvalidArgument);
953            }
954            if ref_acc.merkle_context.queue_pubkey_index != z_acc.merkle_context.queue_pubkey_index
955            {
956                return Err(CompressedAccountError::InvalidArgument);
957            }
958            if ref_acc.merkle_context.leaf_index != u32::from(z_acc.merkle_context.leaf_index) {
959                return Err(CompressedAccountError::InvalidArgument);
960            }
961            if ref_acc.merkle_context.prove_by_index != z_acc.merkle_context.prove_by_index() {
962                return Err(CompressedAccountError::InvalidArgument);
963            }
964
965            if ref_acc.root_index != u16::from(z_acc.root_index) {
966                return Err(CompressedAccountError::InvalidArgument);
967            }
968        }
969
970        // Test trait methods
971        assert_eq!(
972            z_copy.with_transaction_hash(),
973            reference.with_transaction_hash
974        );
975        assert_eq!(z_copy.bump(), Some(reference.bump));
976        assert_eq!(z_copy.owner(), reference.invoking_program_id);
977
978        // Test the complex logic for compress/decompress
979        if reference.compress_or_decompress_lamports > 0 {
980            assert_eq!(
981                z_copy.compress_or_decompress_lamports(),
982                Some(reference.compress_or_decompress_lamports)
983            );
984        } else {
985            assert_eq!(z_copy.compress_or_decompress_lamports(), None);
986        }
987
988        // Test is_compress
989        let expected_is_compress =
990            reference.is_compress && reference.compress_or_decompress_lamports > 0;
991        assert_eq!(z_copy.is_compress(), expected_is_compress);
992
993        // Test cpi_context
994        if reference.with_cpi_context {
995            let context = z_copy.cpi_context();
996            assert!(context.is_some());
997            if let Some(ctx) = context {
998                assert_eq!(
999                    ctx.first_set_context,
1000                    reference.cpi_context.first_set_context
1001                );
1002                assert_eq!(ctx.set_context, reference.cpi_context.set_context);
1003                assert_eq!(
1004                    ctx.cpi_context_account_index,
1005                    reference.cpi_context.cpi_context_account_index
1006                );
1007            }
1008        } else {
1009            assert!(z_copy.cpi_context().is_none());
1010        }
1011
1012        // Check account_option_config
1013        let account_options = z_copy.account_option_config().unwrap();
1014        assert_eq!(
1015            account_options.sol_pool_pda,
1016            z_copy.compress_or_decompress_lamports().is_some()
1017        );
1018        assert_eq!(
1019            account_options.decompression_recipient,
1020            z_copy.compress_or_decompress_lamports().is_some() && !z_copy.is_compress()
1021        );
1022        assert_eq!(
1023            account_options.cpi_context_account,
1024            z_copy.cpi_context().is_some()
1025        );
1026
1027        Ok(())
1028    }
1029
1030    #[test]
1031    fn test_instruction_data_invoke_cpi_with_account_info_rnd() {
1032        let mut thread_rng = ThreadRng::default();
1033        let seed = thread_rng.gen();
1034        println!("\n\ne2e test seed {}\n\n", seed);
1035        let mut rng = StdRng::seed_from_u64(seed);
1036
1037        let num_iters = 1000;
1038        for _ in 0..num_iters {
1039            // For now, we're using a fixed structure to ensure the test passes
1040            // Later, we can gradually introduce randomness in parts of the struct
1041            let value = get_rnd_instruction_data_invoke_cpi_with_account_info(&mut rng);
1042
1043            let mut vec = Vec::new();
1044            value.serialize(&mut vec).unwrap();
1045            let (zero_copy, _) =
1046                InstructionDataInvokeCpiWithAccountInfo::zero_copy_at(&vec).unwrap();
1047            compare_invoke_cpi_with_account_info(&value, &zero_copy).unwrap();
1048        }
1049    }
1050}