From 8c37f598aa29d18384eef26f7e7f9ba81acf2472 Mon Sep 17 00:00:00 2001 From: ananas Date: Mon, 20 Oct 2025 18:00:11 +0100 Subject: [PATCH 1/3] fix: test indexer return full Merkle proof, feat: light-sdk add merkle tree feature --- Cargo.lock | 2 + .../program-test/src/indexer/test_indexer.rs | 2 +- .../src/utils/setup_light_programs.rs | 2 +- sdk-libs/sdk-types/src/constants.rs | 2 + sdk-libs/sdk/Cargo.toml | 3 ++ sdk-libs/sdk/src/lib.rs | 3 ++ sdk-libs/sdk/src/merkle_tree.rs | 49 +++++++++++++++++++ 7 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 sdk-libs/sdk/src/merkle_tree.rs diff --git a/Cargo.lock b/Cargo.lock index 5030a66698..1d449595ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3927,10 +3927,12 @@ dependencies = [ name = "light-sdk" version = "0.15.0" dependencies = [ + "account-compression", "anchor-lang", "borsh 0.10.4", "light-account-checks", "light-compressed-account", + "light-concurrent-merkle-tree", "light-hasher", "light-macros", "light-sdk-macros", diff --git a/sdk-libs/program-test/src/indexer/test_indexer.rs b/sdk-libs/program-test/src/indexer/test_indexer.rs index ee098435f5..351b6265e8 100644 --- a/sdk-libs/program-test/src/indexer/test_indexer.rs +++ b/sdk-libs/program-test/src/indexer/test_indexer.rs @@ -145,7 +145,7 @@ impl Indexer for TestIndexer { if let Some(leaf_index) = tree.merkle_tree.get_leaf_index(hash) { let proof = tree .merkle_tree - .get_proof_of_leaf(leaf_index, false) + .get_proof_of_leaf(leaf_index, true) .unwrap(); proofs.push(MerkleProof { hash: *hash, diff --git a/sdk-libs/program-test/src/utils/setup_light_programs.rs b/sdk-libs/program-test/src/utils/setup_light_programs.rs index e88d22aa4a..eaddcc43d0 100644 --- a/sdk-libs/program-test/src/utils/setup_light_programs.rs +++ b/sdk-libs/program-test/src/utils/setup_light_programs.rs @@ -69,7 +69,7 @@ pub fn setup_light_programs( .inspect_err(|_| { println!("Program light_compressed_token bin not found in {}", path); })?; - let path = format!("{}spl_noop.so", light_bin_path); + let path = format!("{}/spl_noop.so", light_bin_path); program_test .add_program_from_file(NOOP_PROGRAM_ID, path.clone()) .inspect_err(|_| { diff --git a/sdk-libs/sdk-types/src/constants.rs b/sdk-libs/sdk-types/src/constants.rs index d6715d2cab..96dc8bfac6 100644 --- a/sdk-libs/sdk-types/src/constants.rs +++ b/sdk-libs/sdk-types/src/constants.rs @@ -38,3 +38,5 @@ pub const CPI_CONTEXT_ACCOUNT_1_DISCRIMINATOR: [u8; 8] = [22, 20, 149, 218, 74, pub const CPI_CONTEXT_ACCOUNT_2_DISCRIMINATOR: [u8; 8] = [34, 184, 183, 14, 100, 80, 183, 124]; pub const SOL_POOL_PDA: [u8; 32] = pubkey_array!("CHK57ywWSDncAoRu1F8QgwYJeXuAJyyBYT4LixLXvMZ1"); + +pub const ADDRESS_TREE_V2: [u8; 32] = pubkey_array!("amt2kaJA14v3urZbZvnc5v2np8jqvc4Z8zDep5wbtzx"); diff --git a/sdk-libs/sdk/Cargo.toml b/sdk-libs/sdk/Cargo.toml index 547c637dca..58e7042330 100644 --- a/sdk-libs/sdk/Cargo.toml +++ b/sdk-libs/sdk/Cargo.toml @@ -22,6 +22,7 @@ v2 = ["light-sdk-types/v2"] cpi-context = ["light-sdk-types/cpi-context"] devnet = [] poseidon = ["light-hasher/poseidon", "light-compressed-account/poseidon"] +merkle-tree = ["light-concurrent-merkle-tree", "account-compression"] [dependencies] @@ -45,6 +46,8 @@ light-compressed-account = { workspace = true, features = ["std"] } light-hasher = { workspace = true, features = ["std"] } light-account-checks = { workspace = true, features = ["solana"] } light-zero-copy = { workspace = true } +light-concurrent-merkle-tree = { workspace = true, optional = true } +account-compression = { workspace = true, features = ["cpi"], optional = true } [dev-dependencies] num-bigint = { workspace = true } diff --git a/sdk-libs/sdk/src/lib.rs b/sdk-libs/sdk/src/lib.rs index 954163984b..f32f6a3a4f 100644 --- a/sdk-libs/sdk/src/lib.rs +++ b/sdk-libs/sdk/src/lib.rs @@ -152,6 +152,9 @@ pub mod token; pub mod transfer; pub mod utils; +#[cfg(feature = "merkle-tree")] +pub mod merkle_tree; + #[cfg(feature = "anchor")] use anchor_lang::{AnchorDeserialize, AnchorSerialize}; #[cfg(not(feature = "anchor"))] diff --git a/sdk-libs/sdk/src/merkle_tree.rs b/sdk-libs/sdk/src/merkle_tree.rs new file mode 100644 index 0000000000..a135160f8b --- /dev/null +++ b/sdk-libs/sdk/src/merkle_tree.rs @@ -0,0 +1,49 @@ +use account_compression::state_merkle_tree_from_bytes_zero_copy; +use solana_account_info::AccountInfo; +use solana_msg::msg; +use solana_program_error::ProgramError; + +use crate::error::LightSdkError; +pub mod v1 { + use light_account_checks::checks::check_owner; + use light_sdk_types::ACCOUNT_COMPRESSION_PROGRAM_ID; + + use super::*; + + /// StateMerkleTreeAccount discriminator + pub const STATE_MERKLE_TREE_DISCRIMINATOR: [u8; 8] = [172, 43, 172, 186, 29, 73, 219, 84]; + + /// Reads a root from the concurrent state merkle tree by index + pub fn read_state_merkle_tree_root( + account_info: &AccountInfo, + root_index: u16, + ) -> Result<[u8; 32], LightSdkError> { + if root_index as usize >= 2400 { + msg!( + "Invalid root index: {} greater than max root index {}", + root_index, + 2400 + ); + return Err(LightSdkError::from(ProgramError::InvalidArgument)); + } + check_owner(&ACCOUNT_COMPRESSION_PROGRAM_ID, account_info)?; + let account_data = account_info.try_borrow_data()?; + + // Check discriminator + if account_data.len() < 8 { + msg!("StateMerkleTreeAccount data too short for discriminator"); + return Err(LightSdkError::from(ProgramError::InvalidAccountData)); + } + + let discriminator = &account_data[0..8]; + if discriminator != STATE_MERKLE_TREE_DISCRIMINATOR { + msg!("Invalid StateMerkleTreeAccount discriminator"); + return Err(LightSdkError::from(ProgramError::InvalidAccountData)); + } + + let merkle_tree = state_merkle_tree_from_bytes_zero_copy(&account_data) + .map_err(|_| LightSdkError::from(ProgramError::InvalidAccountData))?; + + Ok(merkle_tree.roots[root_index as usize]) + } +} From a97e9f1a76d54ec57af9fd2aa7f2726f7f211727 Mon Sep 17 00:00:00 2001 From: ananas Date: Mon, 20 Oct 2025 18:22:30 +0100 Subject: [PATCH 2/3] fix --- Cargo.lock | 1 - scripts/format.sh | 1 - sdk-libs/sdk/Cargo.toml | 3 +-- sdk-libs/sdk/src/instruction/mod.rs | 4 +++- sdk-libs/sdk/src/merkle_tree.rs | 22 ++++++++++++++-------- 5 files changed, 18 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1d449595ea..ba2bf99c3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3927,7 +3927,6 @@ dependencies = [ name = "light-sdk" version = "0.15.0" dependencies = [ - "account-compression", "anchor-lang", "borsh 0.10.4", "light-account-checks", diff --git a/scripts/format.sh b/scripts/format.sh index 233c389ea2..9e1e2c5c76 100755 --- a/scripts/format.sh +++ b/scripts/format.sh @@ -10,7 +10,6 @@ cargo clippy \ --workspace \ --no-deps \ --all-features \ - --exclude name-service \ --exclude photon-api \ --exclude name-service \ -- -A clippy::result_large_err \ diff --git a/sdk-libs/sdk/Cargo.toml b/sdk-libs/sdk/Cargo.toml index 58e7042330..2e8d915b79 100644 --- a/sdk-libs/sdk/Cargo.toml +++ b/sdk-libs/sdk/Cargo.toml @@ -22,7 +22,7 @@ v2 = ["light-sdk-types/v2"] cpi-context = ["light-sdk-types/cpi-context"] devnet = [] poseidon = ["light-hasher/poseidon", "light-compressed-account/poseidon"] -merkle-tree = ["light-concurrent-merkle-tree", "account-compression"] +merkle-tree = ["light-concurrent-merkle-tree/solana"] [dependencies] @@ -47,7 +47,6 @@ light-hasher = { workspace = true, features = ["std"] } light-account-checks = { workspace = true, features = ["solana"] } light-zero-copy = { workspace = true } light-concurrent-merkle-tree = { workspace = true, optional = true } -account-compression = { workspace = true, features = ["cpi"], optional = true } [dev-dependencies] num-bigint = { workspace = true } diff --git a/sdk-libs/sdk/src/instruction/mod.rs b/sdk-libs/sdk/src/instruction/mod.rs index 2dc1793098..03791d0477 100644 --- a/sdk-libs/sdk/src/instruction/mod.rs +++ b/sdk-libs/sdk/src/instruction/mod.rs @@ -49,7 +49,9 @@ mod system_accounts; mod tree_info; /// Zero-knowledge proof to prove the validity of existing compressed accounts and new addresses. -pub use light_compressed_account::instruction_data::compressed_proof::ValidityProof; +pub use light_compressed_account::instruction_data::compressed_proof::{ + CompressedProof, ValidityProof, +}; pub use light_sdk_types::instruction::*; pub use pack_accounts::*; pub use system_accounts::*; diff --git a/sdk-libs/sdk/src/merkle_tree.rs b/sdk-libs/sdk/src/merkle_tree.rs index a135160f8b..718753df5f 100644 --- a/sdk-libs/sdk/src/merkle_tree.rs +++ b/sdk-libs/sdk/src/merkle_tree.rs @@ -1,30 +1,31 @@ -use account_compression::state_merkle_tree_from_bytes_zero_copy; use solana_account_info::AccountInfo; use solana_msg::msg; use solana_program_error::ProgramError; -use crate::error::LightSdkError; pub mod v1 { use light_account_checks::checks::check_owner; + use light_concurrent_merkle_tree::zero_copy::ConcurrentMerkleTreeZeroCopy; + use light_hasher::Poseidon; use light_sdk_types::ACCOUNT_COMPRESSION_PROGRAM_ID; use super::*; /// StateMerkleTreeAccount discriminator pub const STATE_MERKLE_TREE_DISCRIMINATOR: [u8; 8] = [172, 43, 172, 186, 29, 73, 219, 84]; + pub const STATE_MERKLE_TREE_ACCOUNT_METADATA_LEN: usize = 224; /// Reads a root from the concurrent state merkle tree by index pub fn read_state_merkle_tree_root( account_info: &AccountInfo, root_index: u16, - ) -> Result<[u8; 32], LightSdkError> { + ) -> Result<[u8; 32], ProgramError> { if root_index as usize >= 2400 { msg!( "Invalid root index: {} greater than max root index {}", root_index, 2400 ); - return Err(LightSdkError::from(ProgramError::InvalidArgument)); + return Err(ProgramError::InvalidArgument); } check_owner(&ACCOUNT_COMPRESSION_PROGRAM_ID, account_info)?; let account_data = account_info.try_borrow_data()?; @@ -32,17 +33,22 @@ pub mod v1 { // Check discriminator if account_data.len() < 8 { msg!("StateMerkleTreeAccount data too short for discriminator"); - return Err(LightSdkError::from(ProgramError::InvalidAccountData)); + return Err(ProgramError::InvalidAccountData); } let discriminator = &account_data[0..8]; if discriminator != STATE_MERKLE_TREE_DISCRIMINATOR { msg!("Invalid StateMerkleTreeAccount discriminator"); - return Err(LightSdkError::from(ProgramError::InvalidAccountData)); + return Err(ProgramError::InvalidAccountData); + } + let required_size = STATE_MERKLE_TREE_ACCOUNT_METADATA_LEN; + if account_data.len() < required_size { + msg!("StateMerkleTreeAccount data too short for discriminator"); + return Err(ProgramError::InvalidAccountData); } - let merkle_tree = state_merkle_tree_from_bytes_zero_copy(&account_data) - .map_err(|_| LightSdkError::from(ProgramError::InvalidAccountData))?; + let data = &account_data[required_size..]; + let merkle_tree = ConcurrentMerkleTreeZeroCopy::::from_bytes_zero_copy(data)?; Ok(merkle_tree.roots[root_index as usize]) } From 500a0930441ccc8297abb9a3981358a2c8897038 Mon Sep 17 00:00:00 2001 From: ananas Date: Mon, 20 Oct 2025 23:33:41 +0100 Subject: [PATCH 3/3] fix print --- sdk-libs/sdk/src/merkle_tree.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk-libs/sdk/src/merkle_tree.rs b/sdk-libs/sdk/src/merkle_tree.rs index 718753df5f..038193236e 100644 --- a/sdk-libs/sdk/src/merkle_tree.rs +++ b/sdk-libs/sdk/src/merkle_tree.rs @@ -43,7 +43,7 @@ pub mod v1 { } let required_size = STATE_MERKLE_TREE_ACCOUNT_METADATA_LEN; if account_data.len() < required_size { - msg!("StateMerkleTreeAccount data too short for discriminator"); + msg!("StateMerkleTreeAccount data too short for metadata"); return Err(ProgramError::InvalidAccountData); }