1use core::ops::Deref;
2
3use light_zero_copy::{
4 errors::ZeroCopyError, slice::ZeroCopySliceBorsh, traits::ZeroCopyAt, ZeroCopyMut,
5};
6use zerocopy::{
7 little_endian::{U16, U32, U64},
8 FromBytes, Immutable, IntoBytes, KnownLayout, Ref, Unaligned,
9};
10
11use super::{
12 compressed_proof::CompressedProof,
13 cpi_context::CompressedCpiContext,
14 data::{
15 NewAddressParamsAssignedPacked, OutputCompressedAccountWithPackedContext,
16 PackedReadOnlyAddress,
17 },
18 traits::{AccountOptions, InputAccount, InstructionData, NewAddress},
19 zero_copy::{
20 ZCompressedCpiContext, ZNewAddressParamsAssignedPacked,
21 ZOutputCompressedAccountWithPackedContext, ZPackedMerkleContext, ZPackedReadOnlyAddress,
22 ZPackedReadOnlyCompressedAccount,
23 },
24};
25use crate::{
26 compressed_account::{
27 hash_with_hashed_values, CompressedAccount, CompressedAccountData,
28 PackedCompressedAccountWithMerkleContext, PackedMerkleContext,
29 PackedReadOnlyCompressedAccount,
30 },
31 discriminators::DISCRIMINATOR_INVOKE_CPI_WITH_READ_ONLY,
32 instruction_data::traits::LightInstructionData,
33 pubkey::Pubkey,
34 CompressedAccountError, InstructionDiscriminator, Vec,
35};
36
37#[repr(C)]
38#[cfg_attr(
39 all(feature = "std", feature = "anchor"),
40 derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
41)]
42#[cfg_attr(
43 not(feature = "anchor"),
44 derive(borsh::BorshDeserialize, borsh::BorshSerialize)
45)]
46#[derive(Debug, Default, PartialEq, Clone, ZeroCopyMut)]
47pub struct InAccount {
48 pub discriminator: [u8; 8],
49 pub data_hash: [u8; 32],
51 pub merkle_context: PackedMerkleContext,
53 pub root_index: u16,
55 pub lamports: u64,
57 pub address: Option<[u8; 32]>,
59}
60
61impl From<PackedCompressedAccountWithMerkleContext> for InAccount {
62 fn from(value: PackedCompressedAccountWithMerkleContext) -> Self {
63 Self {
64 discriminator: value
65 .compressed_account
66 .data
67 .as_ref()
68 .expect("Into InAccount expected data to exist.")
69 .discriminator,
70 merkle_context: value.merkle_context,
71 data_hash: value
72 .compressed_account
73 .data
74 .as_ref()
75 .expect("Into InAccount expected data to exist.")
76 .data_hash,
77 root_index: value.root_index,
78 lamports: value.compressed_account.lamports,
79 address: value.compressed_account.address,
80 }
81 }
82}
83
84impl From<InAccount> for PackedCompressedAccountWithMerkleContext {
85 fn from(value: InAccount) -> Self {
86 Self {
87 read_only: false,
88 merkle_context: value.merkle_context,
89 root_index: value.root_index,
90 compressed_account: CompressedAccount {
91 owner: Pubkey::default(), address: value.address,
93 lamports: value.lamports,
94 data: Some(CompressedAccountData {
95 discriminator: value.discriminator,
96 data: Vec::new(),
97 data_hash: value.data_hash,
98 }),
99 },
100 }
101 }
102}
103
104impl<'a> InputAccount<'a> for ZInAccount<'a> {
105 fn skip(&self) -> bool {
106 false
107 }
108 fn owner(&self) -> &Pubkey {
109 &self.owner
110 }
111 fn lamports(&self) -> u64 {
112 self.lamports.into()
113 }
114 fn address(&self) -> Option<[u8; 32]> {
115 self.address.map(|x| *x)
116 }
117 fn merkle_context(&self) -> ZPackedMerkleContext {
118 self.merkle_context
119 }
120
121 fn root_index(&self) -> u16 {
122 self.root_index.into()
123 }
124
125 fn has_data(&self) -> bool {
126 true
127 }
128
129 fn data(&self) -> Option<CompressedAccountData> {
130 Some(CompressedAccountData {
131 discriminator: self.discriminator,
132 data: Vec::new(),
133 data_hash: self.data_hash,
134 })
135 }
136
137 fn hash_with_hashed_values(
138 &self,
139 owner_hashed: &[u8; 32],
140 merkle_tree_hashed: &[u8; 32],
141 leaf_index: &u32,
142 is_batched: bool,
143 ) -> Result<[u8; 32], CompressedAccountError> {
144 hash_with_hashed_values(
145 &(self.lamports.into()),
146 self.address.as_ref().map(|x| x.as_slice()),
147 Some((self.discriminator.as_slice(), self.data_hash.as_slice())),
148 owner_hashed,
149 merkle_tree_hashed,
150 leaf_index,
151 is_batched,
152 )
153 }
154}
155
156impl InAccount {
157 pub fn into_packed_compressed_account_with_merkle_context(
158 &self,
159 owner: Pubkey,
160 ) -> PackedCompressedAccountWithMerkleContext {
161 PackedCompressedAccountWithMerkleContext {
162 read_only: false,
163 merkle_context: self.merkle_context,
164 root_index: self.root_index,
165 compressed_account: CompressedAccount {
166 owner,
167 address: self.address,
168 lamports: self.lamports,
169 data: Some(CompressedAccountData {
170 data: Vec::new(),
171 discriminator: self.discriminator,
172 data_hash: self.data_hash,
173 }),
174 },
175 }
176 }
177}
178
179#[repr(C)]
180#[derive(
181 Debug, Default, PartialEq, Clone, Copy, FromBytes, IntoBytes, Unaligned, Immutable, KnownLayout,
182)]
183pub struct ZInAccountMeta {
184 pub discriminator: [u8; 8],
185 pub data_hash: [u8; 32],
187 pub merkle_context: ZPackedMerkleContext,
189 pub root_index: U16,
191 pub lamports: U64,
193}
194
195#[repr(C)]
196#[derive(Debug, PartialEq)]
197pub struct ZInAccount<'a> {
198 pub owner: Pubkey,
199 meta: Ref<&'a [u8], ZInAccountMeta>,
200 pub address: Option<Ref<&'a [u8], [u8; 32]>>,
201}
202
203impl<'a> InAccount {
204 fn zero_copy_at_with_owner(
205 bytes: &'a [u8],
206 owner: Pubkey,
207 ) -> Result<(ZInAccount<'a>, &'a [u8]), ZeroCopyError> {
208 let (meta, bytes) = Ref::<&[u8], ZInAccountMeta>::from_prefix(bytes)?;
209 let (address, bytes) = Option::<Ref<&[u8], [u8; 32]>>::zero_copy_at(bytes)?;
210 Ok((
211 ZInAccount {
212 owner,
213 meta,
214 address,
215 },
216 bytes,
217 ))
218 }
219}
220
221impl<'a> Deref for ZInAccount<'a> {
222 type Target = Ref<&'a [u8], ZInAccountMeta>;
223
224 fn deref(&self) -> &Self::Target {
225 &self.meta
226 }
227}
228
229#[repr(C)]
230#[cfg_attr(
231 all(feature = "std", feature = "anchor"),
232 derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
233)]
234#[cfg_attr(
235 not(feature = "anchor"),
236 derive(borsh::BorshDeserialize, borsh::BorshSerialize)
237)]
238#[derive(Debug, PartialEq, Default, Clone, ZeroCopyMut)]
239pub struct InstructionDataInvokeCpiWithReadOnly {
240 pub mode: u8,
243 pub bump: u8,
244 pub invoking_program_id: Pubkey,
245 pub compress_or_decompress_lamports: u64,
247 pub is_compress: bool,
249 pub with_cpi_context: bool,
250 pub with_transaction_hash: bool,
251 pub cpi_context: CompressedCpiContext,
252 pub proof: Option<CompressedProof>,
253 pub new_address_params: Vec<NewAddressParamsAssignedPacked>,
254 pub input_compressed_accounts: Vec<InAccount>,
255 pub output_compressed_accounts: Vec<OutputCompressedAccountWithPackedContext>,
256 pub read_only_addresses: Vec<PackedReadOnlyAddress>,
257 pub read_only_accounts: Vec<PackedReadOnlyCompressedAccount>,
258}
259
260impl InstructionDataInvokeCpiWithReadOnly {
261 pub fn new(invoking_program_id: Pubkey, bump: u8, proof: Option<CompressedProof>) -> Self {
262 Self {
263 invoking_program_id,
264 bump,
265 proof,
266 mode: 1,
267 ..Default::default()
268 }
269 }
270
271 #[must_use = "mode_v1 returns a new value"]
272 pub fn mode_v1(mut self) -> Self {
273 self.mode = 0;
274 self
275 }
276
277 #[must_use = "write_to_cpi_context_set returns a new value"]
278 pub fn write_to_cpi_context_set(mut self) -> Self {
279 self.with_cpi_context = true;
280 self.cpi_context = CompressedCpiContext::set();
281 self
282 }
283
284 #[must_use = "write_to_cpi_context_first returns a new value"]
285 pub fn write_to_cpi_context_first(mut self) -> Self {
286 self.with_cpi_context = true;
287 self.cpi_context = CompressedCpiContext::first();
288 self
289 }
290
291 #[must_use = "execute_with_cpi_context returns a new value"]
292 pub fn execute_with_cpi_context(mut self) -> Self {
293 self.with_cpi_context = true;
294 self
295 }
296
297 #[must_use = "with_with_transaction_hash returns a new value"]
298 pub fn with_with_transaction_hash(mut self, with_transaction_hash: bool) -> Self {
299 self.with_transaction_hash = with_transaction_hash;
300 self
301 }
302
303 #[must_use = "with_cpi_context returns a new value"]
304 pub fn with_cpi_context(mut self, cpi_context: CompressedCpiContext) -> Self {
305 self.cpi_context = cpi_context;
306 self
307 }
308
309 #[must_use = "with_proof returns a new value"]
310 pub fn with_proof(mut self, proof: Option<CompressedProof>) -> Self {
311 self.proof = proof;
312 self
313 }
314
315 #[must_use = "with_new_addresses returns a new value"]
316 pub fn with_new_addresses(
317 mut self,
318 new_address_params: &[NewAddressParamsAssignedPacked],
319 ) -> Self {
320 if !new_address_params.is_empty() {
321 self.new_address_params
322 .extend_from_slice(new_address_params);
323 }
324 self
325 }
326
327 #[must_use = "with_input_compressed_accounts returns a new value"]
328 pub fn with_input_compressed_accounts(
329 mut self,
330 input_compressed_accounts: &[InAccount],
331 ) -> Self {
332 if !input_compressed_accounts.is_empty() {
333 self.input_compressed_accounts
334 .extend_from_slice(input_compressed_accounts);
335 }
336 self
337 }
338
339 #[must_use = "with_output_compressed_accounts returns a new value"]
340 pub fn with_output_compressed_accounts(
341 mut self,
342 output_compressed_accounts: &[OutputCompressedAccountWithPackedContext],
343 ) -> Self {
344 if !output_compressed_accounts.is_empty() {
345 self.output_compressed_accounts
346 .extend_from_slice(output_compressed_accounts);
347 }
348 self
349 }
350
351 #[must_use = "with_read_only_addresses returns a new value"]
352 pub fn with_read_only_addresses(
353 mut self,
354 read_only_addresses: &[PackedReadOnlyAddress],
355 ) -> Self {
356 if !read_only_addresses.is_empty() {
357 self.read_only_addresses
358 .extend_from_slice(read_only_addresses);
359 }
360 self
361 }
362
363 #[must_use = "with_read_only_accounts returns a new value"]
364 pub fn with_read_only_accounts(
365 mut self,
366 read_only_accounts: &[PackedReadOnlyCompressedAccount],
367 ) -> Self {
368 if !read_only_accounts.is_empty() {
369 self.read_only_accounts
370 .extend_from_slice(read_only_accounts);
371 }
372 self
373 }
374}
375
376impl InstructionDiscriminator for InstructionDataInvokeCpiWithReadOnly {
377 fn discriminator(&self) -> &'static [u8] {
378 &DISCRIMINATOR_INVOKE_CPI_WITH_READ_ONLY
379 }
380}
381
382impl LightInstructionData for InstructionDataInvokeCpiWithReadOnly {}
383
384#[repr(C)]
385#[derive(
386 Debug, Default, PartialEq, Clone, Copy, FromBytes, IntoBytes, Unaligned, Immutable, KnownLayout,
387)]
388pub struct ZInstructionDataInvokeCpiWithReadOnlyMeta {
389 pub mode: u8,
392 pub bump: u8,
393 pub invoking_program_id: Pubkey,
394 pub compress_or_decompress_lamports: U64,
396 is_compress: u8,
398 with_cpi_context: u8,
399 with_transaction_hash: u8,
400 pub cpi_context: ZCompressedCpiContext,
401}
402
403impl ZInstructionDataInvokeCpiWithReadOnlyMeta {
404 pub fn is_compress(&self) -> bool {
405 self.is_compress > 0
406 }
407 pub fn with_cpi_context(&self) -> bool {
408 self.with_cpi_context > 0
409 }
410 pub fn with_transaction_hash(&self) -> bool {
411 self.with_transaction_hash > 0
412 }
413}
414
415#[derive(Debug, PartialEq)]
416pub struct ZInstructionDataInvokeCpiWithReadOnly<'a> {
417 meta: Ref<&'a [u8], ZInstructionDataInvokeCpiWithReadOnlyMeta>,
418 pub proof: Option<Ref<&'a [u8], CompressedProof>>,
419 pub new_address_params: ZeroCopySliceBorsh<'a, ZNewAddressParamsAssignedPacked>,
420 pub input_compressed_accounts: Vec<ZInAccount<'a>>,
421 pub output_compressed_accounts: Vec<ZOutputCompressedAccountWithPackedContext<'a>>,
422 pub read_only_addresses: ZeroCopySliceBorsh<'a, ZPackedReadOnlyAddress>,
423 pub read_only_accounts: ZeroCopySliceBorsh<'a, ZPackedReadOnlyCompressedAccount>,
424}
425
426impl<'a> InstructionData<'a> for ZInstructionDataInvokeCpiWithReadOnly<'a> {
427 fn account_option_config(&self) -> Result<AccountOptions, CompressedAccountError> {
428 let sol_pool_pda = self.compress_or_decompress_lamports().is_some();
429 let decompression_recipient = sol_pool_pda && !self.is_compress();
430 let cpi_context_account = self.cpi_context().is_some();
431 let write_to_cpi_context =
432 self.cpi_context.first_set_context() || self.cpi_context.set_context();
433
434 if write_to_cpi_context && !cpi_context_account {
436 return Err(CompressedAccountError::InvalidCpiContext);
437 }
438
439 Ok(AccountOptions {
440 sol_pool_pda,
441 decompression_recipient,
442 cpi_context_account,
443 write_to_cpi_context,
444 })
445 }
446
447 fn with_transaction_hash(&self) -> bool {
448 self.meta.with_transaction_hash()
449 }
450
451 fn bump(&self) -> Option<u8> {
452 Some(self.bump)
453 }
454 fn read_only_accounts(&self) -> Option<&[ZPackedReadOnlyCompressedAccount]> {
455 Some(self.read_only_accounts.as_slice())
456 }
457
458 fn read_only_addresses(&self) -> Option<&[ZPackedReadOnlyAddress]> {
459 Some(self.read_only_addresses.as_slice())
460 }
461
462 fn owner(&self) -> Pubkey {
463 self.meta.invoking_program_id
464 }
465
466 fn new_addresses(&self) -> &[impl NewAddress<'a>] {
467 self.new_address_params.as_slice()
468 }
469
470 fn proof(&self) -> Option<Ref<&'a [u8], CompressedProof>> {
471 self.proof
472 }
473
474 fn cpi_context(&self) -> Option<CompressedCpiContext> {
475 if self.meta.with_cpi_context() {
476 Some(CompressedCpiContext {
477 set_context: self.cpi_context.set_context(),
478 first_set_context: self.cpi_context.first_set_context(),
479 cpi_context_account_index: self.cpi_context.cpi_context_account_index,
480 })
481 } else {
482 None
483 }
484 }
485
486 fn is_compress(&self) -> bool {
487 self.meta.is_compress() && self.compress_or_decompress_lamports().is_some()
488 }
489
490 fn input_accounts(&self) -> &[impl InputAccount<'a>] {
491 self.input_compressed_accounts.as_slice()
492 }
493
494 fn output_accounts(&self) -> &[impl super::traits::OutputAccount<'a>] {
495 self.output_compressed_accounts.as_slice()
496 }
497
498 fn compress_or_decompress_lamports(&self) -> Option<u64> {
499 let lamports: u64 = self.meta.compress_or_decompress_lamports.into();
500 if lamports != 0 {
501 Some(lamports)
502 } else {
503 None
504 }
505 }
506}
507
508impl<'a> Deref for ZInstructionDataInvokeCpiWithReadOnly<'a> {
509 type Target = Ref<&'a [u8], ZInstructionDataInvokeCpiWithReadOnlyMeta>;
510
511 fn deref(&self) -> &Self::Target {
512 &self.meta
513 }
514}
515
516impl<'a> ZeroCopyAt<'a> for InstructionDataInvokeCpiWithReadOnly {
517 type ZeroCopyAt = ZInstructionDataInvokeCpiWithReadOnly<'a>;
518 fn zero_copy_at(bytes: &'a [u8]) -> Result<(Self::ZeroCopyAt, &'a [u8]), ZeroCopyError> {
519 let (meta, bytes) =
520 Ref::<&[u8], ZInstructionDataInvokeCpiWithReadOnlyMeta>::from_prefix(bytes)?;
521 let (proof, bytes) = Option::<Ref<&[u8], CompressedProof>>::zero_copy_at(bytes)?;
522 let (new_address_params, bytes) =
523 ZeroCopySliceBorsh::<'a, ZNewAddressParamsAssignedPacked>::from_bytes_at(bytes)?;
524 let (input_compressed_accounts, bytes) = {
525 let (num_slices, mut bytes) = Ref::<&[u8], U32>::from_prefix(bytes)?;
526 let num_slices = u32::from(*num_slices) as usize;
527 if bytes.len() < num_slices {
530 return Err(ZeroCopyError::InsufficientMemoryAllocated(
531 bytes.len(),
532 num_slices,
533 ));
534 }
535 let mut slices = Vec::with_capacity(num_slices);
536 for _ in 0..num_slices {
537 let (slice, _bytes) =
538 InAccount::zero_copy_at_with_owner(bytes, meta.invoking_program_id)?;
539 bytes = _bytes;
540 slices.push(slice);
541 }
542 (slices, bytes)
543 };
544
545 let (output_compressed_accounts, bytes) = <Vec<
546 ZOutputCompressedAccountWithPackedContext<'a>,
547 > as ZeroCopyAt<'a>>::zero_copy_at(bytes)?;
548
549 let (read_only_addresses, bytes) =
550 ZeroCopySliceBorsh::<'a, ZPackedReadOnlyAddress>::from_bytes_at(bytes)?;
551
552 let (read_only_accounts, bytes) =
553 ZeroCopySliceBorsh::<'a, ZPackedReadOnlyCompressedAccount>::from_bytes_at(bytes)?;
554
555 Ok((
556 ZInstructionDataInvokeCpiWithReadOnly {
557 meta,
558 proof,
559 new_address_params,
560 input_compressed_accounts,
561 output_compressed_accounts,
562 read_only_addresses,
563 read_only_accounts,
564 },
565 bytes,
566 ))
567 }
568}
569
570impl PartialEq<InstructionDataInvokeCpiWithReadOnly> for ZInstructionDataInvokeCpiWithReadOnly<'_> {
571 fn eq(&self, other: &InstructionDataInvokeCpiWithReadOnly) -> bool {
572 if self.mode != other.mode
574 || self.bump != other.bump
575 || self.invoking_program_id != other.invoking_program_id
576 || u64::from(self.compress_or_decompress_lamports)
577 != other.compress_or_decompress_lamports
578 || self.is_compress() != other.is_compress
579 || self.with_cpi_context() != other.with_cpi_context
580 {
581 return false;
582 }
583
584 if self.proof.is_some() != other.proof.is_some() {
586 return false;
587 }
588 if self.cpi_context.set_context() != other.cpi_context.set_context
593 || self.cpi_context.first_set_context() != other.cpi_context.first_set_context
594 || self.cpi_context.cpi_context_account_index
595 != other.cpi_context.cpi_context_account_index
596 {
597 return false;
598 }
599
600 if self.new_address_params.len() != other.new_address_params.len()
601 || self.input_compressed_accounts.len() != other.input_compressed_accounts.len()
602 || self.output_compressed_accounts.len() != other.output_compressed_accounts.len()
603 || self.read_only_addresses.len() != other.read_only_addresses.len()
604 || self.read_only_accounts.len() != other.read_only_accounts.len()
605 {
606 return false;
607 }
608
609 true
610 }
611}
612
613#[cfg(all(not(feature = "pinocchio"), feature = "new-unique"))]
614#[cfg(test)]
615mod test {
616 use borsh::BorshSerialize;
617 use rand::{
618 rngs::{StdRng, ThreadRng},
619 Rng, SeedableRng,
620 };
621
622 use super::*;
623 use crate::CompressedAccountError;
624 #[test]
626 fn test_read_only_zero_copy() {
627 let borsh_struct = InstructionDataInvokeCpiWithReadOnly {
628 mode: 0,
629 bump: 0,
630 invoking_program_id: Pubkey::default(),
631 compress_or_decompress_lamports: 0,
632 is_compress: false,
633 with_cpi_context: false,
634 with_transaction_hash: true,
635 cpi_context: CompressedCpiContext {
636 set_context: false,
637 first_set_context: false,
638 cpi_context_account_index: 0,
639 },
640 proof: None,
641 new_address_params: vec![NewAddressParamsAssignedPacked {
642 seed: [1; 32],
643 address_merkle_tree_account_index: 1,
644 address_queue_account_index: 2,
645 address_merkle_tree_root_index: 3,
646 assigned_to_account: true,
647 assigned_account_index: 2,
648 }],
649 input_compressed_accounts: vec![InAccount {
650 discriminator: [1, 2, 3, 4, 5, 6, 7, 8],
651 data_hash: [10; 32],
652 merkle_context: PackedMerkleContext {
653 merkle_tree_pubkey_index: 1,
654 queue_pubkey_index: 2,
655 leaf_index: 3,
656 prove_by_index: false,
657 },
658 root_index: 3,
659 lamports: 1000,
660 address: Some([30; 32]),
661 }],
662 output_compressed_accounts: vec![OutputCompressedAccountWithPackedContext {
663 compressed_account: CompressedAccount {
664 owner: Pubkey::default(),
665 lamports: 2000,
666 address: Some([40; 32]),
667 data: Some(CompressedAccountData {
668 discriminator: [3, 4, 5, 6, 7, 8, 9, 10],
669 data: vec![],
670 data_hash: [50; 32],
671 }),
672 },
673 merkle_tree_index: 3,
674 }],
675 read_only_addresses: vec![PackedReadOnlyAddress {
676 address: [70; 32],
677 address_merkle_tree_account_index: 4,
678 address_merkle_tree_root_index: 5,
679 }],
680 read_only_accounts: vec![PackedReadOnlyCompressedAccount {
681 account_hash: [80; 32],
682 merkle_context: PackedMerkleContext {
683 merkle_tree_pubkey_index: 5,
684 queue_pubkey_index: 6,
685 leaf_index: 7,
686 prove_by_index: false,
687 },
688 root_index: 8,
689 }],
690 };
691 let bytes = borsh_struct.try_to_vec().unwrap();
692
693 let (zero_copy, _) = InstructionDataInvokeCpiWithReadOnly::zero_copy_at(&bytes).unwrap();
694
695 assert_eq!(zero_copy, borsh_struct);
696 }
697
698 fn compare_invoke_cpi_with_readonly(
700 reference: &InstructionDataInvokeCpiWithReadOnly,
701 z_copy: &ZInstructionDataInvokeCpiWithReadOnly,
702 ) -> Result<(), CompressedAccountError> {
703 if reference.mode != z_copy.meta.mode {
705 return Err(CompressedAccountError::InvalidArgument);
706 }
707 if reference.bump != z_copy.meta.bump {
708 return Err(CompressedAccountError::InvalidArgument);
709 }
710 if reference.invoking_program_id != z_copy.meta.invoking_program_id {
711 return Err(CompressedAccountError::InvalidArgument);
712 }
713 if reference.compress_or_decompress_lamports
714 != u64::from(z_copy.meta.compress_or_decompress_lamports)
715 {
716 return Err(CompressedAccountError::InvalidArgument);
717 }
718 if reference.is_compress != z_copy.meta.is_compress() {
719 return Err(CompressedAccountError::InvalidArgument);
720 }
721 if reference.with_cpi_context != z_copy.meta.with_cpi_context() {
722 return Err(CompressedAccountError::InvalidArgument);
723 }
724 if reference.with_transaction_hash != z_copy.meta.with_transaction_hash() {
725 return Err(CompressedAccountError::InvalidArgument);
726 }
727
728 if reference.cpi_context.first_set_context != z_copy.meta.cpi_context.first_set_context() {
730 return Err(CompressedAccountError::InvalidArgument);
731 }
732 if reference.cpi_context.set_context != z_copy.meta.cpi_context.set_context() {
733 return Err(CompressedAccountError::InvalidArgument);
734 }
735 if reference.cpi_context.cpi_context_account_index
736 != z_copy.meta.cpi_context.cpi_context_account_index
737 {
738 return Err(CompressedAccountError::InvalidArgument);
739 }
740
741 if reference.proof.is_some() && z_copy.proof.is_none() {
743 return Err(CompressedAccountError::InvalidArgument);
744 }
745 if reference.proof.is_none() && z_copy.proof.is_some() {
746 return Err(CompressedAccountError::InvalidArgument);
747 }
748 if reference.proof.is_some() && z_copy.proof.is_some() {
749 let ref_proof = reference.proof.as_ref().unwrap();
750 let z_proof = *z_copy.proof.as_ref().unwrap();
751 if ref_proof.a != z_proof.a || ref_proof.b != z_proof.b || ref_proof.c != z_proof.c {
752 return Err(CompressedAccountError::InvalidArgument);
753 }
754 }
755
756 if reference.new_address_params.len() != z_copy.new_address_params.len() {
758 return Err(CompressedAccountError::InvalidArgument);
759 }
760 if reference.input_compressed_accounts.len() != z_copy.input_compressed_accounts.len() {
761 return Err(CompressedAccountError::InvalidArgument);
762 }
763 if reference.output_compressed_accounts.len() != z_copy.output_compressed_accounts.len() {
764 return Err(CompressedAccountError::InvalidArgument);
765 }
766 if reference.read_only_addresses.len() != z_copy.read_only_addresses.len() {
767 return Err(CompressedAccountError::InvalidArgument);
768 }
769 if reference.read_only_accounts.len() != z_copy.read_only_accounts.len() {
770 return Err(CompressedAccountError::InvalidArgument);
771 }
772
773 assert_eq!(
775 z_copy.with_transaction_hash(),
776 reference.with_transaction_hash
777 );
778 assert_eq!(z_copy.bump(), Some(reference.bump));
779 assert_eq!(z_copy.owner(), reference.invoking_program_id);
780
781 if reference.compress_or_decompress_lamports > 0 {
783 assert_eq!(
784 z_copy.compress_or_decompress_lamports(),
785 Some(reference.compress_or_decompress_lamports)
786 );
787 } else {
788 assert_eq!(z_copy.compress_or_decompress_lamports(), None);
789 }
790
791 assert_eq!(
792 z_copy.is_compress(),
793 reference.is_compress && reference.compress_or_decompress_lamports > 0
794 );
795
796 if reference.with_cpi_context {
798 assert!(z_copy.cpi_context().is_some());
799 } else {
800 assert!(z_copy.cpi_context().is_none());
801 }
802
803 Ok(())
804 }
805
806 fn get_rnd_instruction_data_invoke_cpi_with_readonly(
808 rng: &mut StdRng,
809 ) -> InstructionDataInvokeCpiWithReadOnly {
810 InstructionDataInvokeCpiWithReadOnly {
811 mode: rng.gen_range(0..2),
812 bump: rng.gen(),
813 invoking_program_id: Pubkey::new_unique(),
814 compress_or_decompress_lamports: rng.gen(),
815 is_compress: rng.gen(),
816 with_cpi_context: rng.gen(),
817 with_transaction_hash: rng.gen(),
818 cpi_context: CompressedCpiContext {
819 first_set_context: rng.gen(),
820 set_context: rng.gen(),
821 cpi_context_account_index: rng.gen(),
822 },
823 proof: if rng.gen() {
824 Some(CompressedProof {
825 a: rng.gen(),
826 b: (0..64)
827 .map(|_| rng.gen())
828 .collect::<Vec<u8>>()
829 .try_into()
830 .unwrap(),
831 c: rng.gen(),
832 })
833 } else {
834 None
835 },
836 new_address_params: (0..rng.gen_range(1..5))
838 .map(|_| NewAddressParamsAssignedPacked {
839 seed: rng.gen(),
840 address_queue_account_index: rng.gen(),
841 address_merkle_tree_account_index: rng.gen(),
842 address_merkle_tree_root_index: rng.gen(),
843 assigned_to_account: rng.gen(),
844 assigned_account_index: rng.gen(),
845 })
846 .collect::<Vec<_>>(),
847 input_compressed_accounts: (0..rng.gen_range(1..5))
848 .map(|_| InAccount {
849 discriminator: rng.gen(),
850 data_hash: rng.gen(),
851 merkle_context: PackedMerkleContext {
852 merkle_tree_pubkey_index: rng.gen(),
853 queue_pubkey_index: rng.gen(),
854 leaf_index: rng.gen(),
855 prove_by_index: rng.gen(),
856 },
857 root_index: rng.gen(),
858 lamports: rng.gen(),
859 address: if rng.gen() { Some(rng.gen()) } else { None },
860 })
861 .collect(),
862 output_compressed_accounts: (0..rng.gen_range(1..5))
863 .map(|_| {
864 OutputCompressedAccountWithPackedContext {
865 compressed_account: CompressedAccount {
866 owner: Pubkey::new_unique(),
867 lamports: rng.gen(),
868 address: if rng.gen() { Some(rng.gen()) } else { None },
869 data: if rng.gen() {
870 Some(CompressedAccountData {
871 discriminator: rng.gen(),
872 data: vec![], data_hash: rng.gen(),
874 })
875 } else {
876 None
877 },
878 },
879 merkle_tree_index: rng.gen(),
880 }
881 })
882 .collect::<Vec<_>>(),
883 read_only_addresses: (0..rng.gen_range(1..5))
884 .map(|_| PackedReadOnlyAddress {
885 address: rng.gen(),
886 address_merkle_tree_account_index: rng.gen(),
887 address_merkle_tree_root_index: rng.gen(),
888 })
889 .collect::<Vec<_>>(),
890 read_only_accounts: (0..rng.gen_range(1..5))
891 .map(|_| PackedReadOnlyCompressedAccount {
892 account_hash: rng.gen(),
893 merkle_context: PackedMerkleContext {
894 merkle_tree_pubkey_index: rng.gen(),
895 queue_pubkey_index: rng.gen(),
896 leaf_index: rng.gen(),
897 prove_by_index: rng.gen(),
898 },
899 root_index: rng.gen(),
900 })
901 .collect::<Vec<_>>(),
902 }
903 }
904
905 #[test]
906 fn test_instruction_data_invoke_cpi_with_readonly_rnd() {
907 let mut thread_rng = ThreadRng::default();
908 let seed = thread_rng.gen();
909 println!("\n\ne2e test seed {}\n\n", seed);
910 let mut rng = StdRng::seed_from_u64(seed);
911
912 let num_iters = 1000;
913 for _ in 0..num_iters {
914 let value = get_rnd_instruction_data_invoke_cpi_with_readonly(&mut rng);
915
916 let mut vec = Vec::new();
917 value.serialize(&mut vec).unwrap();
918 let (zero_copy, _) = InstructionDataInvokeCpiWithReadOnly::zero_copy_at(&vec).unwrap();
919
920 assert_eq!(zero_copy, value);
922
923 compare_invoke_cpi_with_readonly(&value, &zero_copy).unwrap();
925 }
926 }
927}