diff --git a/Cargo.lock b/Cargo.lock index 5030a66698..ba2bf99c3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3931,6 +3931,7 @@ dependencies = [ "borsh 0.10.4", "light-account-checks", "light-compressed-account", + "light-concurrent-merkle-tree", "light-hasher", "light-macros", "light-sdk-macros", 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/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..2e8d915b79 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/solana"] [dependencies] @@ -45,6 +46,7 @@ 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 } [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/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..038193236e --- /dev/null +++ b/sdk-libs/sdk/src/merkle_tree.rs @@ -0,0 +1,55 @@ +use solana_account_info::AccountInfo; +use solana_msg::msg; +use solana_program_error::ProgramError; + +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], ProgramError> { + if root_index as usize >= 2400 { + msg!( + "Invalid root index: {} greater than max root index {}", + root_index, + 2400 + ); + return Err(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(ProgramError::InvalidAccountData); + } + + let discriminator = &account_data[0..8]; + if discriminator != STATE_MERKLE_TREE_DISCRIMINATOR { + msg!("Invalid StateMerkleTreeAccount discriminator"); + 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 metadata"); + return Err(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]) + } +}