From 5d8bf22e322edd062497e3f7a9584b8121d125a0 Mon Sep 17 00:00:00 2001 From: ananas-block Date: Sun, 8 Dec 2024 02:05:19 +0000 Subject: [PATCH] feat: read only accounts --- circuit-lib/verifier/src/lib.rs | 1 + .../account-compression/src/state/batch.rs | 6 +- .../src/state/batched_queue.rs | 67 ++- programs/system/src/errors.rs | 1 + programs/system/src/invoke/instruction.rs | 2 +- programs/system/src/invoke/nullify_state.rs | 13 +- programs/system/src/invoke/processor.rs | 48 +- programs/system/src/invoke/sum_check.rs | 37 +- .../system/src/invoke/verify_state_proof.rs | 174 +++++-- programs/system/src/invoke_cpi/instruction.rs | 5 +- programs/system/src/invoke_cpi/processor.rs | 9 +- programs/system/src/lib.rs | 14 +- programs/system/src/sdk/address.rs | 54 +- programs/system/src/sdk/compressed_account.rs | 113 +++-- programs/system/src/sdk/invoke.rs | 1 + test-programs/registry-test/tests/tests.rs | 6 +- .../system-cpi-test/src/create_pda.rs | 94 +++- test-programs/system-cpi-test/src/lib.rs | 9 +- test-programs/system-cpi-test/src/sdk.rs | 45 +- test-programs/system-cpi-test/tests/test.rs | 460 +++++++++++++++++- test-utils/src/e2e_test_env.rs | 43 +- 21 files changed, 980 insertions(+), 222 deletions(-) diff --git a/circuit-lib/verifier/src/lib.rs b/circuit-lib/verifier/src/lib.rs index 097af71da..df665d4e5 100644 --- a/circuit-lib/verifier/src/lib.rs +++ b/circuit-lib/verifier/src/lib.rs @@ -173,6 +173,7 @@ pub fn verify_merkle_proof_zkp( leaves: &[[u8; 32]], compressed_proof: &CompressedProof, ) -> Result<(), VerifierError> { + // TODO: replace with poseidon hash which avoids copying the data let public_inputs = [roots, leaves].concat(); // The public inputs are expected to be a multiple of 2 diff --git a/programs/account-compression/src/state/batch.rs b/programs/account-compression/src/state/batch.rs index faa2d9c16..9a20af9b8 100644 --- a/programs/account-compression/src/state/batch.rs +++ b/programs/account-compression/src/state/batch.rs @@ -233,7 +233,11 @@ impl Batch { let num_zkp_batches = self.get_num_zkp_batches(); self.num_inserted_zkps += 1; - + msg!( + "Marking batch as inserted in the merkle tree. num_inserted_zkps: {}", + self.num_inserted_zkps + ); + msg!("num_zkp_batches: {}", num_zkp_batches); // Batch has been successfully inserted into the tree. if self.num_inserted_zkps == num_zkp_batches { self.current_zkp_batch_index = 0; diff --git a/programs/account-compression/src/state/batched_queue.rs b/programs/account-compression/src/state/batched_queue.rs index 4da3ae806..837060f24 100644 --- a/programs/account-compression/src/state/batched_queue.rs +++ b/programs/account-compression/src/state/batched_queue.rs @@ -274,21 +274,36 @@ impl ZeroCopyBatchedQueueAccount { Ok(()) } - pub fn prove_inclusion_by_index_and_zero_out_leaf( - &mut self, - leaf_index: u64, - value: &[u8; 32], - ) -> Result<()> { - self.prove_inclusion_by_index_option_zero_out::(leaf_index, value) + pub fn prove_inclusion_by_index(&mut self, leaf_index: u64, value: &[u8; 32]) -> Result { + for (batch_index, batch) in self.batches.iter().enumerate() { + if batch.value_is_inserted_in_batch(leaf_index)? { + let index = batch.get_value_index_in_batch(leaf_index)?; + let element = self.value_vecs[batch_index] + .get_mut(index as usize) + .ok_or(AccountCompressionErrorCode::InclusionProofByIndexFailed)?; + + if element == value { + return Ok(true); + } else { + return err!(AccountCompressionErrorCode::InclusionProofByIndexFailed); + } + } + } + Ok(false) } - pub fn prove_inclusion_by_index(&mut self, leaf_index: u64, value: &[u8; 32]) -> Result<()> { - self.prove_inclusion_by_index_option_zero_out::(leaf_index, value) + pub fn could_exist_in_batches(&mut self, leaf_index: u64) -> Result<()> { + for batch in self.batches.iter() { + if batch.value_is_inserted_in_batch(leaf_index)? { + return Ok(()); + } + } + err!(AccountCompressionErrorCode::InclusionProofByIndexFailed) } /// Zero out a leaf by index if it exists in the queues value vec. If /// checked fail if leaf is not found. - fn prove_inclusion_by_index_option_zero_out( + pub fn prove_inclusion_by_index_and_zero_out_leaf( &mut self, leaf_index: u64, value: &[u8; 32], @@ -301,16 +316,14 @@ impl ZeroCopyBatchedQueueAccount { .ok_or(AccountCompressionErrorCode::InclusionProofByIndexFailed)?; if element == value { - if ZERO_OUT_LEAF { - *element = [0u8; 32]; - } + *element = [0u8; 32]; return Ok(()); } else { return err!(AccountCompressionErrorCode::InclusionProofByIndexFailed); } } } - err!(AccountCompressionErrorCode::InclusionProofByIndexFailed) + Ok(()) } pub fn get_batch_num_inserted_in_current_batch(&self) -> u64 { @@ -365,6 +378,7 @@ pub fn insert_into_current_batch( println!("value {:?}", value); if wipe { + msg!("wipe"); if let Some(blomfilter_stores) = bloom_filter_stores.as_mut() { if !current_batch.bloom_filter_is_wiped { (*blomfilter_stores) @@ -811,32 +825,39 @@ pub mod tests { { zero_copy_account.insert_into_current_batch(&value).unwrap(); assert_eq!( - zero_copy_account.prove_inclusion_by_index_option_zero_out::(1, &value), + zero_copy_account.prove_inclusion_by_index(1, &value), anchor_lang::err!(AccountCompressionErrorCode::InclusionProofByIndexFailed) ); assert_eq!( - zero_copy_account.prove_inclusion_by_index_option_zero_out::(1, &value), + zero_copy_account.prove_inclusion_by_index_and_zero_out_leaf(1, &value), anchor_lang::err!(AccountCompressionErrorCode::InclusionProofByIndexFailed) ); assert_eq!( - zero_copy_account.prove_inclusion_by_index_option_zero_out::(0, &value2), + zero_copy_account.prove_inclusion_by_index_and_zero_out_leaf(0, &value2), anchor_lang::err!(AccountCompressionErrorCode::InclusionProofByIndexFailed) ); assert!(zero_copy_account - .prove_inclusion_by_index_option_zero_out::(0, &value) + .prove_inclusion_by_index(0, &value) .is_ok()); + // prove inclusion for value out of range returns false + assert_eq!( + zero_copy_account + .prove_inclusion_by_index(100000, &[0u8; 32]) + .unwrap(), + false + ); assert!(zero_copy_account - .prove_inclusion_by_index_option_zero_out::(0, &value) + .prove_inclusion_by_index_and_zero_out_leaf(0, &value) .is_ok()); } // 2. Functional does not succeed on second invocation { assert_eq!( - zero_copy_account.prove_inclusion_by_index_option_zero_out::(0, &value), + zero_copy_account.prove_inclusion_by_index_and_zero_out_leaf(0, &value), anchor_lang::err!(AccountCompressionErrorCode::InclusionProofByIndexFailed) ); assert_eq!( - zero_copy_account.prove_inclusion_by_index_option_zero_out::(0, &value), + zero_copy_account.prove_inclusion_by_index(0, &value), anchor_lang::err!(AccountCompressionErrorCode::InclusionProofByIndexFailed) ); } @@ -848,17 +869,17 @@ pub mod tests { .unwrap(); assert_eq!( - zero_copy_account.prove_inclusion_by_index_option_zero_out::(0, &value2), + zero_copy_account.prove_inclusion_by_index_and_zero_out_leaf(0, &value2), anchor_lang::err!(AccountCompressionErrorCode::InclusionProofByIndexFailed) ); assert!(zero_copy_account - .prove_inclusion_by_index_option_zero_out::(1, &value2) + .prove_inclusion_by_index_and_zero_out_leaf(1, &value2) .is_ok()); } // 4. Functional does not succeed on second invocation { assert_eq!( - zero_copy_account.prove_inclusion_by_index_option_zero_out::(1, &value2), + zero_copy_account.prove_inclusion_by_index_and_zero_out_leaf(1, &value2), anchor_lang::err!(AccountCompressionErrorCode::InclusionProofByIndexFailed) ); } diff --git a/programs/system/src/errors.rs b/programs/system/src/errors.rs index 234025c26..b1487faa3 100644 --- a/programs/system/src/errors.rs +++ b/programs/system/src/errors.rs @@ -69,4 +69,5 @@ pub enum SystemProgramError { OutputMerkleTreeNotUnique, DataFieldUndefined, ReadOnlyAddressAlreadyExists, + ReadOnlyAccountDoesNotExist, } diff --git a/programs/system/src/invoke/instruction.rs b/programs/system/src/invoke/instruction.rs index eed73b192..a565fbecf 100644 --- a/programs/system/src/invoke/instruction.rs +++ b/programs/system/src/invoke/instruction.rs @@ -136,7 +136,7 @@ pub struct PackedReadOnlyAddress { } #[derive(Debug, PartialEq, Default, Clone, AnchorSerialize, AnchorDeserialize)] -pub struct ReadOnlyAddressParams { +pub struct ReadOnlyAddress { pub address: [u8; 32], pub address_merkle_tree_pubkey: Pubkey, pub address_merkle_tree_root_index: u16, diff --git a/programs/system/src/invoke/nullify_state.rs b/programs/system/src/invoke/nullify_state.rs index 5b8bd591e..dead3e263 100644 --- a/programs/system/src/invoke/nullify_state.rs +++ b/programs/system/src/invoke/nullify_state.rs @@ -24,10 +24,9 @@ pub fn insert_nullifiers< >( input_compressed_accounts_with_merkle_context: &'a [PackedCompressedAccountWithMerkleContext], ctx: &'a Context<'a, 'b, 'c, 'info, A>, - input_compressed_account_hashes: &'a [[u8; 32]], + nullifiers: &'a [[u8; 32]], invoking_program: &Option, tx_hash: [u8; 32], - num_read_only: usize, ) -> Result> { light_heap::bench_sbf_start!("cpda_insert_nullifiers_prep_accs"); let mut account_infos = vec![ @@ -57,19 +56,13 @@ pub fn insert_nullifiers< // nullifier queue is mutable. The network fee field in the queue is not // used. let mut network_fee_bundle = None; - let mut nullifiers = - Vec::with_capacity(input_compressed_accounts_with_merkle_context.len() - num_read_only); - for (account, account_hash) in input_compressed_accounts_with_merkle_context - .iter() - .zip(input_compressed_account_hashes) - { + for account in input_compressed_accounts_with_merkle_context.iter() { msg!("nullify state: account: {:?}", account); // Don't nullify read-only accounts. if account.read_only { continue; } leaf_indices.push(account.merkle_context.leaf_index); - nullifiers.push(*account_hash); let account_info = &ctx.remaining_accounts[account.merkle_context.nullifier_queue_pubkey_index as usize]; @@ -103,7 +96,7 @@ pub fn insert_nullifiers< light_heap::bench_sbf_start!("cpda_instruction_data"); let instruction_data = account_compression::instruction::InsertIntoNullifierQueues { - nullifiers, + nullifiers: nullifiers.to_vec(), leaf_indices, tx_hash: Some(tx_hash), }; diff --git a/programs/system/src/invoke/processor.rs b/programs/system/src/invoke/processor.rs index 54ccbc19c..7677bdeaf 100644 --- a/programs/system/src/invoke/processor.rs +++ b/programs/system/src/invoke/processor.rs @@ -14,11 +14,15 @@ use crate::{ sum_check::sum_check, verify_state_proof::{ create_tx_hash, fetch_input_compressed_account_roots, fetch_roots_address_merkle_tree, - hash_input_compressed_accounts, verify_read_only_account, - verify_read_only_address_queue_non_inclusion, verify_state_proof, + hash_input_compressed_accounts, verify_input_accounts_proof_by_index, + verify_read_only_account_inclusion, verify_read_only_address_queue_non_inclusion, + verify_state_proof, }, }, - sdk::accounts::{InvokeAccounts, SignerAccounts}, + sdk::{ + accounts::{InvokeAccounts, SignerAccounts}, + compressed_account::PackedReadOnlyCompressedAccount, + }, InstructionDataInvoke, }; @@ -61,13 +65,14 @@ pub fn process< ctx: Context<'a, 'b, 'c, 'info, A>, cpi_context_inputs: usize, read_only_addresses: Option>, + read_only_accounts: Option>, ) -> Result<()> { if inputs.relay_fee.is_some() { unimplemented!("Relay fee is not implemented yet."); } // Sum check --------------------------------------------------- bench_sbf_start!("cpda_sum_check"); - let (num_read_only_input_accounts, num_prove_by_index_input_accounts) = sum_check( + let num_prove_by_index_input_accounts = sum_check( &inputs.input_compressed_accounts_with_merkle_context, &inputs.output_compressed_accounts, &inputs.relay_fee, @@ -136,11 +141,6 @@ pub fn process< ); return err!(SystemProgramError::InvalidCapacity); } - verify_read_only_account( - &ctx.remaining_accounts, - &inputs.input_compressed_accounts_with_merkle_context, - &input_compressed_account_hashes, - )?; } bench_sbf_end!("cpda_hash_input_compressed_accounts"); @@ -207,7 +207,7 @@ pub fn process< // in certain cases we zero out roots in batched input queues. // These roots need to be zero prior to proof verification. bench_sbf_start!("cpda_nullifiers"); - let input_network_fee_bundle = if num_input_compressed_accounts > num_read_only_input_accounts { + let input_network_fee_bundle = if num_input_compressed_accounts != 0 { // Access the current slot let current_slot = Clock::get()?.slot; let tx_hash = create_tx_hash( @@ -224,7 +224,6 @@ pub fn process< &input_compressed_account_hashes, &invoking_program, tx_hash, - num_read_only_input_accounts, )? } else { None @@ -239,8 +238,14 @@ pub fn process< output_network_fee_bundle, )?; + // Proof inputs order: + // 1. input compressed accounts + // 2. read only compressed accounts + // 3. new addresses + // 4. read only addresses if num_prove_by_index_input_accounts < num_input_compressed_accounts || new_address_roots.capacity() != 0 + || read_only_accounts.as_ref().map_or(false, |x| !x.is_empty()) { bench_sbf_start!("cpda_verify_state_proof"); if let Some(proof) = inputs.proof.as_ref() { @@ -250,6 +255,10 @@ pub fn process< b: proof.b, c: proof.c, }; + verify_input_accounts_proof_by_index( + &ctx.remaining_accounts, + &inputs.input_compressed_accounts_with_merkle_context, + )?; let mut input_compressed_account_roots = Vec::with_capacity(num_input_compressed_accounts); fetch_input_compressed_account_roots( @@ -257,6 +266,15 @@ pub fn process< &ctx, &mut input_compressed_account_roots, )?; + let read_only_accounts = read_only_accounts.unwrap_or_default(); + let mut read_only_accounts_roots = Vec::with_capacity(read_only_accounts.len()); + fetch_input_compressed_account_roots( + &read_only_accounts, + &ctx, + &mut read_only_accounts_roots, + )?; + verify_read_only_account_inclusion(&ctx.remaining_accounts, &read_only_accounts)?; + fetch_roots_address_merkle_tree( &inputs.new_address_params, &read_only_addresses, @@ -272,12 +290,16 @@ pub fn process< new_addresses.push(read_only_address.address); } msg!("read_only_addresses {:?}", read_only_addresses); + msg!("read_only_accounts {:?}", read_only_accounts); + msg!("read_only_accounts_roots {:?}", read_only_accounts_roots); match verify_state_proof( &inputs.input_compressed_accounts_with_merkle_context, - &input_compressed_account_roots, + input_compressed_account_roots, &input_compressed_account_hashes, &new_address_roots, &new_addresses, + &read_only_accounts, + &read_only_accounts_roots, &compressed_verifier_proof, ) { Ok(_) => Ok(()), @@ -287,7 +309,7 @@ pub fn process< "input_compressed_account_hashes {:?}", input_compressed_account_hashes ); - msg!("input roots {:?}", input_compressed_account_roots); + // msg!("input roots {:?}", input_compressed_account_roots); msg!( "input_compressed_accounts_with_merkle_context: {:?}", inputs.input_compressed_accounts_with_merkle_context diff --git a/programs/system/src/invoke/sum_check.rs b/programs/system/src/invoke/sum_check.rs index 387afb9fa..336286c3d 100644 --- a/programs/system/src/invoke/sum_check.rs +++ b/programs/system/src/invoke/sum_check.rs @@ -14,9 +14,8 @@ pub fn sum_check( relay_fee: &Option, compress_or_decompress_lamports: &Option, is_compress: &bool, -) -> Result<(usize, usize)> { +) -> Result { let mut sum: u64 = 0; - let mut num_read_only = 0; let mut num_prove_by_index_accounts = 0; for compressed_account_with_context in input_compressed_accounts_with_merkle_context.iter() { if compressed_account_with_context @@ -29,9 +28,9 @@ pub fn sum_check( // Readonly accounts are not included in the sum check, since these are // not invalidated in this transaction. if compressed_account_with_context.read_only { - // unimplemented!("read_only accounts are not supported. Set read_only to false."); - num_read_only += 1; - continue; + unimplemented!("read_only accounts are not supported. Set read_only to false."); + // num_read_only += 1; + // continue; } sum = sum .checked_add(compressed_account_with_context.compressed_account.lamports) @@ -71,7 +70,7 @@ pub fn sum_check( } if sum == 0 { - Ok((num_read_only, num_prove_by_index_accounts)) + Ok(num_prove_by_index_accounts) } else { Err(SystemProgramError::SumCheckFailed.into()) } @@ -131,22 +130,7 @@ mod test { sum_check_test(&[100, 50], &[2125], Some(25 - 1), None, false).unwrap_err(); sum_check_test(&[100, 50], &[2125], Some(25 + 1), None, false).unwrap_err(); for i in 0..10 { - for j in i..10 { - println!("num read only = {}, num by index = {}", i, j); - let num_non_read_only = j - i; - if num_non_read_only != 0 { - sum_check_test_with_num( - &vec![150; j], - &vec![150; num_non_read_only], - None, - None, - false, - i, - j, - ) - .unwrap(); - } - } + sum_check_test_with_num(&vec![150; i], &vec![150; i], None, None, false, i).unwrap(); } } fn sum_check_test( @@ -163,7 +147,6 @@ mod test { compress_or_decompress_lamports, is_compress, 0, - 0, ) } fn sum_check_test_with_num( @@ -172,7 +155,6 @@ mod test { relay_fee: Option, compress_or_decompress_lamports: Option, is_compress: bool, - num_read_only: usize, num_by_index: usize, ) -> Result<()> { let mut inputs = Vec::new(); @@ -182,7 +164,6 @@ mod test { } else { None }; - let read_only = index < num_read_only; inputs.push(PackedCompressedAccountWithMerkleContext { compressed_account: CompressedAccount { owner: Keypair::new().pubkey(), @@ -197,7 +178,7 @@ mod test { queue_index, }, root_index: 1, - read_only, + read_only: false, }); } let mut outputs = Vec::new(); @@ -213,14 +194,14 @@ mod test { }); } - let (calc_num_read_only, calc_num_prove_by_index_accounts) = sum_check( + let calc_num_prove_by_index_accounts = sum_check( &inputs, &outputs, &relay_fee, &compress_or_decompress_lamports, &is_compress, )?; - assert_eq!(num_read_only, calc_num_read_only); + assert_eq!(num_by_index, calc_num_prove_by_index_accounts); Ok(()) } diff --git a/programs/system/src/invoke/verify_state_proof.rs b/programs/system/src/invoke/verify_state_proof.rs index aade5baae..d0f761d6d 100644 --- a/programs/system/src/invoke/verify_state_proof.rs +++ b/programs/system/src/invoke/verify_state_proof.rs @@ -1,6 +1,11 @@ use crate::{ errors::SystemProgramError, - sdk::{accounts::InvokeAccounts, compressed_account::PackedCompressedAccountWithMerkleContext}, + sdk::{ + accounts::InvokeAccounts, + compressed_account::{ + FetchRoot, PackedCompressedAccountWithMerkleContext, PackedReadOnlyCompressedAccount, + }, + }, NewAddressParamsPacked, }; use account_compression::{ @@ -23,7 +28,6 @@ use std::mem; use super::PackedReadOnlyAddress; -// TODO: add support for batched Merkle trees #[inline(never)] #[heap_neutral] pub fn fetch_input_compressed_account_roots< @@ -32,8 +36,9 @@ pub fn fetch_input_compressed_account_roots< 'c: 'info, 'info, A: InvokeAccounts<'info> + Bumps, + F: FetchRoot, >( - input_compressed_accounts_with_merkle_context: &'a [PackedCompressedAccountWithMerkleContext], + input_compressed_accounts_with_merkle_context: &'a [F], ctx: &'a Context<'a, 'b, 'c, 'info, A>, roots: &'a mut Vec<[u8; 32]>, ) -> Result<()> { @@ -42,14 +47,14 @@ pub fn fetch_input_compressed_account_roots< { // Skip accounts which prove inclusion by index in output queue. if input_compressed_account_with_context - .merkle_context + .get_merkle_context() .queue_index .is_some() { continue; } let merkle_tree_account_info = &ctx.remaining_accounts[input_compressed_account_with_context - .merkle_context + .get_merkle_context() .merkle_tree_pubkey_index as usize]; let mut discriminator_bytes = [0u8; 8]; @@ -64,8 +69,9 @@ pub fn fetch_input_compressed_account_roots< .map_err(ProgramError::from)?; let fetched_roots = &merkle_tree.roots; - (*roots) - .push(fetched_roots[input_compressed_account_with_context.root_index as usize]); + (*roots).push( + fetched_roots[input_compressed_account_with_context.get_root_index() as usize], + ); } BatchedMerkleTreeAccount::DISCRIMINATOR => { let merkle_tree = @@ -75,7 +81,7 @@ pub fn fetch_input_compressed_account_roots< .map_err(ProgramError::from)?; (*roots).push( merkle_tree.root_history - [input_compressed_account_with_context.root_index as usize], + [input_compressed_account_with_context.get_root_index() as usize], ); } _ => { @@ -122,6 +128,86 @@ pub fn fetch_roots_address_merkle_tree< Ok(()) } +/// For each input account which is marked to be proven by index +/// 1. check that it can exist in the output queue +/// - note the output queue checks whether the value acutally exists in the queue +/// - the purpose of this check is to catch marked input accounts which shouldn't be proven by index +#[inline(always)] +pub fn verify_input_accounts_proof_by_index<'a>( + remaining_accounts: &'a [AccountInfo<'_>], + input_accounts: &'a [PackedCompressedAccountWithMerkleContext], +) -> Result<()> { + for account in input_accounts.iter() { + if account.merkle_context.queue_index.is_some() { + let output_queue_account_info = + &remaining_accounts[account.merkle_context.nullifier_queue_pubkey_index as usize]; + let output_queue = + &mut ZeroCopyBatchedQueueAccount::output_queue_from_account_info_mut( + output_queue_account_info, + ) + .map_err(ProgramError::from)?; + output_queue + .could_exist_in_batches(account.merkle_context.leaf_index as u64) + .map_err(|_| SystemProgramError::ReadOnlyAccountDoesNotExist)?; + } + } + Ok(()) +} +/// For each read-only account +/// 1. prove inclusion by index in the output queue if leaf index should exist in the output queue. +/// 1.1. if proved inclusion by index, return Ok. +/// 2. prove non-inclusion in the bloom filters +/// 2.1. skip wiped batches. +/// 2.2. prove non-inclusion in the bloom filters for each batch. +#[inline(always)] +pub fn verify_read_only_account_inclusion<'a>( + remaining_accounts: &'a [AccountInfo<'_>], + read_only_accounts: &'a [PackedReadOnlyCompressedAccount], +) -> Result<()> { + for read_only_account in read_only_accounts.iter() { + let output_queue_account_info = &remaining_accounts[read_only_account + .merkle_context + .nullifier_queue_pubkey_index + as usize]; + let output_queue = &mut ZeroCopyBatchedQueueAccount::output_queue_from_account_info_mut( + output_queue_account_info, + ) + .map_err(ProgramError::from)?; + let proved_inclusion = output_queue + .prove_inclusion_by_index( + read_only_account.merkle_context.leaf_index as u64, + &read_only_account.account_hash, + ) + .map_err(|_| SystemProgramError::ReadOnlyAccountDoesNotExist)?; + if !proved_inclusion && read_only_account.merkle_context.queue_index.is_some() { + msg!("Expected read-only account in the output queue but does not exist."); + return err!(SystemProgramError::ReadOnlyAccountDoesNotExist); + } + // If we prove inclusion by index we do not need to check non-inclusion in bloom filters. + if !proved_inclusion { + let merkle_tree_account_info = &remaining_accounts + [read_only_account.merkle_context.merkle_tree_pubkey_index as usize]; + let merkle_tree = + &mut ZeroCopyBatchedMerkleTreeAccount::state_tree_from_account_info_mut( + merkle_tree_account_info, + ) + .map_err(ProgramError::from)?; + + let num_bloom_filters = merkle_tree.bloom_filter_stores.len(); + for i in 0..num_bloom_filters { + let bloom_filter_store = merkle_tree.bloom_filter_stores[i].as_mut_slice(); + let batch = &merkle_tree.batches[i]; + if !batch.bloom_filter_is_wiped { + batch + .check_non_inclusion(&read_only_account.account_hash, bloom_filter_store) + .map_err(|_| SystemProgramError::ReadOnlyAccountDoesNotExist)?; + } + } + } + } + Ok(()) +} + #[inline(always)] pub fn verify_read_only_address_queue_non_inclusion<'a>( remaining_accounts: &'a [AccountInfo<'_>], @@ -151,32 +237,32 @@ pub fn verify_read_only_address_queue_non_inclusion<'a>( Ok(()) } -#[inline(always)] -pub fn verify_read_only_account<'a>( - remaining_accounts: &'a [AccountInfo<'_>], - accounts: &'a [PackedCompressedAccountWithMerkleContext], - input_compressed_account_hashes: &'a [[u8; 32]], -) -> Result<()> { - for (i, account) in accounts.iter().enumerate() { - if account.read_only && account.merkle_context.queue_index.is_some() { - msg!("verify_read_only_account"); - msg!("merkle_context: {:?}", account.merkle_context); - let output_queue_account_info = - &remaining_accounts[account.merkle_context.nullifier_queue_pubkey_index as usize]; +// #[inline(always)] +// pub fn verify_read_only_account<'a>( +// remaining_accounts: &'a [AccountInfo<'_>], +// accounts: &'a [PackedCompressedAccountWithMerkleContext], +// input_compressed_account_hashes: &'a [[u8; 32]], +// ) -> Result<()> { +// for (i, account) in accounts.iter().enumerate() { +// if account.read_only && account.merkle_context.queue_index.is_some() { +// msg!("verify_read_only_account"); +// msg!("merkle_context: {:?}", account.merkle_context); +// let output_queue_account_info = +// &remaining_accounts[account.merkle_context.nullifier_queue_pubkey_index as usize]; - let output_queue = - &mut ZeroCopyBatchedQueueAccount::output_queue_from_account_info_mut( - output_queue_account_info, - ) - .map_err(ProgramError::from)?; - output_queue.prove_inclusion_by_index( - account.merkle_context.leaf_index as u64, - &input_compressed_account_hashes[i], - )?; - } - } - Ok(()) -} +// let output_queue = +// &mut ZeroCopyBatchedQueueAccount::output_queue_from_account_info_mut( +// output_queue_account_info, +// ) +// .map_err(ProgramError::from)?; +// output_queue.prove_inclusion_by_index( +// account.merkle_context.leaf_index as u64, +// &input_compressed_account_hashes[i], +// )?; +// } +// } +// Ok(()) +// } fn fetch_address_root< 'a, @@ -346,14 +432,16 @@ pub fn hash_input_compressed_accounts<'a, 'b, 'c: 'info, 'info>( #[heap_neutral] pub fn verify_state_proof( input_compressed_accounts_with_merkle_context: &[PackedCompressedAccountWithMerkleContext], - roots: &[[u8; 32]], + mut roots: Vec<[u8; 32]>, leaves: &[[u8; 32]], address_roots: &[[u8; 32]], addresses: &[[u8; 32]], + read_only_accounts: &[PackedReadOnlyCompressedAccount], + read_only_roots: &[[u8; 32]], compressed_proof: &CompressedProof, ) -> anchor_lang::Result<()> { // Filter out leaves that are not in the proof (proven by index). - let proof_input_leaves = leaves + let mut proof_input_leaves = leaves .iter() .enumerate() .filter(|(x, _)| { @@ -364,9 +452,21 @@ pub fn verify_state_proof( }) .map(|x| *x.1) .collect::>(); + msg!("proof_input_leaves: {:?}", proof_input_leaves); + + read_only_accounts.iter().for_each(|x| { + if x.merkle_context.queue_index.is_none() { + proof_input_leaves.extend_from_slice(&[x.account_hash]); + } + }); + msg!("roots: {:?}", roots); + + roots.extend_from_slice(read_only_roots); + msg!("proof_input_leaves: {:?}", proof_input_leaves); + msg!("roots: {:?}", roots); if !addresses.is_empty() && !proof_input_leaves.is_empty() { verify_create_addresses_and_merkle_proof_zkp( - roots, + &roots, &proof_input_leaves, address_roots, addresses, @@ -377,7 +477,7 @@ pub fn verify_state_proof( verify_create_addresses_zkp(address_roots, addresses, compressed_proof) .map_err(ProgramError::from)?; } else { - verify_merkle_proof_zkp(roots, &proof_input_leaves, compressed_proof) + verify_merkle_proof_zkp(&roots, &proof_input_leaves, compressed_proof) .map_err(ProgramError::from)?; } Ok(()) diff --git a/programs/system/src/invoke_cpi/instruction.rs b/programs/system/src/invoke_cpi/instruction.rs index 951ad4f80..7dae64f1b 100644 --- a/programs/system/src/invoke_cpi/instruction.rs +++ b/programs/system/src/invoke_cpi/instruction.rs @@ -10,8 +10,7 @@ use crate::{ sdk::{ accounts::{InvokeAccounts, SignerAccounts}, compressed_account::{ - MerkleContext, PackedCompressedAccountWithMerkleContext, - PackedReadOnlyCompressedAccount, + PackedCompressedAccountWithMerkleContext, PackedReadOnlyCompressedAccount, }, CompressedCpiContext, }, @@ -117,7 +116,7 @@ impl InstructionDataInvokeCpi { } #[derive(Debug, PartialEq, Default, Clone, AnchorSerialize, AnchorDeserialize)] -pub struct InstructionDataInvokeCpiWithReadOnlyAddress { +pub struct InstructionDataInvokeCpiWithReadOnly { pub invoke_cpi: InstructionDataInvokeCpi, pub read_only_addresses: Option>, pub read_only_accounts: Option>, diff --git a/programs/system/src/invoke_cpi/processor.rs b/programs/system/src/invoke_cpi/processor.rs index f30f0b577..e59d56e43 100644 --- a/programs/system/src/invoke_cpi/processor.rs +++ b/programs/system/src/invoke_cpi/processor.rs @@ -3,9 +3,10 @@ use light_heap::{bench_sbf_end, bench_sbf_start}; use super::verify_signer::cpi_signer_checks; use crate::{ - invoke::processor::process, invoke_cpi::instruction::InvokeCpiInstruction, - sdk::accounts::SignerAccounts, InstructionDataInvoke, InstructionDataInvokeCpi, - PackedReadOnlyAddress, + invoke::processor::process, + invoke_cpi::instruction::InvokeCpiInstruction, + sdk::{accounts::SignerAccounts, compressed_account::PackedReadOnlyCompressedAccount}, + InstructionDataInvoke, InstructionDataInvokeCpi, PackedReadOnlyAddress, }; /// Processes an `InvokeCpi` instruction. @@ -17,6 +18,7 @@ pub fn process_invoke_cpi<'a, 'b, 'c: 'info + 'b, 'info>( mut ctx: Context<'a, 'b, 'c, 'info, InvokeCpiInstruction<'info>>, inputs: InstructionDataInvokeCpi, read_only_addresses: Option>, + read_only_accounts: Option>, ) -> Result<()> { bench_sbf_start!("cpda_cpi_signer_checks"); cpi_signer_checks( @@ -62,5 +64,6 @@ pub fn process_invoke_cpi<'a, 'b, 'c: 'info + 'b, 'info>( ctx, cpi_context_inputs_len, read_only_addresses, + read_only_accounts, ) } diff --git a/programs/system/src/lib.rs b/programs/system/src/lib.rs index 98ce3aa97..0fb36f41f 100644 --- a/programs/system/src/lib.rs +++ b/programs/system/src/lib.rs @@ -66,7 +66,7 @@ pub mod light_system_program { &inputs.input_compressed_accounts_with_merkle_context, &ctx.accounts.authority.key(), )?; - process(inputs, None, ctx, 0, None) + process(inputs, None, ctx, 0, None, None) } pub fn invoke_cpi<'a, 'b, 'c: 'info, 'info>( @@ -78,7 +78,7 @@ pub mod light_system_program { InstructionDataInvokeCpi::deserialize(&mut inputs.as_slice())?; bench_sbf_end!("cpda_deserialize"); - process_invoke_cpi(ctx, inputs, None) + process_invoke_cpi(ctx, inputs, None, None) } pub fn invoke_cpi_with_read_only_address<'a, 'b, 'c: 'info, 'info>( @@ -86,8 +86,7 @@ pub mod light_system_program { inputs: Vec, ) -> Result<()> { bench_sbf_start!("cpda_deserialize"); - let inputs = - InstructionDataInvokeCpiWithReadOnlyAddress::deserialize(&mut inputs.as_slice())?; + let inputs = InstructionDataInvokeCpiWithReadOnly::deserialize(&mut inputs.as_slice())?; bench_sbf_end!("cpda_deserialize"); // disable set cpi context because cpi context account uses InvokeCpiInstruction if let Some(cpi_context) = inputs.invoke_cpi.cpi_context { @@ -97,7 +96,12 @@ pub mod light_system_program { return Err(SystemProgramError::InstructionNotCallable.into()); } } - process_invoke_cpi(ctx, inputs.invoke_cpi, inputs.read_only_addresses) + process_invoke_cpi( + ctx, + inputs.invoke_cpi, + inputs.read_only_addresses, + inputs.read_only_accounts, + ) } /// This function is a stub to allow Anchor to include the input types in diff --git a/programs/system/src/sdk/address.rs b/programs/system/src/sdk/address.rs index c3a8bbba6..2d0a4d719 100644 --- a/programs/system/src/sdk/address.rs +++ b/programs/system/src/sdk/address.rs @@ -5,7 +5,11 @@ use light_utils::{hash_to_bn254_field_size_be, hashv_to_bn254_field_size_be}; use crate::{ errors::SystemProgramError, NewAddressParams, NewAddressParamsPacked, PackedReadOnlyAddress, - ReadOnlyAddressParams, + ReadOnlyAddress, +}; + +use super::compressed_account::{ + pack_merkle_context, PackedReadOnlyCompressedAccount, ReadOnlyCompressedAccount, }; pub fn derive_address_legacy(merkle_tree_pubkey: &Pubkey, seed: &[u8; 32]) -> Result<[u8; 32]> { let hash = match hash_to_bn254_field_size_be( @@ -96,33 +100,45 @@ pub fn pack_new_address_params( } pub fn pack_read_only_address_params( - new_address_params: &[ReadOnlyAddressParams], + new_address_params: &[ReadOnlyAddress], remaining_accounts: &mut HashMap, ) -> Vec { - let mut new_address_params_packed = new_address_params + new_address_params .iter() .map(|x| PackedReadOnlyAddress { address: x.address, address_merkle_tree_root_index: x.address_merkle_tree_root_index, - address_merkle_tree_account_index: 0, // will be assigned later + address_merkle_tree_account_index: pack_account( + &x.address_merkle_tree_pubkey, + remaining_accounts, + ), }) - .collect::>(); - let mut next_index: usize = remaining_accounts.len(); - for (i, params) in new_address_params.iter().enumerate() { - match remaining_accounts.get(¶ms.address_merkle_tree_pubkey) { - Some(_) => {} - None => { - remaining_accounts.insert(params.address_merkle_tree_pubkey, next_index); - next_index += 1; - } - }; - new_address_params_packed[i].address_merkle_tree_account_index = *remaining_accounts - .get(¶ms.address_merkle_tree_pubkey) - .unwrap() - as u8; + .collect::>() +} + +pub fn pack_account(pubkey: &Pubkey, remaining_accounts: &mut HashMap) -> u8 { + match remaining_accounts.get(pubkey) { + Some(index) => *index as u8, + None => { + let next_index = remaining_accounts.len(); + remaining_accounts.insert(*pubkey, next_index); + next_index as u8 + } } +} - new_address_params_packed +pub fn pack_read_only_accounts( + accounts: &[ReadOnlyCompressedAccount], + remaining_accounts: &mut HashMap, +) -> Vec { + accounts + .iter() + .map(|x| PackedReadOnlyCompressedAccount { + account_hash: x.account_hash, + merkle_context: pack_merkle_context(&[x.merkle_context], remaining_accounts)[0], + root_index: x.root_index, + }) + .collect::>() } #[cfg(test)] diff --git a/programs/system/src/sdk/compressed_account.rs b/programs/system/src/sdk/compressed_account.rs index c543f0ba4..3795d69d2 100644 --- a/programs/system/src/sdk/compressed_account.rs +++ b/programs/system/src/sdk/compressed_account.rs @@ -4,6 +4,13 @@ use anchor_lang::prelude::*; use light_hasher::{Hasher, Poseidon}; use light_utils::hash_to_bn254_field_size_be; +use super::address::pack_account; + +pub trait FetchRoot { + fn get_root_index(&self) -> u16; + fn get_merkle_context(&self) -> PackedMerkleContext; +} + #[derive(Debug, PartialEq, Default, Clone, AnchorSerialize, AnchorDeserialize)] pub struct PackedCompressedAccountWithMerkleContext { pub compressed_account: CompressedAccount, @@ -13,6 +20,14 @@ pub struct PackedCompressedAccountWithMerkleContext { /// Placeholder to mark accounts read-only unimplemented set to false. pub read_only: bool, } +impl FetchRoot for PackedCompressedAccountWithMerkleContext { + fn get_root_index(&self) -> u16 { + self.root_index + } + fn get_merkle_context(&self) -> PackedMerkleContext { + self.merkle_context + } +} #[derive(Debug, PartialEq, Default, Clone, AnchorSerialize, AnchorDeserialize)] pub struct CompressedAccountWithMerkleContext { @@ -28,16 +43,71 @@ impl CompressedAccountWithMerkleContext { } } +impl CompressedAccountWithMerkleContext { + pub fn into_read_only(&self, root_index: Option) -> Result { + let account_hash = self.hash()?; + let merkle_context = if root_index.is_none() { + let mut merkle_context = self.merkle_context; + merkle_context.queue_index = Some(QueueIndex::default()); + merkle_context + } else { + self.merkle_context + }; + Ok(ReadOnlyCompressedAccount { + account_hash, + merkle_context, + root_index: root_index.unwrap_or_default(), + }) + } + + pub fn pack( + &self, + root_index: Option, + remaining_accounts: &mut HashMap, + ) -> Result { + Ok(PackedCompressedAccountWithMerkleContext { + compressed_account: self.compressed_account.clone(), + merkle_context: PackedMerkleContext { + merkle_tree_pubkey_index: pack_account( + &self.merkle_context.merkle_tree_pubkey, + remaining_accounts, + ), + nullifier_queue_pubkey_index: pack_account( + &self.merkle_context.nullifier_queue_pubkey, + remaining_accounts, + ), + leaf_index: self.merkle_context.leaf_index, + queue_index: if root_index.is_none() { + Some(QueueIndex::default()) + } else { + None + }, + }, + root_index: root_index.unwrap_or_default(), + read_only: false, + }) + } +} #[derive(Debug, PartialEq, Default, Clone, AnchorSerialize, AnchorDeserialize)] pub struct ReadOnlyCompressedAccount { pub account_hash: [u8; 32], - pub account: MerkleContext, + pub merkle_context: MerkleContext, + pub root_index: u16, } #[derive(Debug, PartialEq, Default, Clone, AnchorSerialize, AnchorDeserialize)] pub struct PackedReadOnlyCompressedAccount { pub account_hash: [u8; 32], - pub account: PackedCompressedAccountWithMerkleContext, + pub merkle_context: PackedMerkleContext, + pub root_index: u16, +} +impl FetchRoot for PackedReadOnlyCompressedAccount { + fn get_root_index(&self) -> u16 { + self.root_index + } + fn get_merkle_context(&self) -> PackedMerkleContext { + self.merkle_context + } } #[derive(Debug, Clone, Copy, AnchorSerialize, AnchorDeserialize, PartialEq, Default)] @@ -72,41 +142,18 @@ pub fn pack_merkle_context( merkle_context: &[MerkleContext], remaining_accounts: &mut HashMap, ) -> Vec { - let mut merkle_context_packed = merkle_context + merkle_context .iter() .map(|x| PackedMerkleContext { leaf_index: x.leaf_index, - merkle_tree_pubkey_index: 0, // will be assigned later - nullifier_queue_pubkey_index: 0, // will be assigned later - queue_index: None, + merkle_tree_pubkey_index: pack_account(&x.merkle_tree_pubkey, remaining_accounts), + nullifier_queue_pubkey_index: pack_account( + &x.nullifier_queue_pubkey, + remaining_accounts, + ), + queue_index: x.queue_index, }) - .collect::>(); - let mut index: usize = remaining_accounts.len(); - for (i, params) in merkle_context.iter().enumerate() { - match remaining_accounts.get(¶ms.merkle_tree_pubkey) { - Some(_) => {} - None => { - remaining_accounts.insert(params.merkle_tree_pubkey, index); - index += 1; - } - }; - merkle_context_packed[i].merkle_tree_pubkey_index = - *remaining_accounts.get(¶ms.merkle_tree_pubkey).unwrap() as u8; - } - - for (i, params) in merkle_context.iter().enumerate() { - match remaining_accounts.get(¶ms.nullifier_queue_pubkey) { - Some(_) => {} - None => { - remaining_accounts.insert(params.nullifier_queue_pubkey, index); - index += 1; - } - }; - merkle_context_packed[i].nullifier_queue_pubkey_index = *remaining_accounts - .get(¶ms.nullifier_queue_pubkey) - .unwrap() as u8; - } - merkle_context_packed + .collect::>() } #[derive(Debug, PartialEq, Default, Clone, AnchorSerialize, AnchorDeserialize)] diff --git a/programs/system/src/sdk/invoke.rs b/programs/system/src/sdk/invoke.rs index 049176d1a..a985df60b 100644 --- a/programs/system/src/sdk/invoke.rs +++ b/programs/system/src/sdk/invoke.rs @@ -58,6 +58,7 @@ pub fn create_invoke_instruction( .sort_by(|a, b| a.merkle_tree_index.cmp(&b.merkle_tree_index)); } let mut inputs = Vec::new(); + println!("inputs_struct: {:?}", inputs_struct); InstructionDataInvoke::serialize(&inputs_struct, &mut inputs).unwrap(); diff --git a/test-programs/registry-test/tests/tests.rs b/test-programs/registry-test/tests/tests.rs index a00c0e0e4..ad95923ec 100644 --- a/test-programs/registry-test/tests/tests.rs +++ b/test-programs/registry-test/tests/tests.rs @@ -530,7 +530,7 @@ async fn test_custom_forester() { let cpi_context_keypair = Keypair::new(); // create work 1 item in address and nullifier queue each let (mut state_merkle_tree_bundle, _, mut rpc) = { - let mut e2e_env = init_program_test_env(rpc, &env).await; + let mut e2e_env = init_program_test_env(rpc, &env, false).await; e2e_env.indexer.state_merkle_trees.clear(); // add state merkle tree to the indexer e2e_env @@ -625,7 +625,7 @@ async fn test_custom_forester_batched() { e2e_env.keypair_action_config.fee_assert = false; e2e_env } else { - init_program_test_env(rpc, &env).await + init_program_test_env(rpc, &env, false).await }; e2e_env.indexer.state_merkle_trees.clear(); // add state merkle tree to the indexer @@ -893,7 +893,7 @@ async fn test_register_and_update_forester_pda() { // create work 1 item in address and nullifier queue each let (mut state_merkle_tree_bundle, mut address_merkle_tree, mut rpc) = { - let mut e2e_env = init_program_test_env(rpc, &env).await; + let mut e2e_env = init_program_test_env(rpc, &env, false).await; // remove batched Merkle tree, fee assert makes this test flaky otherwise e2e_env.indexer.state_merkle_trees.remove(1); e2e_env.create_address(None, None).await; diff --git a/test-programs/system-cpi-test/src/create_pda.rs b/test-programs/system-cpi-test/src/create_pda.rs index 316b06047..7c0087df8 100644 --- a/test-programs/system-cpi-test/src/create_pda.rs +++ b/test-programs/system-cpi-test/src/create_pda.rs @@ -5,8 +5,10 @@ use account_compression::{ use anchor_lang::prelude::*; use anchor_lang::Discriminator; use light_hasher::{errors::HasherError, DataHasher, Poseidon}; -use light_system_program::InstructionDataInvokeCpiWithReadOnlyAddress; -use light_system_program::ReadOnlyAddressParamsPacked; +use light_system_program::sdk::compressed_account::PackedCompressedAccountWithMerkleContext; +use light_system_program::sdk::compressed_account::PackedReadOnlyCompressedAccount; +use light_system_program::InstructionDataInvokeCpiWithReadOnly; +use light_system_program::PackedReadOnlyAddress; use light_system_program::{ invoke::processor::CompressedProof, program::LightSystemProgram, @@ -20,6 +22,8 @@ use light_system_program::{ #[derive(AnchorSerialize, AnchorDeserialize, Debug, Clone, PartialEq)] pub enum CreatePdaMode { + Functional, + BatchFunctional, ProgramIsSigner, ProgramIsNotSigner, InvalidSignerSeeds, @@ -35,6 +39,13 @@ pub enum CreatePdaMode { InvalidReadOnlyMerkleTree, ReadOnlyProofOfInsertedAddress, UseReadOnlyAddressInAccount, + InvalidReadOnlyAccount, + InvalidReadOnlyAccountRootIndex, + InvalidReadOnlyAccountMerkleTree, + InvalidReadOnlyAccountOutputQueue, + InvalidProofReadOnlyAccount, + ReadOnlyProofOfInsertedAccount, + ProofIsNoneReadOnlyAccount, } pub fn process_create_pda<'info>( @@ -46,7 +57,9 @@ pub fn process_create_pda<'info>( cpi_context: Option, mode: CreatePdaMode, bump: u8, - read_only_address: Option>, + read_only_address: Option>, + read_only_accounts: Option>, + input_accounts: Option>, ) -> Result<()> { let compressed_pda = create_compressed_pda_data( data, @@ -76,7 +89,16 @@ pub fn process_create_pda<'info>( | CreatePdaMode::InvalidReadOnlyRootIndex | CreatePdaMode::InvalidReadOnlyMerkleTree | CreatePdaMode::UseReadOnlyAddressInAccount - | CreatePdaMode::ReadOnlyProofOfInsertedAddress => { + | CreatePdaMode::ReadOnlyProofOfInsertedAddress + | CreatePdaMode::InvalidReadOnlyAccount + | CreatePdaMode::InvalidReadOnlyAccountRootIndex + | CreatePdaMode::InvalidReadOnlyAccountMerkleTree + | CreatePdaMode::ReadOnlyProofOfInsertedAccount + | CreatePdaMode::BatchFunctional + | CreatePdaMode::Functional + | CreatePdaMode::InvalidProofReadOnlyAccount + | CreatePdaMode::InvalidReadOnlyAccountOutputQueue + | CreatePdaMode::ProofIsNoneReadOnlyAccount => { cpi_compressed_pda_transfer_as_program( &ctx, proof, @@ -85,6 +107,8 @@ pub fn process_create_pda<'info>( cpi_context, bump, read_only_address, + read_only_accounts, + input_accounts, mode, )?; } @@ -97,6 +121,8 @@ pub fn process_create_pda<'info>( cpi_context, bump, read_only_address, + None, + None, CreatePdaMode::InvalidSignerSeeds, )?; } @@ -109,6 +135,8 @@ pub fn process_create_pda<'info>( cpi_context, bump, read_only_address, + None, + None, CreatePdaMode::InvalidInvokingProgram, )?; } @@ -121,6 +149,8 @@ pub fn process_create_pda<'info>( cpi_context, bump, read_only_address, + None, + None, CreatePdaMode::WriteToAccountNotOwned, )?; } @@ -133,6 +163,8 @@ pub fn process_create_pda<'info>( cpi_context, bump, read_only_address, + None, + None, CreatePdaMode::NoData, )?; } @@ -194,7 +226,9 @@ fn cpi_compressed_pda_transfer_as_program<'info>( compressed_pda: OutputCompressedAccountWithPackedContext, cpi_context: Option, bump: u8, - mut read_only_address: Option>, + mut read_only_address: Option>, + mut read_only_accounts: Option>, + input_accounts: Option>, mode: CreatePdaMode, ) -> Result<()> { let invoking_program = match mode { @@ -225,7 +259,7 @@ fn cpi_compressed_pda_transfer_as_program<'info>( let mut inputs_struct = InstructionDataInvokeCpi { relay_fee: None, - input_compressed_accounts_with_merkle_context: Vec::new(), + input_compressed_accounts_with_merkle_context: input_accounts.unwrap_or_default(), output_compressed_accounts: vec![compressed_pda], proof, new_address_params: vec![new_address_params], @@ -236,7 +270,8 @@ fn cpi_compressed_pda_transfer_as_program<'info>( // defining seeds again so that the cpi doesn't fail we want to test the check in the compressed pda program let seeds: [&[u8]; 2] = [CPI_AUTHORITY_PDA_SEED, &[bump]]; msg!("read only address {:?}", read_only_address); - if read_only_address.is_some() { + msg!("read only accounts {:?}", read_only_accounts); + if read_only_address.is_some() || read_only_accounts.is_some() { if mode == CreatePdaMode::ReadOnlyProofOfInsertedAddress { let read_only_address = read_only_address.as_mut().unwrap(); read_only_address[0].address = inputs_struct.output_compressed_accounts[0] @@ -254,7 +289,7 @@ fn cpi_compressed_pda_transfer_as_program<'info>( println!("ctx.remaining_accounts {:?}", ctx.remaining_accounts); let mut remaining_accounts = ctx.remaining_accounts.to_vec(); - { + if read_only_address.is_some() { let read_only_address = read_only_address.as_mut().unwrap(); match mode { CreatePdaMode::InvalidReadOnlyMerkleTree => { @@ -279,16 +314,51 @@ fn cpi_compressed_pda_transfer_as_program<'info>( _ => {} } } + if read_only_accounts.is_some() { + let read_only_account = read_only_accounts.as_mut().unwrap(); + match mode { + CreatePdaMode::InvalidReadOnlyAccountMerkleTree => { + read_only_account[0].merkle_context.merkle_tree_pubkey_index = + read_only_account[0] + .merkle_context + .nullifier_queue_pubkey_index; + } + CreatePdaMode::InvalidReadOnlyAccountRootIndex => { + let init_value = read_only_account[0].root_index; + read_only_account[0].root_index = + read_only_account[0].root_index.saturating_sub(1); + if read_only_account[0].root_index == init_value { + read_only_account[0].root_index = + read_only_account[0].root_index.saturating_add(1); + } + } + CreatePdaMode::InvalidReadOnlyAccount => { + read_only_account[0].account_hash = [0; 32]; + } + CreatePdaMode::ProofIsNoneReadOnlyAccount => { + inputs_struct.proof = None; + } + CreatePdaMode::InvalidProofReadOnlyAccount => { + inputs_struct.proof = Some(CompressedProof::default()); + } + CreatePdaMode::InvalidReadOnlyAccountOutputQueue => { + read_only_account[0] + .merkle_context + .nullifier_queue_pubkey_index = + read_only_account[0].merkle_context.merkle_tree_pubkey_index; + } + _ => {} + } + } msg!("read_only_address {:?}", read_only_address); - let inputs_struct = InstructionDataInvokeCpiWithReadOnlyAddress { + let inputs_struct = InstructionDataInvokeCpiWithReadOnly { invoke_cpi: inputs_struct, read_only_addresses: read_only_address, - read_only_accounts: None, + read_only_accounts, }; let mut inputs = Vec::new(); - InstructionDataInvokeCpiWithReadOnlyAddress::serialize(&inputs_struct, &mut inputs) - .unwrap(); + InstructionDataInvokeCpiWithReadOnly::serialize(&inputs_struct, &mut inputs).unwrap(); let cpi_accounts = light_system_program::cpi::accounts::InvokeCpiInstruction { fee_payer: ctx.accounts.signer.to_account_info(), diff --git a/test-programs/system-cpi-test/src/lib.rs b/test-programs/system-cpi-test/src/lib.rs index 1283dbaf8..26e4359bd 100644 --- a/test-programs/system-cpi-test/src/lib.rs +++ b/test-programs/system-cpi-test/src/lib.rs @@ -14,8 +14,9 @@ use account_compression::{ }; pub use invalidate_not_owned_account::*; use light_system_program::sdk::compressed_account::PackedCompressedAccountWithMerkleContext; +use light_system_program::sdk::compressed_account::PackedReadOnlyCompressedAccount; use light_system_program::sdk::CompressedCpiContext; -use light_system_program::ReadOnlyAddressParamsPacked; +use light_system_program::PackedReadOnlyAddress; declare_id!("FNt7byTHev1k5x2cXZLBr8TdWiC3zoP5vcnZR4P682Uy"); @@ -33,7 +34,9 @@ pub mod system_cpi_test { signer_is_program: CreatePdaMode, bump: u8, cpi_context: Option, - read_only_address: Option>, + read_only_address: Option>, + read_only_accounts: Option>, + input_accounts: Option>, ) -> Result<()> { process_create_pda( ctx, @@ -45,6 +48,8 @@ pub mod system_cpi_test { signer_is_program, bump, read_only_address, + read_only_accounts, + input_accounts, ) } diff --git a/test-programs/system-cpi-test/src/sdk.rs b/test-programs/system-cpi-test/src/sdk.rs index b35e1e1a9..1534673d0 100644 --- a/test-programs/system-cpi-test/src/sdk.rs +++ b/test-programs/system-cpi-test/src/sdk.rs @@ -13,11 +13,16 @@ use light_compressed_token::{ use light_system_program::{ invoke::processor::CompressedProof, sdk::{ - address::{pack_new_address_params, pack_read_only_address_params}, - compressed_account::PackedCompressedAccountWithMerkleContext, + address::{ + pack_new_address_params, pack_read_only_accounts, pack_read_only_address_params, + }, + compressed_account::{ + CompressedAccountWithMerkleContext, PackedCompressedAccountWithMerkleContext, + ReadOnlyCompressedAccount, + }, }, utils::get_registered_program_pda, - NewAddressParams, ReadOnlyAddressParams, + NewAddressParams, ReadOnlyAddress, }; use solana_sdk::{instruction::Instruction, pubkey::Pubkey}; @@ -34,7 +39,11 @@ pub struct CreateCompressedPdaInstructionInputs<'a> { pub owner_program: &'a Pubkey, pub signer_is_program: CreatePdaMode, pub registered_program_pda: &'a Pubkey, - pub readonly_adresses: Option>, + pub readonly_adresses: Option>, + pub read_only_accounts: Option>, + pub input_compressed_accounts_with_merkle_context: + Option>, + pub state_roots: Option>>, } pub fn create_pda_instruction(input_params: CreateCompressedPdaInstructionInputs) -> Instruction { @@ -49,8 +58,30 @@ pub fn create_pda_instruction(input_params: CreateCompressedPdaInstructionInputs let read_only_address = input_params .readonly_adresses .as_ref() - .map(|readonly_adresses| { - pack_read_only_address_params(readonly_adresses, &mut remaining_accounts) + .map(|read_only_adresses| { + pack_read_only_address_params(read_only_adresses, &mut remaining_accounts) + }); + let read_only_accounts = input_params + .read_only_accounts + .as_ref() + .map(|read_only_accounts| { + pack_read_only_accounts(read_only_accounts, &mut remaining_accounts) + }); + let input_accounts = input_params + .input_compressed_accounts_with_merkle_context + .as_ref() + .map(|input_accounts| { + input_accounts + .iter() + .enumerate() + .map(|(i, x)| { + x.pack( + input_params.state_roots.as_ref().unwrap()[i], + &mut remaining_accounts, + ) + .unwrap() + }) + .collect::>() }); let instruction_data = crate::instruction::CreateCompressedPda { data: input_params.data, @@ -61,6 +92,8 @@ pub fn create_pda_instruction(input_params: CreateCompressedPdaInstructionInputs signer_is_program: input_params.signer_is_program, cpi_context: None, read_only_address, + read_only_accounts, + input_accounts, }; let compressed_token_cpi_authority_pda = get_cpi_authority_pda().0; diff --git a/test-programs/system-cpi-test/tests/test.rs b/test-programs/system-cpi-test/tests/test.rs index f05f13581..b8ecae8aa 100644 --- a/test-programs/system-cpi-test/tests/test.rs +++ b/test-programs/system-cpi-test/tests/test.rs @@ -1,6 +1,8 @@ #![cfg(feature = "test-sbf")] use account_compression::errors::AccountCompressionErrorCode; +use account_compression::InitStateTreeAccountsInstructionData; +use anchor_lang::error::ErrorCode; use anchor_lang::AnchorDeserialize; use light_compressed_token::process_transfer::InputTokenDataWithContext; use light_compressed_token::token_data::AccountState; @@ -14,13 +16,18 @@ use light_system_program::sdk::compressed_account::{ }; use light_system_program::sdk::event::PublicTransactionEvent; use light_system_program::sdk::CompressedCpiContext; -use light_system_program::{NewAddressParams, ReadOnlyAddressParams}; +use light_system_program::{NewAddressParams, ReadOnlyAddress}; +use light_test_utils::e2e_test_env::init_program_test_env; use light_test_utils::indexer::TestIndexer; use light_test_utils::spl::{create_mint_helper, mint_tokens_helper}; use light_test_utils::system_program::transfer_compressed_sol_test; +use light_test_utils::test_batch_forester::perform_batch_append; use light_test_utils::test_env::{setup_test_programs_with_accounts, EnvAccounts}; -use light_test_utils::{assert_rpc_error, Indexer, RpcConnection, RpcError, TokenDataWithContext}; +use light_test_utils::{ + assert_rpc_error, env_accounts, Indexer, RpcConnection, RpcError, TokenDataWithContext, +}; use light_utils::hash_to_bn254_field_size_be; +use light_verifier::VerifierError; use serial_test::serial; use solana_sdk::signature::Keypair; use solana_sdk::{pubkey::Pubkey, signer::Signer, transaction::Transaction}; @@ -31,6 +38,350 @@ use system_cpi_test::sdk::{ use system_cpi_test::{self, RegisteredUser, TokenTransferData, WithInputAccountsMode}; use system_cpi_test::{CreatePdaMode, ID}; +/// Functional: +/// +/// failing: +/// ReadOnlyProofOfInsertedAccount, InvalidReadOnlyAccountOutputQueue, InvalidReadOnlyAccount, v1 state mt +/// +/// TODO: failing: +/// - invalid Merkle tree +/// - invalid proof +/// - proof is none +/// - invalid root index +/// - invalid account which is not in value vec +#[serial] +#[tokio::test] +async fn test_read_only_accounts() { + let (_rpc, env) = + setup_test_programs_with_accounts(Some(vec![(String::from("system_cpi_test"), ID)])).await; + let payer = _rpc.get_payer().insecure_clone(); + + let mut e2e_env = init_program_test_env(_rpc, &env, true).await; + e2e_env.keypair_action_config.fee_assert = false; + // Create system state with accounts: + // - inserted a batched Merkle tree + // - inserted a batched output queue + // - inserted a batched output queue and batched Merkle tree + { + let params = InitStateTreeAccountsInstructionData::test_default(); + // fill two batches + for i in 0..params.output_queue_batch_size * 2 { + let seed = [i as u8; 32]; + let data = [i as u8; 31]; + perform_create_pda_with_event( + &mut e2e_env.indexer, + &mut e2e_env.rpc, + &env, + &payer, + seed, + &data, + &ID, + None, + None, + CreatePdaMode::BatchFunctional, + ) + .await + .unwrap(); + } + println!("inserted two batches"); + // insert one batch + for _ in 0..5 { + perform_batch_append( + &mut e2e_env.rpc, + &mut e2e_env.indexer.state_merkle_trees[1], + &env.forester, + 0, + false, + None, + ) + .await + .unwrap(); + } + for i in 0..params.output_queue_zkp_batch_size { + let seed = [i as u8 + 100; 32]; + let data = [i as u8 + 100; 31]; + perform_create_pda_with_event( + &mut e2e_env.indexer, + &mut e2e_env.rpc, + &env, + &payer, + seed, + &data, + &ID, + None, + None, + CreatePdaMode::BatchFunctional, + ) + .await + .unwrap(); + } + } + + { + let seed = [201u8; 32]; + let data = [201u8; 31]; + + perform_create_pda_with_event( + &mut e2e_env.indexer, + &mut e2e_env.rpc, + &env, + &payer, + seed, + &data, + &ID, + None, + None, + CreatePdaMode::BatchFunctional, + ) + .await + .unwrap(); + println!("created first account"); + // account in batched state mt and value vec + let account = e2e_env + .indexer + .get_compressed_accounts_by_owner(&ID) + .iter() + .find(|x| { + x.merkle_context.leaf_index == 101 + && x.merkle_context.merkle_tree_pubkey == env.batched_state_merkle_tree + }) + .unwrap() + .clone(); + + let seed = [202u8; 32]; + let data = [2u8; 31]; + + perform_create_pda_with_event( + &mut e2e_env.indexer, + &mut e2e_env.rpc, + &env, + &payer, + seed, + &data, + &ID, + None, + Some(vec![account.clone()]), + CreatePdaMode::BatchFunctional, + ) + .await + .unwrap(); + let seed = [203u8; 32]; + let data = [3u8; 31]; + + perform_create_pda_with_event( + &mut e2e_env.indexer, + &mut e2e_env.rpc, + &env, + &payer, + seed, + &data, + &ID, + None, + Some(vec![account.clone()]), + CreatePdaMode::Functional, + ) + .await + .unwrap(); + let seed = [204u8; 32]; + let data = [4u8; 31]; + // account in v1 state mt + let account_2 = e2e_env + .indexer + .get_compressed_accounts_by_owner(&ID) + .iter() + .find(|x| x.merkle_context.merkle_tree_pubkey == env.merkle_tree_pubkey) + .unwrap() + .clone(); + let result = perform_create_pda_with_event( + &mut e2e_env.indexer, + &mut e2e_env.rpc, + &env, + &payer, + seed, + &data, + &ID, + None, + Some(vec![account.clone(), account_2.clone()]), + CreatePdaMode::Functional, + ) + .await; + assert_rpc_error( + result, + 0, + AccountCompressionErrorCode::InvalidDiscriminator.into(), + ) + .unwrap(); + // Failing invalid read only account batch state mt + + let seed = [205u8; 32]; + let data = [5u8; 31]; + + let result = perform_create_pda_with_event( + &mut e2e_env.indexer, + &mut e2e_env.rpc, + &env, + &payer, + seed, + &data, + &ID, + None, + Some(vec![account.clone()]), + CreatePdaMode::InvalidReadOnlyAccount, + ) + .await; + assert_rpc_error( + result, + 0, + SystemProgramError::ReadOnlyAccountDoesNotExist.into(), + ) + .unwrap(); + + let result = perform_create_pda_with_event( + &mut e2e_env.indexer, + &mut e2e_env.rpc, + &env, + &payer, + seed, + &data, + &ID, + None, + Some(vec![account.clone()]), + CreatePdaMode::InvalidReadOnlyAccountOutputQueue, + ) + .await; + + assert_rpc_error( + result, + 0, + AccountCompressionErrorCode::InvalidDiscriminator.into(), + ) + .unwrap(); + + let result = perform_create_pda_with_event( + &mut e2e_env.indexer, + &mut e2e_env.rpc, + &env, + &payer, + seed, + &data, + &ID, + Some(vec![account.clone()]), + Some(vec![account.clone()]), + CreatePdaMode::ReadOnlyProofOfInsertedAccount, + ) + .await; + assert_rpc_error( + result, + 0, + SystemProgramError::ReadOnlyAccountDoesNotExist.into(), + ) + .unwrap(); + + let account_in_mt = e2e_env + .indexer + .get_compressed_accounts_by_owner(&ID) + .iter() + .find(|x| { + x.merkle_context.leaf_index == 0 + && x.merkle_context.merkle_tree_pubkey == env.batched_state_merkle_tree + }) + .unwrap() + .clone(); + let result = perform_create_pda_with_event( + &mut e2e_env.indexer, + &mut e2e_env.rpc, + &env, + &payer, + seed, + &data, + &ID, + None, + Some(vec![account_in_mt.clone()]), + CreatePdaMode::ProofIsNoneReadOnlyAccount, + ) + .await; + assert_rpc_error(result, 0, SystemProgramError::ProofIsNone.into()).unwrap(); + let result = perform_create_pda_with_event( + &mut e2e_env.indexer, + &mut e2e_env.rpc, + &env, + &payer, + seed, + &data, + &ID, + None, + Some(vec![account_in_mt.clone()]), + CreatePdaMode::InvalidProofReadOnlyAccount, + ) + .await; + assert_rpc_error(result, 0, VerifierError::ProofVerificationFailed.into()).unwrap(); + + let result = perform_create_pda_with_event( + &mut e2e_env.indexer, + &mut e2e_env.rpc, + &env, + &payer, + seed, + &data, + &ID, + None, + Some(vec![account_in_mt.clone()]), + CreatePdaMode::InvalidReadOnlyAccountRootIndex, + ) + .await; + assert_rpc_error(result, 0, VerifierError::ProofVerificationFailed.into()).unwrap(); + + let result = perform_create_pda_with_event( + &mut e2e_env.indexer, + &mut e2e_env.rpc, + &env, + &payer, + seed, + &data, + &ID, + Some(vec![account_in_mt.clone()]), + Some(vec![account_in_mt.clone()]), + CreatePdaMode::InvalidReadOnlyAccount, + ) + .await; + assert_rpc_error(result, 0, VerifierError::ProofVerificationFailed.into()).unwrap(); + + let result = perform_create_pda_with_event( + &mut e2e_env.indexer, + &mut e2e_env.rpc, + &env, + &payer, + seed, + &data, + &ID, + None, + Some(vec![account_in_mt.clone()]), + CreatePdaMode::InvalidReadOnlyAccountMerkleTree, + ) + .await; + assert_rpc_error( + result, + 0, + AccountCompressionErrorCode::StateMerkleTreeAccountDiscriminatorMismatch.into(), + ) + .unwrap(); + + perform_create_pda_with_event( + &mut e2e_env.indexer, + &mut e2e_env.rpc, + &env, + &payer, + seed, + &data, + &ID, + None, + Some(vec![account_in_mt.clone()]), + CreatePdaMode::Functional, + ) + .await + .unwrap(); + } +} /// Test: /// Functional: /// 1. Create pda @@ -75,6 +426,8 @@ async fn only_test_create_pda() { seed, &data, &ID, + None, + None, CreatePdaMode::InvalidReadOnlyAddress, ) .await; @@ -93,6 +446,8 @@ async fn only_test_create_pda() { seed, &data, &ID, + None, + None, CreatePdaMode::InvalidReadOnlyMerkleTree, ) .await; @@ -111,6 +466,8 @@ async fn only_test_create_pda() { seed, &data, &ID, + None, + None, CreatePdaMode::InvalidReadOnlyRootIndex, ) .await; @@ -129,6 +486,8 @@ async fn only_test_create_pda() { seed, &data, &ID, + None, + None, CreatePdaMode::UseReadOnlyAddressInAccount, ) .await; @@ -143,6 +502,8 @@ async fn only_test_create_pda() { seed, &data, &ID, + None, + None, CreatePdaMode::ReadOnlyProofOfInsertedAddress, ) .await; @@ -162,6 +523,8 @@ async fn only_test_create_pda() { seed, &data, &ID, + None, + None, CreatePdaMode::OneReadOnlyAddress, ) .await @@ -177,6 +540,8 @@ async fn only_test_create_pda() { seed, &data, &ID, + None, + None, CreatePdaMode::TwoReadOnlyAddresses, ) .await @@ -195,6 +560,8 @@ async fn only_test_create_pda() { seed, &data, &ID, + None, + None, CreatePdaMode::BatchAddressFunctional, ) .await @@ -209,6 +576,8 @@ async fn only_test_create_pda() { seed, &data, &ID, + None, + None, CreatePdaMode::BatchAddressFunctional, ) .await; @@ -224,6 +593,8 @@ async fn only_test_create_pda() { seed, &data, &ID, + None, + None, CreatePdaMode::InvalidBatchTreeAccount, ) .await; @@ -246,6 +617,8 @@ async fn only_test_create_pda() { seed, &data, &ID, + None, + None, CreatePdaMode::ProgramIsSigner, ) .await @@ -505,6 +878,8 @@ async fn test_approve_revoke_burn_freeze_thaw_with_cpi_context() { seed, &data, &ID, + None, + None, CreatePdaMode::ProgramIsSigner, ) .await @@ -806,6 +1181,8 @@ async fn test_create_pda_in_program_owned_merkle_trees() { seed, &data, &ID, + None, + None, CreatePdaMode::ProgramIsSigner, ) .await @@ -842,6 +1219,8 @@ pub async fn perform_create_pda_failing( data, payer_pubkey, owner_program, + None, + None, signer_is_program, ) .await; @@ -864,6 +1243,8 @@ pub async fn perform_create_pda_with_event( seed: [u8; 32], data: &[u8; 31], owner_program: &Pubkey, + input_accounts: Option>, + read_only_accounts: Option>, mode: CreatePdaMode, ) -> Result<(), RpcError> { let payer_pubkey = payer.pubkey(); @@ -875,6 +1256,8 @@ pub async fn perform_create_pda_with_event( data, payer_pubkey, owner_program, + input_accounts, + read_only_accounts, mode, ) .await; @@ -897,8 +1280,15 @@ async fn perform_create_pda( data: &[u8; 31], payer_pubkey: Pubkey, owner_program: &Pubkey, + input_accounts: Option>, + read_only_accounts: Option>, mode: CreatePdaMode, ) -> solana_sdk::instruction::Instruction { + let output_compressed_account_merkle_tree_pubkey = if mode == CreatePdaMode::BatchFunctional { + &env.batched_output_queue + } else { + &env.merkle_tree_pubkey + }; let (address, mut address_merkle_tree_pubkey, address_queue_pubkey) = if mode == CreatePdaMode::BatchAddressFunctional || mode == CreatePdaMode::InvalidReadOnlyAddress @@ -961,12 +1351,32 @@ async fn perform_create_pda( addresses.insert(0, read_only_address); address_merkle_tree_pubkeys.push(address_merkle_tree_pubkey); } - - println!("addresses: {:?}", addresses); + let mut compressed_account_hashes = Vec::new(); + let mut compressed_account_merkle_tree_pubkeys = Vec::new(); + if let Some(input_accounts) = input_accounts.as_ref() { + input_accounts.iter().for_each(|x| { + compressed_account_hashes.push(x.hash().unwrap()); + compressed_account_merkle_tree_pubkeys.push(x.merkle_context.merkle_tree_pubkey); + }); + } + if let Some(read_only_accounts) = read_only_accounts.as_ref() { + read_only_accounts.iter().for_each(|x| { + compressed_account_hashes.push(x.hash().unwrap()); + compressed_account_merkle_tree_pubkeys.push(x.merkle_context.merkle_tree_pubkey); + }); + } let rpc_result = test_indexer - .create_proof_for_compressed_accounts( - None, - None, + .create_proof_for_compressed_accounts2( + if compressed_account_hashes.is_empty() { + None + } else { + Some(compressed_account_hashes) + }, + if compressed_account_merkle_tree_pubkeys.is_empty() { + None + } else { + Some(compressed_account_merkle_tree_pubkeys) + }, Some(&addresses), Some(address_merkle_tree_pubkeys), rpc, @@ -983,7 +1393,7 @@ async fn perform_create_pda( address_merkle_tree_root_index: rpc_result.address_root_indices[0], }; let readonly_adresses = if addresses.len() == 2 && mode != CreatePdaMode::TwoReadOnlyAddresses { - let read_only_address = vec![ReadOnlyAddressParams { + let read_only_address = vec![ReadOnlyAddress { address: addresses[1], address_merkle_tree_pubkey, address_merkle_tree_root_index: rpc_result.address_root_indices[1], @@ -991,12 +1401,12 @@ async fn perform_create_pda( Some(read_only_address) } else if mode == CreatePdaMode::TwoReadOnlyAddresses { let read_only_address = vec![ - ReadOnlyAddressParams { + ReadOnlyAddress { address: addresses[0], address_merkle_tree_pubkey, address_merkle_tree_root_index: rpc_result.address_root_indices[0], }, - ReadOnlyAddressParams { + ReadOnlyAddress { address: addresses[1], address_merkle_tree_pubkey, address_merkle_tree_root_index: rpc_result.address_root_indices[1], @@ -1006,18 +1416,44 @@ async fn perform_create_pda( } else { None }; + let mut index = 0; + let state_roots = if input_accounts.as_ref().is_none() { + None + } else { + let input_account_len = input_accounts.as_ref().unwrap().len(); + index += input_account_len; + Some(rpc_result.root_indices[..index].to_vec()) + }; + + let read_only_accounts = if let Some(read_only_accounts) = read_only_accounts.as_ref() { + Some( + read_only_accounts + .iter() + .map(|x| { + index += 1; + x.into_read_only(rpc_result.root_indices[index - 1]) + .unwrap() + }) + .collect::>(), + ) + } else { + None + }; let create_ix_inputs = CreateCompressedPdaInstructionInputs { data: *data, signer: &payer_pubkey, - output_compressed_account_merkle_tree_pubkey: &env.merkle_tree_pubkey, - proof: &rpc_result.proof, + output_compressed_account_merkle_tree_pubkey, + proof: &rpc_result.proof.unwrap(), new_address_params, cpi_context_account: &env.cpi_context_account_pubkey, owner_program, signer_is_program: mode.clone(), registered_program_pda: &env.registered_program_pda, readonly_adresses, + read_only_accounts, + input_compressed_accounts_with_merkle_context: input_accounts, + state_roots, }; create_pda_instruction(create_ix_inputs) } diff --git a/test-utils/src/e2e_test_env.rs b/test-utils/src/e2e_test_env.rs index ef9b1aae0..0cdaf97cc 100644 --- a/test-utils/src/e2e_test_env.rs +++ b/test-utils/src/e2e_test_env.rs @@ -207,19 +207,25 @@ impl Stats { pub async fn init_program_test_env( rpc: ProgramTestRpcConnection, env_accounts: &EnvAccounts, + skip_prover: bool, ) -> E2ETestEnv> { let indexer: TestIndexer = TestIndexer::init_from_env( &env_accounts.forester.insecure_clone(), env_accounts, - Some(ProverConfig { - run_mode: None, - circuits: vec![ - ProofType::BatchAppendWithProofsTest, - ProofType::BatchUpdateTest, - ProofType::Inclusion, - ProofType::NonInclusion, - ], - }), + if skip_prover { + None + } else { + Some(ProverConfig { + run_mode: None, + circuits: vec![ + ProofType::BatchAppendWithProofsTest, + ProofType::BatchUpdateTest, + ProofType::Inclusion, + ProofType::NonInclusion, + ProofType::Combined, + ], + }) + }, ) .await; @@ -1406,7 +1412,22 @@ where amount: u64, tree_index: Option, ) { - let input_compressed_accounts = self.get_compressed_sol_accounts(&from.pubkey()); + self.compress_sol_deterministic_opt_inputs(from, amount, tree_index, true) + .await; + } + + pub async fn compress_sol_deterministic_opt_inputs( + &mut self, + from: &Keypair, + amount: u64, + tree_index: Option, + inputs: bool, + ) { + let input_compressed_accounts = if inputs { + self.get_compressed_sol_accounts(&from.pubkey()) + } else { + vec![] + }; let bundle = self.indexer.get_state_merkle_trees()[tree_index.unwrap_or(0)].clone(); let rollover_fee = bundle.rollover_fee; let output_merkle_tree = match bundle.version { @@ -1434,7 +1455,7 @@ where &mut self.rpc, &mut self.indexer, from, - input_compressed_accounts.as_slice(), + &input_compressed_accounts[..std::cmp::min(input_compressed_accounts.len(), 4)], false, amount, &output_merkle_tree,