Skip to content

Commit eaa5068

Browse files
committed
test: check for duplicate input and read only accounts
1 parent 2ce9c3d commit eaa5068

3 files changed

Lines changed: 108 additions & 1 deletion

File tree

anchor-programs/system/src/errors.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,6 @@ pub enum SystemProgramError {
8888
TooManyOutputAccounts,
8989
#[msg("Failed to borrow account data")]
9090
BorrowingDataFailed,
91+
#[msg("DuplicateAccountInInputsAndReadOnly")]
92+
DuplicateAccountInInputsAndReadOnly,
9193
}

program-tests/system-cpi-v2-test/tests/invoke_cpi_with_read_only.rs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2826,6 +2826,109 @@ fn get_output_account_info(output_merkle_tree_index: u8) -> OutAccountInfo {
28262826
}
28272827
}
28282828

2829+
#[serial]
2830+
#[tokio::test]
2831+
async fn test_duplicate_account_in_inputs_and_read_only() {
2832+
spawn_prover(ProverConfig::default()).await;
2833+
2834+
let mut config = ProgramTestConfig::default_with_batched_trees(false);
2835+
config.with_prover = false;
2836+
config.additional_programs = Some(vec![(
2837+
"create_address_test_program",
2838+
create_address_test_program::ID,
2839+
)]);
2840+
config.v2_state_tree_config = Some(InitStateTreeAccountsInstructionData::default());
2841+
2842+
let mut rpc = LightProgramTest::new(config).await.unwrap();
2843+
let env = rpc.test_accounts.clone();
2844+
let queue = env.v2_state_trees[0].output_queue;
2845+
let tree = env.v2_state_trees[0].merkle_tree;
2846+
2847+
let payer = rpc.get_payer().insecure_clone();
2848+
let mut test_indexer = TestIndexer::init_from_acounts(&payer, &env, 0).await;
2849+
2850+
// Create a compressed account first
2851+
let output_account = get_compressed_output_account(true, queue);
2852+
local_sdk::perform_test_transaction(
2853+
&mut rpc,
2854+
&mut test_indexer,
2855+
&payer,
2856+
vec![],
2857+
vec![output_account.clone()],
2858+
vec![],
2859+
None, // proof
2860+
None,
2861+
None,
2862+
false,
2863+
false,
2864+
Vec::new(),
2865+
Vec::new(),
2866+
queue,
2867+
tree,
2868+
false,
2869+
None,
2870+
false,
2871+
None,
2872+
None,
2873+
)
2874+
.await
2875+
.unwrap()
2876+
.unwrap();
2877+
2878+
// Now try to use the same account as both input and read-only
2879+
let compressed_account = test_indexer.compressed_accounts[0].clone();
2880+
2881+
let read_only_account = ReadOnlyCompressedAccount {
2882+
account_hash: compressed_account.hash().unwrap(),
2883+
merkle_context: MerkleContext {
2884+
merkle_tree_pubkey: tree.into(),
2885+
queue_pubkey: queue.into(),
2886+
leaf_index: 0,
2887+
prove_by_index: false,
2888+
tree_type: TreeType::StateV2,
2889+
},
2890+
root_index: 0,
2891+
};
2892+
2893+
// Get validity proof for the input account
2894+
let rpc_result = rpc
2895+
.get_validity_proof(vec![compressed_account.hash().unwrap()], Vec::new(), None)
2896+
.await
2897+
.unwrap();
2898+
2899+
// Attempt transaction with duplicate account - should fail
2900+
let result = local_sdk::perform_test_transaction(
2901+
&mut rpc,
2902+
&mut test_indexer,
2903+
&payer,
2904+
vec![compressed_account], // input_accounts
2905+
vec![], // output_accounts
2906+
vec![], // new_addresses
2907+
rpc_result.value.proof.0, // proof
2908+
None, // sol_compression_recipient
2909+
None, // account_infos
2910+
false, // small_ix
2911+
false, // with_transaction_hash
2912+
vec![read_only_account], // read_only_accounts
2913+
Vec::new(), // read_only_addresses
2914+
queue,
2915+
tree,
2916+
false, // is_compress
2917+
None, // compress_or_decompress_lamports
2918+
false, // invalid_sol_pool
2919+
None, // invalid_fee_recipient
2920+
None, // invalid_cpi_context
2921+
)
2922+
.await;
2923+
2924+
assert_rpc_error(
2925+
result,
2926+
0,
2927+
SystemProgramError::DuplicateAccountInInputsAndReadOnly.into(),
2928+
)
2929+
.unwrap();
2930+
}
2931+
28292932
pub mod local_sdk {
28302933
use std::collections::HashMap;
28312934

program-tests/utils/src/e2e_test_env.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2491,7 +2491,9 @@ where
24912491
for _ in 0..select_num_accounts {
24922492
let index = Self::safe_gen_range(&mut self.rng, 0..num_accounts, 0);
24932493
let account = program_accounts[index].clone();
2494-
accounts.push(account);
2494+
if !input_accounts.contains(&account) {
2495+
accounts.push(account);
2496+
}
24952497
}
24962498
accounts
24972499
.iter()

0 commit comments

Comments
 (0)