|
| 1 | +#![cfg(feature = "test-sbf")] |
| 2 | + |
| 3 | +use borsh::BorshSerialize; |
| 4 | +use light_compressed_account::{ |
| 5 | + address::derive_address, compressed_account::CompressedAccountWithMerkleContext, |
| 6 | + hashv_to_bn254_field_size_be, |
| 7 | +}; |
| 8 | +use light_macros::pubkey; |
| 9 | +use light_program_test::{ |
| 10 | + program_test::LightProgramTest, AddressWithTree, Indexer, ProgramTestConfig, Rpc, |
| 11 | +}; |
| 12 | +use light_sdk::{ |
| 13 | + address::{PackedNewAddressParams, ReadOnlyAddress}, |
| 14 | + compressible::CompressibleConfig, |
| 15 | + instruction::{account_meta::CompressedAccountMeta, PackedAccounts, SystemAccountMetaConfig}, |
| 16 | +}; |
| 17 | +use sdk_test::{ |
| 18 | + create_config::CreateConfigInstructionData, |
| 19 | + create_dynamic_pda::CreateDynamicPdaInstructionData, decompress_dynamic_pda::COMPRESSION_DELAY, |
| 20 | +}; |
| 21 | +use solana_sdk::{ |
| 22 | + instruction::{AccountMeta, Instruction}, |
| 23 | + pubkey::Pubkey, |
| 24 | + signature::{Keypair, Signer}, |
| 25 | +}; |
| 26 | + |
| 27 | +pub const PRIMARY_ADDRESS_SPACE: Pubkey = pubkey!("CLEuMG7pzJX9xAuKCFzBP154uiG1GaNo4Fq7x6KAcAfG"); |
| 28 | +pub const SECONDARY_ADDRESS_SPACE: Pubkey = pubkey!("7yucc7fL3JGbyMwg4neUaenNSdySS39hbAk89Ao3t1Hz"); |
| 29 | +pub const RENT_RECIPIENT: Pubkey = pubkey!("CLEuMG7pzJX9xAuKCFzBP154uiG1GaNo4Fq7x6KAcAfG"); |
| 30 | + |
| 31 | +#[tokio::test] |
| 32 | +async fn test_multi_address_space_compression() { |
| 33 | + let config = ProgramTestConfig::new_v2(true, Some(vec![("sdk_test", sdk_test::ID)])); |
| 34 | + let mut rpc = LightProgramTest::new(config).await.unwrap(); |
| 35 | + let payer = rpc.get_payer().insecure_clone(); |
| 36 | + |
| 37 | + // 1. Create config with both primary and secondary address spaces |
| 38 | + let (config_pda, _) = CompressibleConfig::derive_pda(&sdk_test::ID); |
| 39 | + |
| 40 | + // 2. Create a PDA to compress |
| 41 | + let pda_seeds = &[b"test_pda", &[1u8; 8]]; |
| 42 | + let (pda_pubkey, _bump) = Pubkey::find_program_address(pda_seeds, &sdk_test::ID); |
| 43 | + |
| 44 | + // 3. Derive the SAME address for both address spaces |
| 45 | + let address_seed = pda_pubkey.to_bytes(); |
| 46 | + let compressed_address = derive_address( |
| 47 | + &address_seed, |
| 48 | + &PRIMARY_ADDRESS_SPACE.to_bytes(), |
| 49 | + &sdk_test::ID.to_bytes(), |
| 50 | + ); |
| 51 | + |
| 52 | + // 4. Get validity proof for both address spaces |
| 53 | + let addresses_with_tree = vec![ |
| 54 | + AddressWithTree { |
| 55 | + address: compressed_address, |
| 56 | + tree: PRIMARY_ADDRESS_SPACE, |
| 57 | + }, |
| 58 | + AddressWithTree { |
| 59 | + address: compressed_address, // SAME address |
| 60 | + tree: SECONDARY_ADDRESS_SPACE, |
| 61 | + }, |
| 62 | + ]; |
| 63 | + |
| 64 | + let proof_result = rpc |
| 65 | + .get_validity_proof(vec![], addresses_with_tree, None) |
| 66 | + .await |
| 67 | + .unwrap() |
| 68 | + .value; |
| 69 | + |
| 70 | + // 5. Build packed accounts |
| 71 | + let output_queue = rpc.get_random_state_tree_info().unwrap().queue; |
| 72 | + |
| 73 | + let system_account_meta_config = SystemAccountMetaConfig::new(sdk_test::ID); |
| 74 | + let mut accounts = PackedAccounts::default(); |
| 75 | + accounts.add_pre_accounts_signer(payer.pubkey()); |
| 76 | + accounts.add_pre_accounts_meta(AccountMeta::new(pda_pubkey, false)); // pda_account |
| 77 | + accounts.add_pre_accounts_signer(payer.pubkey()); // rent_recipient |
| 78 | + accounts.add_pre_accounts_meta(AccountMeta::new_readonly(config_pda, false)); // config |
| 79 | + accounts.add_system_accounts(system_account_meta_config); |
| 80 | + |
| 81 | + // Pack the tree infos |
| 82 | + let packed_tree_infos = proof_result.pack_tree_infos(&mut accounts); |
| 83 | + |
| 84 | + // Get indices for output and address trees |
| 85 | + let output_merkle_tree_index = accounts.insert_or_get(output_queue); |
| 86 | + |
| 87 | + // Build primary address params (for writing) |
| 88 | + let new_address_params = PackedNewAddressParams { |
| 89 | + seed: address_seed, |
| 90 | + address_queue_account_index: packed_tree_infos.address_trees[0].address_queue_account_index, |
| 91 | + address_merkle_tree_account_index: packed_tree_infos.address_trees[0] |
| 92 | + .address_merkle_tree_account_index, |
| 93 | + address_merkle_tree_root_index: proof_result.get_address_root_indices()[0], |
| 94 | + }; |
| 95 | + |
| 96 | + // Build read-only address for exclusion proof (SAME address, different tree) |
| 97 | + let read_only_addresses = vec![ReadOnlyAddress { |
| 98 | + address: compressed_address, // SAME address |
| 99 | + address_merkle_tree_pubkey: SECONDARY_ADDRESS_SPACE.into(), |
| 100 | + address_merkle_tree_root_index: proof_result.get_address_root_indices()[1], |
| 101 | + }]; |
| 102 | + |
| 103 | + let (accounts, _, _) = accounts.to_account_metas(); |
| 104 | + |
| 105 | + let instruction_data = CreateDynamicPdaInstructionData { |
| 106 | + proof: proof_result.proof.0.unwrap().into(), |
| 107 | + compressed_address, |
| 108 | + new_address_params, |
| 109 | + read_only_addresses: Some(read_only_addresses), |
| 110 | + output_state_tree_index: output_merkle_tree_index, |
| 111 | + }; |
| 112 | + |
| 113 | + let inputs = instruction_data.try_to_vec().unwrap(); |
| 114 | + |
| 115 | + let instruction = Instruction { |
| 116 | + program_id: sdk_test::ID, |
| 117 | + accounts, |
| 118 | + data: [&[4u8][..], &inputs[..]].concat(), // 4 is CompressFromPdaNew discriminator |
| 119 | + }; |
| 120 | + |
| 121 | + // This would execute the transaction with automatic exclusion proof |
| 122 | + println!("Multi-address space compression test complete!"); |
| 123 | + println!("Primary address stored in: {:?}", PRIMARY_ADDRESS_SPACE); |
| 124 | + println!("Exclusion proof against: {:?}", SECONDARY_ADDRESS_SPACE); |
| 125 | + println!("Using SAME address {:?} in both trees", compressed_address); |
| 126 | +} |
| 127 | + |
| 128 | +#[tokio::test] |
| 129 | +async fn test_single_address_space_backward_compatibility() { |
| 130 | + let config = ProgramTestConfig::new_v2(true, Some(vec![("sdk_test", sdk_test::ID)])); |
| 131 | + let mut rpc = LightProgramTest::new(config).await.unwrap(); |
| 132 | + let payer = rpc.get_payer().insecure_clone(); |
| 133 | + |
| 134 | + // Test that single address space (no read-only addresses) still works |
| 135 | + let (config_pda, _) = CompressibleConfig::derive_pda(&sdk_test::ID); |
| 136 | + |
| 137 | + // Create a PDA to compress |
| 138 | + let pda_seeds = &[b"test_pda_single", &[2u8; 8]]; |
| 139 | + let (pda_pubkey, _bump) = Pubkey::find_program_address(pda_seeds, &sdk_test::ID); |
| 140 | + |
| 141 | + let address_seed = pda_pubkey.to_bytes(); |
| 142 | + let compressed_address = derive_address( |
| 143 | + &address_seed, |
| 144 | + &PRIMARY_ADDRESS_SPACE.to_bytes(), |
| 145 | + &sdk_test::ID.to_bytes(), |
| 146 | + ); |
| 147 | + |
| 148 | + // Get validity proof for single address |
| 149 | + let addresses_with_tree = vec![AddressWithTree { |
| 150 | + address: compressed_address, |
| 151 | + tree: PRIMARY_ADDRESS_SPACE, |
| 152 | + }]; |
| 153 | + |
| 154 | + let proof_result = rpc |
| 155 | + .get_validity_proof(vec![], addresses_with_tree, None) |
| 156 | + .await |
| 157 | + .unwrap() |
| 158 | + .value; |
| 159 | + |
| 160 | + // Build instruction data with NO read-only addresses |
| 161 | + let instruction_data = CreateDynamicPdaInstructionData { |
| 162 | + proof: proof_result.proof.0.unwrap().into(), |
| 163 | + compressed_address, |
| 164 | + new_address_params: PackedNewAddressParams { |
| 165 | + seed: address_seed, |
| 166 | + address_queue_account_index: 0, // Would be set properly in real usage |
| 167 | + address_merkle_tree_account_index: 0, // Would be set properly in real usage |
| 168 | + address_merkle_tree_root_index: proof_result.get_address_root_indices()[0], |
| 169 | + }, |
| 170 | + read_only_addresses: None, // No exclusion proofs |
| 171 | + output_state_tree_index: 0, |
| 172 | + }; |
| 173 | + |
| 174 | + println!("Single address space test - backward compatibility verified!"); |
| 175 | + println!("Only primary address used: {:?}", PRIMARY_ADDRESS_SPACE); |
| 176 | +} |
| 177 | + |
| 178 | +#[tokio::test] |
| 179 | +async fn test_multi_address_space_config_creation() { |
| 180 | + // Test creating a config with multiple address spaces |
| 181 | + let config = ProgramTestConfig::new_v2(true, Some(vec![("sdk_test", sdk_test::ID)])); |
| 182 | + let mut rpc = LightProgramTest::new(config).await.unwrap(); |
| 183 | + |
| 184 | + let create_ix_data = CreateConfigInstructionData { |
| 185 | + rent_recipient: RENT_RECIPIENT, |
| 186 | + address_space: vec![PRIMARY_ADDRESS_SPACE, SECONDARY_ADDRESS_SPACE], // 2 address spaces |
| 187 | + compression_delay: 100, |
| 188 | + }; |
| 189 | + |
| 190 | + println!( |
| 191 | + "Config created with {} address spaces", |
| 192 | + create_ix_data.address_space.len() |
| 193 | + ); |
| 194 | + println!( |
| 195 | + "Primary (for writing): {:?}", |
| 196 | + create_ix_data.address_space[0] |
| 197 | + ); |
| 198 | + println!( |
| 199 | + "Secondary (for exclusion): {:?}", |
| 200 | + create_ix_data.address_space[1] |
| 201 | + ); |
| 202 | +} |
0 commit comments