light_compressed_account/instruction_data/
with_account_info.rs

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