Skip to content

Commit 4161d3d

Browse files
add support for multiple address_trees per address space
1 parent cc17cef commit 4161d3d

File tree

11 files changed

+312
-57
lines changed

11 files changed

+312
-57
lines changed

program-tests/anchor-compressible-user/src/lib.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ pub mod anchor_compressible_user {
2222
use light_sdk::account::LightAccount;
2323
use light_sdk::compressible::{
2424
compress_pda, compress_pda_new, create_compression_config_checked,
25-
decompress_multiple_idempotent, update_config,
25+
decompress_multiple_idempotent, update_compression_config,
2626
};
2727

2828
use super::*;
@@ -59,7 +59,7 @@ pub mod anchor_compressible_user {
5959
new_address_space: Option<Pubkey>,
6060
new_update_authority: Option<Pubkey>,
6161
) -> Result<()> {
62-
update_config(
62+
update_compression_config(
6363
&ctx.accounts.config.to_account_info(),
6464
&ctx.accounts.authority.to_account_info(),
6565
new_update_authority.as_ref(),
@@ -118,6 +118,7 @@ pub mod anchor_compressible_user {
118118
&crate::ID,
119119
&ctx.accounts.rent_recipient,
120120
&config.address_space,
121+
None,
121122
)
122123
.map_err(|e| anchor_lang::prelude::ProgramError::from(e))?;
123124

@@ -159,7 +160,8 @@ pub mod anchor_compressible_user {
159160
cpi_accounts,
160161
&crate::ID,
161162
&ctx.accounts.rent_recipient,
162-
&ADDRESS_SPACE,
163+
&vec![ADDRESS_SPACE],
164+
None,
163165
)
164166
.map_err(|e| anchor_lang::prelude::ProgramError::from(e))?;
165167

program-tests/sdk-test/src/create_config.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,25 @@ pub fn process_create_compression_config_checked(
1919
let system_program = &accounts[3];
2020
let program_data_account = &accounts[4];
2121

22-
// Use the SDK's safe create_config function which validates upgrade authority
2322
create_compression_config_checked(
2423
config_account,
2524
update_authority,
2625
program_data_account,
2726
&instruction_data.rent_recipient,
28-
&instruction_data.address_space,
27+
instruction_data.address_space,
2928
instruction_data.compression_delay,
3029
payer,
3130
system_program,
3231
&crate::ID,
33-
)
32+
)?;
33+
34+
Ok(())
3435
}
3536

3637
#[derive(Clone, Debug, BorshDeserialize, BorshSerialize)]
3738
pub struct CreateConfigInstructionData {
3839
pub rent_recipient: Pubkey,
39-
pub address_space: Pubkey,
40+
/// Address spaces (1-4 allowed, first is primary for writing)
41+
pub address_space: Vec<Pubkey>,
4042
pub compression_delay: u32,
4143
}

program-tests/sdk-test/src/create_dynamic_pda.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use borsh::{BorshDeserialize, BorshSerialize};
22
use light_sdk::{
3+
address::ReadOnlyAddress,
34
compressible::{compress_pda_new, CompressibleConfig, CompressionInfo},
45
cpi::CpiAccounts,
56
error::LightSdkError,
@@ -59,6 +60,7 @@ pub fn create_dynamic_pda(
5960
&crate::ID,
6061
rent_recipient,
6162
&config.address_space,
63+
instruction_data.read_only_addresses,
6264
)?;
6365

6466
Ok(())
@@ -69,5 +71,7 @@ pub struct CreateDynamicPdaInstructionData {
6971
pub proof: ValidityProof,
7072
pub compressed_address: [u8; 32],
7173
pub address_tree_info: PackedAddressTreeInfo,
74+
/// Optional read-only addresses for exclusion proofs (same address, different trees)
75+
pub read_only_addresses: Option<Vec<ReadOnlyAddress>>,
7276
pub output_state_tree_index: u8,
7377
}

program-tests/sdk-test/src/update_config.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use borsh::{BorshDeserialize, BorshSerialize};
22
use light_sdk::{
3-
compressible::{update_config, CompressibleConfig},
3+
compressible::{update_compression_config, CompressibleConfig},
44
error::LightSdkError,
55
};
66
use solana_program::account_info::AccountInfo;
@@ -25,13 +25,12 @@ pub fn process_update_config(
2525
return Err(LightSdkError::ConstraintViolation);
2626
}
2727

28-
// Update the config
29-
update_config(
28+
update_compression_config(
3029
config_account,
3130
authority,
3231
instruction_data.new_update_authority.as_ref(),
3332
instruction_data.new_rent_recipient.as_ref(),
34-
instruction_data.new_address_space.as_ref(),
33+
instruction_data.new_address_space,
3534
instruction_data.new_compression_delay,
3635
&crate::ID,
3736
)?;
@@ -43,6 +42,6 @@ pub fn process_update_config(
4342
pub struct UpdateConfigInstructionData {
4443
pub new_update_authority: Option<Pubkey>,
4544
pub new_rent_recipient: Option<Pubkey>,
46-
pub new_address_space: Option<Pubkey>,
45+
pub new_address_space: Option<Vec<Pubkey>>,
4746
pub new_compression_delay: Option<u32>,
4847
}

program-tests/sdk-test/tests/test_config.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ async fn test_create_and_update_config() {
3434
// Test create config
3535
let create_ix_data = CreateConfigInstructionData {
3636
rent_recipient: RENT_RECIPIENT,
37-
address_space: ADDRESS_SPACE,
37+
address_space: vec![ADDRESS_SPACE], // Can add more for multi-address-space support
3838
compression_delay: 100,
3939
};
4040

@@ -78,7 +78,7 @@ async fn test_config_validation() {
7878
// Try to create config with non-authority (should fail)
7979
let create_ix_data = CreateConfigInstructionData {
8080
rent_recipient: RENT_RECIPIENT,
81-
address_space: ADDRESS_SPACE,
81+
address_space: vec![ADDRESS_SPACE],
8282
compression_delay: 100,
8383
};
8484

@@ -121,7 +121,7 @@ async fn test_config_creation_requires_signer() {
121121
// Try to create config with non-signer as update authority (should fail)
122122
let create_ix_data = CreateConfigInstructionData {
123123
rent_recipient: RENT_RECIPIENT,
124-
address_space: ADDRESS_SPACE,
124+
address_space: vec![ADDRESS_SPACE],
125125
compression_delay: 100,
126126
};
127127

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
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+
}

sdk-libs/macros/src/compressible.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ pub(crate) fn add_compressible_instructions(
214214
new_address_space: Option<Pubkey>,
215215
new_update_authority: Option<Pubkey>,
216216
) -> Result<()> {
217-
light_sdk::compressible::update_config(
217+
light_sdk::compressible::update_compression_config(
218218
&ctx.accounts.config.to_account_info(),
219219
&ctx.accounts.authority.to_account_info(),
220220
new_update_authority.as_ref(),

sdk-libs/sdk/src/compressible/ANCHOR_CONFIG_EXAMPLE.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// This file shows how to implement secure config creation following Solana best practices
33

44
use anchor_lang::prelude::*;
5-
use light_sdk::compressible::{create_config, update_config, CompressibleConfig};
5+
use light_sdk::compressible::{create_config, update_compression_config, CompressibleConfig};
66

77
#[program]
88
pub mod example_program {
@@ -32,7 +32,7 @@ pub mod example_program {
3232
// If you want the config update authority to be different from the program upgrade authority,
3333
// you can update it here
3434
if config_update_authority != ctx.accounts.authority.key() {
35-
update_config(
35+
update_compression_config(
3636
&ctx.accounts.config.to_account_info(),
3737
&ctx.accounts.authority.to_account_info(),
3838
Some(&config_update_authority),
@@ -53,7 +53,7 @@ pub mod example_program {
5353
new_address_space: Option<Pubkey>,
5454
new_update_authority: Option<Pubkey>,
5555
) -> Result<()> {
56-
update_config(
56+
update_compression_config(
5757
&ctx.accounts.config.to_account_info(),
5858
&ctx.accounts.authority.to_account_info(),
5959
new_update_authority.as_ref(),
@@ -109,7 +109,7 @@ pub struct UpdateConfig<'info> {
109109
seeds = [b"compressible_config"],
110110
bump,
111111
// This constraint could also load and check the config's update_authority
112-
// but the SDK's update_config function will do that check
112+
// but the SDK's update_compression_config function will do that check
113113
)]
114114
pub config: AccountInfo<'info>,
115115

0 commit comments

Comments
 (0)