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