diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 34d871c25e..51cf3aef08 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -50,13 +50,10 @@ jobs: cargo test -p light-array-map --all-features cargo test -p light-hasher --all-features cargo test -p light-compressed-account --all-features - cargo test -p light-compressed-account --features new-unique,poseidon cargo test -p light-account-checks --all-features cargo test -p light-verifier --all-features cargo test -p light-merkle-tree-metadata --all-features cargo test -p light-zero-copy --features "std, mut, derive" - cargo test -p light-zero-copy --no-default-features # Test no_std compatibility - cargo build -p light-zero-copy --no-default-features # Ensure no_std builds cargo test -p light-zero-copy-derive --all-features cargo test -p zero-copy-derive-test cargo test -p light-hash-set --all-features diff --git a/.github/workflows/sdk-tests.yml b/.github/workflows/sdk-tests.yml index fe0ad66f6b..09fd05e183 100644 --- a/.github/workflows/sdk-tests.yml +++ b/.github/workflows/sdk-tests.yml @@ -47,11 +47,11 @@ jobs: strategy: matrix: include: - - program: sdk-test-program + - program: native sub-tests: '["cargo-test-sbf -p sdk-native-test", "cargo-test-sbf -p sdk-v1-native-test", "cargo test-sbf -p client-test"]' - - program: sdk-anchor-test-program - sub-tests: '["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -p sdk-pinocchio-v1-test", "cargo-test-sbf -p sdk-pinocchio-v2-test"]' - - program: sdk-token-test-program + - program: anchor & pinocchio + sub-tests: '["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -p sdk-pinocchio-v1-test", "cargo-test-sbf -p sdk-pinocchio-v2-test", "cargo-test-sbf -p pinocchio-nostd-test"]' + - program: token test sub-tests: '["cargo-test-sbf -p sdk-token-test"]' - program: sdk-libs packages: light-sdk-macros light-sdk light-program-test light-client light-compressed-token-types light-compressed-token-sdk diff --git a/Cargo.lock b/Cargo.lock index e73f80dd78..5030a66698 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3355,6 +3355,7 @@ name = "light-account-checks" version = "0.4.0" dependencies = [ "borsh 0.10.4", + "light-account-checks", "pinocchio", "rand 0.8.5", "solana-account-info", @@ -3436,6 +3437,7 @@ dependencies = [ "lazy_static", "light-compressed-account", "light-concurrent-merkle-tree", + "light-event", "light-hasher", "light-indexed-merkle-tree", "light-merkle-tree-metadata", @@ -3479,6 +3481,7 @@ dependencies = [ "ark-ff 0.5.0", "borsh 0.10.4", "bytemuck", + "light-compressed-account", "light-hasher", "light-heap", "light-macros", @@ -3492,6 +3495,7 @@ dependencies = [ "solana-program-error 2.2.2", "solana-pubkey 2.4.0", "thiserror 2.0.17", + "tinyvec", "zerocopy", ] @@ -3635,6 +3639,7 @@ dependencies = [ "light-array-map", "light-compressed-account", "light-compressible", + "light-ctoken-types", "light-hasher", "light-heap", "light-macros", @@ -3657,6 +3662,18 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "light-event" +version = "0.1.0" +dependencies = [ + "borsh 0.10.4", + "light-compressed-account", + "light-hasher", + "light-zero-copy", + "rand 0.8.5", + "thiserror 2.0.17", +] + [[package]] name = "light-hash-set" version = "3.0.0" @@ -3677,18 +3694,18 @@ version = "4.0.0" dependencies = [ "ark-bn254 0.5.0", "ark-ff 0.5.0", - "arrayvec", "borsh 0.10.4", + "light-hasher", "light-poseidon 0.3.0", "num-bigint 0.4.6", "pinocchio", "rand 0.8.5", "sha2 0.10.9", "sha3", - "solana-nostd-keccak", "solana-program-error 2.2.2", "solana-pubkey 2.4.0", "thiserror 2.0.17", + "tinyvec", "zerocopy", ] @@ -3734,6 +3751,7 @@ dependencies = [ "bs58", "proc-macro2", "quote", + "solana-pubkey 2.4.0", "syn 2.0.106", ] @@ -3829,6 +3847,7 @@ dependencies = [ "light-compressible", "light-concurrent-merkle-tree", "light-ctoken-types", + "light-event", "light-hasher", "light-indexed-array", "light-indexed-merkle-tree", @@ -3954,12 +3973,10 @@ dependencies = [ "light-compressed-account", "light-hasher", "light-macros", + "light-sdk", "light-sdk-macros", "light-sdk-types", - "light-zero-copy", "pinocchio", - "solana-msg 2.2.1", - "solana-pubkey 2.4.0", "thiserror 2.0.17", ] @@ -3973,7 +3990,6 @@ dependencies = [ "light-compressed-account", "light-hasher", "light-macros", - "light-zero-copy", "solana-msg 2.2.1", "solana-pubkey 2.4.0", "thiserror 2.0.17", @@ -4053,6 +4069,7 @@ dependencies = [ "light-compressible", "light-concurrent-merkle-tree", "light-ctoken-types", + "light-event", "light-hasher", "light-indexed-array", "light-indexed-merkle-tree", @@ -4117,6 +4134,7 @@ name = "light-zero-copy" version = "0.4.0" dependencies = [ "borsh 0.10.4", + "light-zero-copy", "light-zero-copy-derive", "pinocchio", "rand 0.8.5", @@ -4792,6 +4810,23 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd11022408f312e6179ece321c1f7dc0d1b2aa7765fddd39b2a7378d65a899e8" +[[package]] +name = "pinocchio-nostd-test" +version = "0.1.0" +dependencies = [ + "borsh 0.10.4", + "light-compressed-account", + "light-hasher", + "light-macros", + "light-program-test", + "light-sdk", + "light-sdk-pinocchio", + "light-sdk-types", + "pinocchio", + "solana-sdk", + "tokio", +] + [[package]] name = "pinocchio-pubkey" version = "0.3.0" @@ -5724,6 +5759,7 @@ dependencies = [ "light-sdk-types", "light-test-utils", "serial_test", + "solana-pubkey 2.4.0", "solana-sdk", "tokio", ] @@ -9912,6 +9948,7 @@ dependencies = [ "light-client", "light-compressed-account", "light-compressed-token", + "light-event", "light-hasher", "light-merkle-tree-metadata", "light-program-test", diff --git a/Cargo.toml b/Cargo.toml index e040022d48..3ee033b374 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ members = [ "anchor-programs/system", "sdk-libs/client", "sdk-libs/compressed-token-sdk", + "sdk-libs/event", "sdk-libs/token-client", "sdk-libs/macros", "sdk-libs/sdk", @@ -44,6 +45,7 @@ members = [ "program-tests/utils", "program-tests/merkle-tree", "program-tests/zero-copy-derive-test", + "program-tests/pinocchio-nostd-test", "sdk-tests/client-test", "sdk-tests/sdk-anchor-test/programs/sdk-anchor-test", "sdk-tests/sdk-pinocchio-v1-test", @@ -121,11 +123,11 @@ pinocchio-system = { version = "0.3.0" } bs58 = "^0.5.1" litesvm = "0.7" # Anchor -anchor-lang = { version = "=0.31.1" } -anchor-spl = "=0.31.1" +anchor-lang = { version = "0.31.1" } +anchor-spl = "0.31.1" # Anchor compatibility -borsh = "0.10.0" +borsh = { version = "0.10.4", default-features = false } # Serialization serde = { version = "1.0", features = ["derive"] } @@ -166,7 +168,8 @@ light-indexed-merkle-tree = { version = "3.0.0", path = "program-libs/indexed-me light-concurrent-merkle-tree = { version = "3.0.0", path = "program-libs/concurrent-merkle-tree" } light-sparse-merkle-tree = { version = "0.2.0", path = "sparse-merkle-tree" } light-client = { path = "sdk-libs/client", version = "0.15.0" } -light-hasher = { path = "program-libs/hasher", version = "4.0.0" } +light-event = { path = "sdk-libs/event", version = "0.1.0" } +light-hasher = { path = "program-libs/hasher", version = "4.0.0", default-features = false } light-macros = { path = "program-libs/macros", version = "2.1.0" } light-merkle-tree-reference = { path = "program-tests/merkle-tree", version = "3.0.1" } light-heap = { path = "program-libs/heap", version = "2.0.0" } @@ -174,13 +177,13 @@ light-prover-client = { path = "prover/client", version = "3.0.0" } light-sdk = { path = "sdk-libs/sdk", version = "0.15.0" } light-sdk-pinocchio = { path = "sdk-libs/sdk-pinocchio", version = "0.13.0" } light-sdk-macros = { path = "sdk-libs/macros", version = "0.15.0" } -light-sdk-types = { path = "sdk-libs/sdk-types", version = "0.15.0" } -light-compressed-account = { path = "program-libs/compressed-account", version = "0.5.0" } +light-sdk-types = { path = "sdk-libs/sdk-types", version = "0.15.0", default-features = false } +light-compressed-account = { path = "program-libs/compressed-account", version = "0.5.0", default-features = false } light-compressible = { path = "program-libs/compressible", version = "0.1.0" } light-ctoken-types = { path = "program-libs/ctoken-types", version = "0.1.0" } -light-account-checks = { path = "program-libs/account-checks", version = "0.4.0" } +light-account-checks = { path = "program-libs/account-checks", version = "0.4.0", default-features = false } light-verifier = { path = "program-libs/verifier", version = "4.0.0" } -light-zero-copy = { path = "program-libs/zero-copy", version = "0.4.0" } +light-zero-copy = { path = "program-libs/zero-copy", version = "0.4.0", default-features = false } light-zero-copy-derive = { path = "program-libs/zero-copy-derive", version = "0.4.0" } photon-api = { path = "sdk-libs/photon-api", version = "0.52.0" } forester-utils = { path = "forester-utils", version = "2.0.0" } diff --git a/forester-utils/Cargo.toml b/forester-utils/Cargo.toml index 3efd38a2a7..5d22cf7d5c 100644 --- a/forester-utils/Cargo.toml +++ b/forester-utils/Cargo.toml @@ -18,7 +18,7 @@ light-hash-set = { workspace = true } light-hasher = { workspace = true, features = ["poseidon"] } light-concurrent-merkle-tree = { workspace = true } light-indexed-merkle-tree = { workspace = true } -light-compressed-account = { workspace = true } +light-compressed-account = { workspace = true, features = ["std"] } light-batched-merkle-tree = { workspace = true } light-merkle-tree-metadata = { workspace = true } light-sparse-merkle-tree = { workspace = true } diff --git a/forester/Cargo.toml b/forester/Cargo.toml index c13571ca6d..55406669f5 100644 --- a/forester/Cargo.toml +++ b/forester/Cargo.toml @@ -13,7 +13,7 @@ solana-account-decoder = { workspace = true } solana-program = { workspace = true } account-compression = { workspace = true } light-batched-merkle-tree = { workspace = true } -light-compressed-account = { workspace = true } +light-compressed-account = { workspace = true, features = ["std"] } light-system-program-anchor = { workspace = true, features = ["cpi"] } light-hash-set = { workspace = true, features = ["solana"] } light-hasher = { workspace = true, features = ["poseidon"] } diff --git a/js/stateless.js/src/rpc.ts b/js/stateless.js/src/rpc.ts index 9ec26b2b4b..66a05256b0 100644 --- a/js/stateless.js/src/rpc.ts +++ b/js/stateless.js/src/rpc.ts @@ -1590,7 +1590,6 @@ export class Rpc extends Connection implements CompressionApiInterface { const timeout = isLocalTest(this.rpcEndpoint) ? 10000 : 20000; const interval = isLocalTest(this.rpcEndpoint) ? 100 : 200; const startTime = Date.now(); - // eslint-disable-next-line no-constant-condition while (true) { const indexerSlot = await this.getIndexerSlot(); diff --git a/program-libs/account-checks/Cargo.toml b/program-libs/account-checks/Cargo.toml index 3812761937..bc86cef6e9 100644 --- a/program-libs/account-checks/Cargo.toml +++ b/program-libs/account-checks/Cargo.toml @@ -7,7 +7,8 @@ license = "Apache-2.0" edition = "2021" [features] -default = [] +default = ["std"] +std = [] solana = [ "solana-program-error", "solana-sysvar", @@ -15,7 +16,7 @@ solana = [ "solana-pubkey", ] pinocchio = ["dep:pinocchio"] -test-only = ["dep:rand"] +test-only = ["dep:rand", "std"] [dependencies] solana-sysvar = { workspace = true, optional = true, features = ["bincode"] } @@ -34,6 +35,7 @@ rand = { workspace = true, optional = true } pinocchio = { workspace = true } borsh = { workspace = true } solana-pubkey = { workspace = true, features = ["curve25519", "sha2"] } +light-account-checks = { workspace = true, features = ["solana", "pinocchio", "test-only", "std"] } [lints.rust.unexpected_cfgs] level = "allow" diff --git a/program-libs/account-checks/src/account_info/solana.rs b/program-libs/account-checks/src/account_info/solana.rs index 14a5507d51..ebeca75dc5 100644 --- a/program-libs/account-checks/src/account_info/solana.rs +++ b/program-libs/account-checks/src/account_info/solana.rs @@ -5,11 +5,11 @@ use crate::error::AccountError; impl AccountInfoTrait for solana_account_info::AccountInfo<'_> { type Pubkey = solana_pubkey::Pubkey; type DataRef<'b> - = std::cell::Ref<'b, [u8]> + = core::cell::Ref<'b, [u8]> where Self: 'b; type DataRefMut<'b> - = std::cell::RefMut<'b, [u8]> + = core::cell::RefMut<'b, [u8]> where Self: 'b; @@ -48,14 +48,14 @@ impl AccountInfoTrait for solana_account_info::AccountInfo<'_> { fn try_borrow_data(&self) -> Result, AccountError> { self.data .try_borrow() - .map(|r| std::cell::Ref::map(r, |data| &**data)) + .map(|r| core::cell::Ref::map(r, |data| &**data)) .map_err(Into::into) } fn try_borrow_mut_data(&self) -> Result, AccountError> { self.data .try_borrow_mut() - .map(|r| std::cell::RefMut::map(r, |data| &mut **data)) + .map(|r| core::cell::RefMut::map(r, |data| &mut **data)) .map_err(Into::into) } diff --git a/program-libs/account-checks/src/account_info/test_account_info.rs b/program-libs/account-checks/src/account_info/test_account_info.rs index 873bcd76fe..95b91b06be 100644 --- a/program-libs/account-checks/src/account_info/test_account_info.rs +++ b/program-libs/account-checks/src/account_info/test_account_info.rs @@ -2,6 +2,9 @@ #[cfg(feature = "pinocchio")] pub mod pinocchio { + extern crate std; + use std::vec::Vec; + use pinocchio::{account_info::AccountInfo, instruction::Account, pubkey::Pubkey}; use rand::{prelude::Rng, thread_rng}; @@ -58,10 +61,10 @@ pub mod pinocchio { account_info_raw[0..8].copy_from_slice(&(account_ptr as u64).to_le_bytes()); // Need to leak the memory so it doesn't get dropped while the AccountInfo is still using it - std::mem::forget(raw_data); - std::mem::forget(account_info_raw); + core::mem::forget(raw_data); + core::mem::forget(account_info_raw); - unsafe { std::mem::transmute::<*mut Account, AccountInfo>(account_ptr) } + unsafe { core::mem::transmute::<*mut Account, AccountInfo>(account_ptr) } } #[test] @@ -103,9 +106,10 @@ pub mod pinocchio { } } -#[cfg(feature = "solana")] +#[cfg(all(feature = "solana", feature = "std"))] pub mod solana_program { - use std::{cell::RefCell, rc::Rc}; + extern crate std; + use std::{cell::RefCell, rc::Rc, vec, vec::Vec}; use solana_account_info::AccountInfo; use solana_pubkey::Pubkey; diff --git a/program-libs/account-checks/src/account_iterator.rs b/program-libs/account-checks/src/account_iterator.rs index e2be3658d9..39b34e8e80 100644 --- a/program-libs/account-checks/src/account_iterator.rs +++ b/program-libs/account-checks/src/account_iterator.rs @@ -1,4 +1,4 @@ -use std::panic::Location; +use core::panic::Location; use crate::{ checks::{check_mut, check_non_mut, check_signer}, @@ -46,13 +46,16 @@ impl<'info, T: AccountInfoTrait> AccountIterator<'info, T> { /// * `Err(AccountError::NotEnoughAccountKeys)` - If no more accounts are available #[track_caller] #[inline(always)] - pub fn next_account(&mut self, account_name: &str) -> Result<&'info T, AccountError> { + pub fn next_account(&mut self, _account_name: &str) -> Result<&'info T, AccountError> { if self.position >= self.accounts.len() { - let location = Location::caller(); - solana_msg::msg!( - "ERROR: Not enough accounts. Requested '{}' at index {} but only {} accounts available. {}:{}:{}", - account_name, self.position, self.accounts.len(), location.file(), location.line(), location.column() - ); + #[cfg(feature = "std")] + { + let location = Location::caller(); + solana_msg::msg!( + "ERROR: Not enough accounts. Requested '{}' at index {} but only {} accounts available. {}:{}:{}", + _account_name, self.position, self.accounts.len(), location.file(), location.line(), location.column() + ); + } return Err(AccountError::NotEnoughAccountKeys); } @@ -165,12 +168,15 @@ impl<'info, T: AccountInfoTrait> AccountIterator<'info, T> { #[track_caller] pub fn remaining(&self) -> Result<&'info [T], AccountError> { if self.position >= self.accounts.len() { - let location = Location::caller(); - let account_name = "remaining accounts"; - solana_msg::msg!( - "ERROR: Not enough accounts. Requested '{}' at index {} but only {} accounts available. {}:{}:{}", - account_name, self.position, self.accounts.len(), location.file(), location.line(), location.column() - ); + #[cfg(feature = "std")] + { + let location = Location::caller(); + let account_name = "remaining accounts"; + solana_msg::msg!( + "ERROR: Not enough accounts. Requested '{}' at index {} but only {} accounts available. {}:{}:{}", + account_name, self.position, self.accounts.len(), location.file(), location.line(), location.column() + ); + } return Err(AccountError::NotEnoughAccountKeys); } Ok(&self.accounts[self.position..]) @@ -212,6 +218,7 @@ impl<'info, T: AccountInfoTrait> AccountIterator<'info, T> { #[cold] fn print_on_error(&self, error: &AccountError, account_name: &str, location: &Location) { + #[cfg(feature = "std")] solana_msg::msg!( "ERROR: {}. for account '{}' at index {} {}:{}:{}", error, @@ -221,6 +228,8 @@ impl<'info, T: AccountInfoTrait> AccountIterator<'info, T> { location.line(), location.column() ); + #[cfg(not(feature = "std"))] + let _ = (error, account_name, location); } #[cold] fn print_on_error_pubkey( @@ -231,7 +240,7 @@ impl<'info, T: AccountInfoTrait> AccountIterator<'info, T> { account_name: &str, location: &Location, ) { - #[cfg(feature = "solana")] + #[cfg(all(feature = "std", feature = "solana"))] solana_msg::msg!( "ERROR: {}. for account '{}' address: {:?}, expected: {:?}, at index {} {}:{}:{}", error, @@ -243,7 +252,7 @@ impl<'info, T: AccountInfoTrait> AccountIterator<'info, T> { location.line(), location.column() ); - #[cfg(not(feature = "solana"))] + #[cfg(all(feature = "std", not(feature = "solana")))] solana_msg::msg!( "ERROR: {}. for account '{}' address: {:?}, expected: {:?}, at index {} {}:{}:{}", error, @@ -255,5 +264,7 @@ impl<'info, T: AccountInfoTrait> AccountIterator<'info, T> { location.line(), location.column() ); + #[cfg(not(feature = "std"))] + let _ = (error, pubkey1, pubkey2, account_name, location); } } diff --git a/program-libs/account-checks/src/checks.rs b/program-libs/account-checks/src/checks.rs index 29d7591dca..a957d1f21a 100644 --- a/program-libs/account-checks/src/checks.rs +++ b/program-libs/account-checks/src/checks.rs @@ -81,6 +81,7 @@ pub fn check_discriminator(bytes: &[u8]) -> Result<(), Account } if T::LIGHT_DISCRIMINATOR != bytes[0..DISCRIMINATOR_LEN] { + #[cfg(feature = "std")] solana_msg::msg!( "expected discriminator {:?} != {:?} actual", T::LIGHT_DISCRIMINATOR, @@ -111,6 +112,7 @@ pub fn check_account_balance_is_rent_exempt( } #[cfg(not(target_os = "solana"))] { + #[cfg(feature = "std")] println!("Rent exemption check skipped since not target_os solana."); Ok(lamports) } diff --git a/program-libs/account-checks/src/error.rs b/program-libs/account-checks/src/error.rs index f1d801ce06..1a0d3f0cc3 100644 --- a/program-libs/account-checks/src/error.rs +++ b/program-libs/account-checks/src/error.rs @@ -106,15 +106,15 @@ impl From for AccountError { } #[cfg(feature = "solana")] -impl From for AccountError { - fn from(_: std::cell::BorrowError) -> Self { +impl From for AccountError { + fn from(_: core::cell::BorrowError) -> Self { AccountError::BorrowAccountDataFailed } } #[cfg(feature = "solana")] -impl From for AccountError { - fn from(_: std::cell::BorrowMutError) -> Self { +impl From for AccountError { + fn from(_: core::cell::BorrowMutError) -> Self { AccountError::BorrowAccountDataFailed } } diff --git a/program-libs/account-checks/src/lib.rs b/program-libs/account-checks/src/lib.rs index 79edd89aac..b2d0db0825 100644 --- a/program-libs/account-checks/src/lib.rs +++ b/program-libs/account-checks/src/lib.rs @@ -1,3 +1,5 @@ +#![cfg_attr(not(feature = "std"), no_std)] + pub mod account_info; pub mod account_iterator; pub mod checks; diff --git a/program-libs/account-checks/src/packed_accounts.rs b/program-libs/account-checks/src/packed_accounts.rs index 1d78d4e1d2..638b26d2ee 100644 --- a/program-libs/account-checks/src/packed_accounts.rs +++ b/program-libs/account-checks/src/packed_accounts.rs @@ -1,4 +1,5 @@ -use std::panic::Location; +#[cfg(feature = "std")] +use core::panic::Location; use crate::{AccountError, AccountInfoTrait}; @@ -12,13 +13,16 @@ impl ProgramPackedAccounts<'_, A> { /// Get account by index with bounds checking #[track_caller] #[inline(never)] - pub fn get(&self, index: usize, name: &str) -> Result<&A, AccountError> { - let location = Location::caller(); + pub fn get(&self, index: usize, _name: &str) -> Result<&A, AccountError> { if index >= self.accounts.len() { - solana_msg::msg!( - "ERROR: Not enough accounts. Requested '{}' at index {} but only {} accounts available. {}:{}:{}", - name, index, self.accounts.len(), location.file(), location.line(), location.column() - ); + #[cfg(feature = "std")] + { + let location = Location::caller(); + solana_msg::msg!( + "ERROR: Not enough accounts. Requested '{}' at index {} but only {} accounts available. {}:{}:{}", + _name, index, self.accounts.len(), location.file(), location.line(), location.column() + ); + } return Err(AccountError::NotEnoughAccountKeys); } Ok(&self.accounts[index]) diff --git a/program-libs/batched-merkle-tree/Cargo.toml b/program-libs/batched-merkle-tree/Cargo.toml index b5e34ff720..7166a60df6 100644 --- a/program-libs/batched-merkle-tree/Cargo.toml +++ b/program-libs/batched-merkle-tree/Cargo.toml @@ -50,7 +50,7 @@ light-merkle-tree-metadata = { workspace = true } borsh = { workspace = true } zerocopy = { workspace = true } pinocchio = { workspace = true, optional = true } -light-compressed-account = { workspace = true } +light-compressed-account = { workspace = true, features = ["std"] } light-macros = { workspace = true } [dev-dependencies] @@ -58,6 +58,7 @@ rand = { workspace = true } light-merkle-tree-reference = { workspace = true } light-account-checks = { workspace = true, features = ["test-only"] } light-compressed-account = { workspace = true, features = ["new-unique"] } +light-hasher = { workspace = true, features = ["keccak"] } [lints.rust.unexpected_cfgs] level = "allow" diff --git a/program-libs/batched-merkle-tree/src/errors.rs b/program-libs/batched-merkle-tree/src/errors.rs index 63975273ef..a322777757 100644 --- a/program-libs/batched-merkle-tree/src/errors.rs +++ b/program-libs/batched-merkle-tree/src/errors.rs @@ -36,7 +36,7 @@ pub enum BatchedMerkleTreeError { #[cfg(feature = "pinocchio")] #[error("Program error {0}")] ProgramError(u64), - #[cfg(not(feature = "pinocchio"))] + #[cfg(all(feature = "solana", not(feature = "pinocchio")))] #[error("Program error {0}")] ProgramError(#[from] solana_program_error::ProgramError), #[error("Verifier error {0}")] @@ -76,6 +76,7 @@ impl From for u32 { BatchedMerkleTreeError::BloomFilter(e) => e.into(), BatchedMerkleTreeError::VerifierErrorError(e) => e.into(), BatchedMerkleTreeError::CompressedAccountError(e) => e.into(), + #[cfg(any(feature = "pinocchio", feature = "solana"))] #[allow(clippy::useless_conversion)] BatchedMerkleTreeError::ProgramError(e) => u32::try_from(u64::from(e)).unwrap(), BatchedMerkleTreeError::AccountError(e) => e.into(), diff --git a/program-libs/compressed-account/Cargo.toml b/program-libs/compressed-account/Cargo.toml index 874e4b2799..fe21b4e1e4 100644 --- a/program-libs/compressed-account/Cargo.toml +++ b/program-libs/compressed-account/Cargo.toml @@ -7,9 +7,11 @@ license = "Apache-2.0" edition = "2021" [features] -default = [] +default = ["alloc"] +alloc = ["light-hasher/alloc"] +std = ["alloc", "borsh/std", "dep:solana-msg", "light-zero-copy/std"] solana = ["dep:solana-pubkey", "dep:solana-program-error"] -anchor = ["anchor-lang"] +anchor = ["anchor-lang", "std"] pinocchio = ["dep:pinocchio"] bytemuck-des = ["bytemuck"] new-unique = ["dep:solana-pubkey"] @@ -22,18 +24,19 @@ thiserror = { workspace = true } zerocopy = { workspace = true, features = ["derive"] } light-hasher = { workspace = true } light-poseidon = { workspace = true, optional = true } -light-zero-copy = { workspace = true, features = ["std", "mut", "derive"] } +light-zero-copy = { workspace = true, default-features = false, features = ["alloc", "mut", "derive"] } light-macros = { workspace = true } pinocchio = { workspace = true, optional = true } solana-program-error = { workspace = true, optional = true } -solana-msg = { workspace = true } +solana-msg = { workspace = true, optional = true } # Feature-gated dependencies anchor-lang = { workspace = true, optional = true } bytemuck = { workspace = true, optional = true, features = ["derive"] } -borsh = { workspace = true } +borsh = { workspace = true, default-features = false } solana-pubkey = { workspace = true, optional = true } light-program-profiler = { workspace = true } light-heap = { workspace = true, optional = true } +tinyvec = { workspace = true } [dev-dependencies] rand = { workspace = true } @@ -43,3 +46,4 @@ borsh = { workspace = true } ark-ff = { workspace = true } ark-bn254 = { workspace = true } num-bigint = { workspace = true, features = ["rand"] } +light-compressed-account = { workspace = true, features = ["new-unique", "std"] } diff --git a/program-libs/compressed-account/src/address.rs b/program-libs/compressed-account/src/address.rs index 1e3f633ee0..fe25d24075 100644 --- a/program-libs/compressed-account/src/address.rs +++ b/program-libs/compressed-account/src/address.rs @@ -1,29 +1,13 @@ -use std::collections::HashMap; - use light_hasher::hash_to_field_size::hashv_to_bn254_field_size_be_const_array; -use super::compressed_account::{ - pack_merkle_context, PackedReadOnlyCompressedAccount, ReadOnlyCompressedAccount, -}; -use crate::{ - hash_to_bn254_field_size_be, - instruction_data::data::{ - pack_pubkey_usize, NewAddressParams, NewAddressParamsAssigned, - NewAddressParamsAssignedPacked, NewAddressParamsPacked, PackedReadOnlyAddress, - ReadOnlyAddress, - }, - CompressedAccountError, Pubkey, -}; +use crate::{CompressedAccountError, Pubkey}; pub fn derive_address_legacy( merkle_tree_pubkey: &Pubkey, seed: &[u8; 32], ) -> Result<[u8; 32], CompressedAccountError> { - let hash = hash_to_bn254_field_size_be( - [merkle_tree_pubkey.as_ref(), seed.as_ref()] - .concat() - .as_slice(), - ); + let slices = [merkle_tree_pubkey.as_ref(), seed.as_ref()]; + let hash = hashv_to_bn254_field_size_be_const_array::<3>(&slices)?; Ok(hash) } @@ -37,218 +21,6 @@ pub fn derive_address( merkle_tree_pubkey.as_slice(), program_id_bytes.as_slice(), ]; - hashv_to_bn254_field_size_be_const_array::<4>(&slices).unwrap() -} - -pub fn add_and_get_remaining_account_indices( - pubkeys: &[Pubkey], - remaining_accounts: &mut HashMap, -) -> Vec { - let mut vec = Vec::new(); - let mut next_index: usize = remaining_accounts.len(); - for pubkey in pubkeys.iter() { - match remaining_accounts.get(pubkey) { - Some(_) => {} - None => { - remaining_accounts.insert(*pubkey, next_index); - next_index += 1; - } - }; - vec.push(*remaining_accounts.get(pubkey).unwrap() as u8); - } - vec -} - -pub fn pack_new_address_params_assigned( - new_address_params: &[NewAddressParamsAssigned], - remaining_accounts: &mut HashMap, -) -> Vec { - let mut vec = Vec::new(); - for new_address_param in new_address_params.iter() { - let address_merkle_tree_account_index = pack_pubkey_usize( - &new_address_param.address_merkle_tree_pubkey, - remaining_accounts, - ); - let address_queue_account_index = - pack_pubkey_usize(&new_address_param.address_queue_pubkey, remaining_accounts); - vec.push(NewAddressParamsAssignedPacked { - seed: new_address_param.seed, - address_queue_account_index, - address_merkle_tree_root_index: new_address_param.address_merkle_tree_root_index, - address_merkle_tree_account_index, - assigned_to_account: new_address_param.assigned_account_index.is_some(), - assigned_account_index: new_address_param.assigned_account_index.unwrap_or_default(), - }); - } - - vec -} - -// Helper function to pack new address params for instruction data in rust clients -pub fn pack_new_address_params( - new_address_params: &[NewAddressParams], - remaining_accounts: &mut HashMap, -) -> Vec { - let mut new_address_params_packed = new_address_params - .iter() - .map(|x| NewAddressParamsPacked { - seed: x.seed, - address_merkle_tree_root_index: x.address_merkle_tree_root_index, - address_merkle_tree_account_index: 0, // will be assigned later - address_queue_account_index: 0, // will be assigned later - }) - .collect::>(); - let mut next_index: usize = remaining_accounts.len(); - for (i, params) in new_address_params.iter().enumerate() { - match remaining_accounts.get(¶ms.address_merkle_tree_pubkey) { - Some(_) => {} - None => { - remaining_accounts.insert(params.address_merkle_tree_pubkey, next_index); - next_index += 1; - } - }; - new_address_params_packed[i].address_merkle_tree_account_index = *remaining_accounts - .get(¶ms.address_merkle_tree_pubkey) - .unwrap() - as u8; - } - - for (i, params) in new_address_params.iter().enumerate() { - match remaining_accounts.get(¶ms.address_queue_pubkey) { - Some(_) => {} - None => { - remaining_accounts.insert(params.address_queue_pubkey, next_index); - next_index += 1; - } - }; - new_address_params_packed[i].address_queue_account_index = *remaining_accounts - .get(¶ms.address_queue_pubkey) - .unwrap() as u8; - } - new_address_params_packed -} - -pub fn pack_read_only_address_params( - new_address_params: &[ReadOnlyAddress], - remaining_accounts: &mut HashMap, -) -> Vec { - new_address_params - .iter() - .map(|x| PackedReadOnlyAddress { - address: x.address, - address_merkle_tree_root_index: x.address_merkle_tree_root_index, - address_merkle_tree_account_index: pack_account( - &x.address_merkle_tree_pubkey, - remaining_accounts, - ), - }) - .collect::>() -} - -pub fn pack_account(pubkey: &Pubkey, remaining_accounts: &mut HashMap) -> u8 { - match remaining_accounts.get(pubkey) { - Some(index) => *index as u8, - None => { - let next_index = remaining_accounts.len(); - remaining_accounts.insert(*pubkey, next_index); - next_index as u8 - } - } -} - -pub fn pack_read_only_accounts( - accounts: &[ReadOnlyCompressedAccount], - remaining_accounts: &mut HashMap, -) -> Vec { - accounts - .iter() - .map(|x| PackedReadOnlyCompressedAccount { - account_hash: x.account_hash, - merkle_context: pack_merkle_context(&[x.merkle_context], remaining_accounts)[0], - root_index: x.root_index, - }) - .collect::>() -} - -#[cfg(not(feature = "pinocchio"))] -#[cfg(test)] -mod tests { - - use super::*; - - #[test] - fn test_derive_address_with_valid_input() { - let merkle_tree_pubkey = crate::Pubkey::new_unique(); - let seeds = [1u8; 32]; - let result = derive_address_legacy(&merkle_tree_pubkey, &seeds); - let result_2 = derive_address_legacy(&merkle_tree_pubkey, &seeds); - assert_eq!(result, result_2); - } - - #[test] - fn test_derive_address_no_collision_same_seeds_diff_pubkey() { - let merkle_tree_pubkey = crate::Pubkey::new_unique(); - let merkle_tree_pubkey_2 = crate::Pubkey::new_unique(); - let seed = [2u8; 32]; - - let result = derive_address_legacy(&merkle_tree_pubkey, &seed); - let result_2 = derive_address_legacy(&merkle_tree_pubkey_2, &seed); - assert_ne!(result, result_2); - } - - #[test] - fn test_add_and_get_remaining_account_indices_empty() { - let pubkeys = vec![]; - let mut remaining_accounts = HashMap::new(); - let result = add_and_get_remaining_account_indices(&pubkeys, &mut remaining_accounts); - assert!(result.is_empty()); - } - - #[test] - fn test_add_and_get_remaining_account_indices_single() { - let pubkey = Pubkey::new_unique(); - let pubkeys = vec![pubkey]; - let mut remaining_accounts = HashMap::new(); - let result = add_and_get_remaining_account_indices(&pubkeys, &mut remaining_accounts); - assert_eq!(result, vec![0]); - assert_eq!(remaining_accounts.get(&pubkey), Some(&0)); - } - - #[test] - fn test_add_and_get_remaining_account_indices_multiple() { - let pubkey1 = Pubkey::new_unique(); - let pubkey2 = Pubkey::new_unique(); - let pubkeys = vec![pubkey1, pubkey2]; - let mut remaining_accounts = HashMap::new(); - let result = add_and_get_remaining_account_indices(&pubkeys, &mut remaining_accounts); - assert_eq!(result, vec![0, 1]); - assert_eq!(remaining_accounts.get(&pubkey1), Some(&0)); - assert_eq!(remaining_accounts.get(&pubkey2), Some(&1)); - } - - #[test] - fn test_add_and_get_remaining_account_indices_duplicates() { - let pubkey = Pubkey::new_unique(); - let pubkeys = vec![pubkey, pubkey]; - let mut remaining_accounts = HashMap::new(); - let result = add_and_get_remaining_account_indices(&pubkeys, &mut remaining_accounts); - assert_eq!(result, vec![0, 0]); - assert_eq!(remaining_accounts.get(&pubkey), Some(&0)); - assert_eq!(remaining_accounts.len(), 1); - } - - #[test] - fn test_add_and_get_remaining_account_indices_multiple_duplicates() { - let pubkey1 = Pubkey::new_unique(); - let pubkey2 = Pubkey::new_unique(); - let pubkey3 = Pubkey::new_unique(); - let pubkeys = vec![pubkey1, pubkey2, pubkey1, pubkey3, pubkey2, pubkey1]; - let mut remaining_accounts = HashMap::new(); - let result = add_and_get_remaining_account_indices(&pubkeys, &mut remaining_accounts); - assert_eq!(result, vec![0, 1, 0, 2, 1, 0]); - assert_eq!(remaining_accounts.get(&pubkey1), Some(&0)); - assert_eq!(remaining_accounts.get(&pubkey2), Some(&1)); - assert_eq!(remaining_accounts.get(&pubkey3), Some(&2)); - assert_eq!(remaining_accounts.len(), 3); - } + hashv_to_bn254_field_size_be_const_array::<4>(&slices) + .expect("hashv_to_bn254_field_size_be_const_array::<4> should be infallible for Keccak") } diff --git a/program-libs/compressed-account/src/compressed_account.rs b/program-libs/compressed-account/src/compressed_account.rs index aea089341a..08e87b7437 100644 --- a/program-libs/compressed-account/src/compressed_account.rs +++ b/program-libs/compressed-account/src/compressed_account.rs @@ -1,19 +1,20 @@ -use std::collections::HashMap; - use light_hasher::{Hasher, Poseidon}; use light_zero_copy::{ZeroCopy, ZeroCopyMut}; use crate::{ - address::pack_account, - hash_to_bn254_field_size_be, - instruction_data::{ - data::OutputCompressedAccountWithPackedContext, zero_copy::ZCompressedAccount, - }, - AnchorDeserialize, AnchorSerialize, CompressedAccountError, Pubkey, TreeType, + instruction_data::zero_copy::ZCompressedAccount, CompressedAccountError, Pubkey, TreeType, Vec, }; #[repr(C)] -#[derive(Debug, PartialEq, Default, Clone, AnchorSerialize, AnchorDeserialize, ZeroCopyMut)] +#[cfg_attr( + all(feature = "std", feature = "anchor"), + derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize) +)] +#[cfg_attr( + not(feature = "anchor"), + derive(borsh::BorshDeserialize, borsh::BorshSerialize) +)] +#[derive(Debug, PartialEq, Default, Clone, ZeroCopyMut)] pub struct PackedCompressedAccountWithMerkleContext { pub compressed_account: CompressedAccount, pub merkle_context: PackedMerkleContext, @@ -22,7 +23,15 @@ pub struct PackedCompressedAccountWithMerkleContext { pub read_only: bool, } -#[derive(Debug, PartialEq, Default, Clone, AnchorSerialize, AnchorDeserialize)] +#[cfg_attr( + all(feature = "std", feature = "anchor"), + derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize) +)] +#[cfg_attr( + not(feature = "anchor"), + derive(borsh::BorshDeserialize, borsh::BorshSerialize) +)] +#[derive(Debug, PartialEq, Default, Clone)] pub struct InCompressedAccountWithMerkleContext { pub compressed_account: InCompressedAccount, pub merkle_context: MerkleContext, @@ -41,7 +50,15 @@ impl From for InCompressedAccount { } } -#[derive(Debug, PartialEq, Default, Clone, AnchorSerialize, AnchorDeserialize)] +#[cfg_attr( + all(feature = "std", feature = "anchor"), + derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize) +)] +#[cfg_attr( + not(feature = "anchor"), + derive(borsh::BorshDeserialize, borsh::BorshSerialize) +)] +#[derive(Debug, PartialEq, Default, Clone)] pub struct PackedInCompressedAccountWithMerkleContext { pub compressed_account: InCompressedAccount, pub merkle_context: PackedMerkleContext, @@ -68,7 +85,15 @@ impl From for InCompressedAccountWithMerkleC } } -#[derive(Debug, PartialEq, Default, Clone, AnchorSerialize, AnchorDeserialize)] +#[cfg_attr( + all(feature = "std", feature = "anchor"), + derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize) +)] +#[cfg_attr( + not(feature = "anchor"), + derive(borsh::BorshDeserialize, borsh::BorshSerialize) +)] +#[derive(Debug, PartialEq, Default, Clone)] pub struct CompressedAccountWithMerkleContext { pub compressed_account: CompressedAccount, pub merkle_context: MerkleContext, @@ -103,32 +128,17 @@ impl CompressedAccountWithMerkleContext { root_index: root_index.unwrap_or_default(), }) } - - pub fn pack( - &self, - root_index: Option, - remaining_accounts: &mut HashMap, - ) -> Result { - Ok(PackedCompressedAccountWithMerkleContext { - compressed_account: self.compressed_account.clone(), - merkle_context: PackedMerkleContext { - merkle_tree_pubkey_index: pack_account( - &self.merkle_context.merkle_tree_pubkey, - remaining_accounts, - ), - queue_pubkey_index: pack_account( - &self.merkle_context.queue_pubkey, - remaining_accounts, - ), - leaf_index: self.merkle_context.leaf_index, - prove_by_index: root_index.is_none(), - }, - root_index: root_index.unwrap_or_default(), - read_only: false, - }) - } } -#[derive(Debug, PartialEq, Default, Clone, AnchorSerialize, AnchorDeserialize)] + +#[cfg_attr( + all(feature = "std", feature = "anchor"), + derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize) +)] +#[cfg_attr( + not(feature = "anchor"), + derive(borsh::BorshDeserialize, borsh::BorshSerialize) +)] +#[derive(Debug, PartialEq, Default, Clone)] pub struct ReadOnlyCompressedAccount { pub account_hash: [u8; 32], pub merkle_context: MerkleContext, @@ -136,14 +146,30 @@ pub struct ReadOnlyCompressedAccount { } #[repr(C)] -#[derive(Debug, PartialEq, Default, Clone, AnchorSerialize, AnchorDeserialize, ZeroCopyMut)] +#[cfg_attr( + all(feature = "std", feature = "anchor"), + derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize) +)] +#[cfg_attr( + not(feature = "anchor"), + derive(borsh::BorshDeserialize, borsh::BorshSerialize) +)] +#[derive(Debug, PartialEq, Default, Clone, ZeroCopyMut)] pub struct PackedReadOnlyCompressedAccount { pub account_hash: [u8; 32], pub merkle_context: PackedMerkleContext, pub root_index: u16, } -#[derive(Debug, Clone, Copy, AnchorSerialize, AnchorDeserialize, PartialEq, Default)] +#[cfg_attr( + all(feature = "std", feature = "anchor"), + derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize) +)] +#[cfg_attr( + not(feature = "anchor"), + derive(borsh::BorshDeserialize, borsh::BorshSerialize) +)] +#[derive(Debug, Clone, Copy, PartialEq, Default)] pub struct MerkleContext { pub merkle_tree_pubkey: Pubkey, pub queue_pubkey: Pubkey, @@ -153,17 +179,15 @@ pub struct MerkleContext { } #[repr(C)] -#[derive( - Debug, - Clone, - Copy, - AnchorSerialize, - AnchorDeserialize, - PartialEq, - Default, - ZeroCopy, - ZeroCopyMut, +#[cfg_attr( + all(feature = "std", feature = "anchor"), + derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize) +)] +#[cfg_attr( + not(feature = "anchor"), + derive(borsh::BorshDeserialize, borsh::BorshSerialize) )] +#[derive(Debug, Clone, Copy, PartialEq, Default, ZeroCopy, ZeroCopyMut)] pub struct PackedMerkleContext { pub merkle_tree_pubkey_index: u8, pub queue_pubkey_index: u8, @@ -171,68 +195,16 @@ pub struct PackedMerkleContext { pub prove_by_index: bool, } -pub fn pack_compressed_accounts( - compressed_accounts: &[CompressedAccountWithMerkleContext], - root_indices: &[Option], - remaining_accounts: &mut HashMap, -) -> Vec { - compressed_accounts - .iter() - .zip(root_indices.iter()) - .map(|(x, root_index)| { - let mut merkle_context = x.merkle_context; - let root_index = if let Some(root) = root_index { - *root - } else { - merkle_context.prove_by_index = true; - 0 - }; - - PackedCompressedAccountWithMerkleContext { - compressed_account: x.compressed_account.clone(), - merkle_context: pack_merkle_context(&[merkle_context], remaining_accounts)[0], - root_index, - read_only: false, - } - }) - .collect::>() -} - -pub fn pack_output_compressed_accounts( - compressed_accounts: &[CompressedAccount], - merkle_trees: &[Pubkey], - remaining_accounts: &mut HashMap, -) -> Vec { - compressed_accounts - .iter() - .zip(merkle_trees.iter()) - .map(|(x, tree)| OutputCompressedAccountWithPackedContext { - compressed_account: x.clone(), - merkle_tree_index: pack_account(tree, remaining_accounts), - }) - .collect::>() -} - -pub fn pack_merkle_context( - merkle_context: &[MerkleContext], - remaining_accounts: &mut HashMap, -) -> Vec { - merkle_context - .iter() - .map(|merkle_context| PackedMerkleContext { - leaf_index: merkle_context.leaf_index, - merkle_tree_pubkey_index: pack_account( - &merkle_context.merkle_tree_pubkey, - remaining_accounts, - ), - queue_pubkey_index: pack_account(&merkle_context.queue_pubkey, remaining_accounts), - prove_by_index: merkle_context.prove_by_index, - }) - .collect::>() -} - #[repr(C)] -#[derive(Debug, PartialEq, Default, Clone, AnchorSerialize, AnchorDeserialize, ZeroCopyMut)] +#[cfg_attr( + all(feature = "std", feature = "anchor"), + derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize) +)] +#[cfg_attr( + not(feature = "anchor"), + derive(borsh::BorshDeserialize, borsh::BorshSerialize) +)] +#[derive(Debug, PartialEq, Default, Clone, ZeroCopyMut)] pub struct CompressedAccount { pub owner: Pubkey, pub lamports: u64, @@ -240,7 +212,15 @@ pub struct CompressedAccount { pub data: Option, } -#[derive(Debug, PartialEq, Default, Clone, AnchorSerialize, AnchorDeserialize)] +#[cfg_attr( + all(feature = "std", feature = "anchor"), + derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize) +)] +#[cfg_attr( + not(feature = "anchor"), + derive(borsh::BorshDeserialize, borsh::BorshSerialize) +)] +#[derive(Debug, PartialEq, Default, Clone)] pub struct InCompressedAccount { pub owner: Pubkey, pub lamports: u64, @@ -250,7 +230,15 @@ pub struct InCompressedAccount { } #[repr(C)] -#[derive(Debug, PartialEq, Default, Clone, AnchorSerialize, AnchorDeserialize, ZeroCopyMut)] +#[cfg_attr( + all(feature = "std", feature = "anchor"), + derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize) +)] +#[cfg_attr( + not(feature = "anchor"), + derive(borsh::BorshDeserialize, borsh::BorshSerialize) +)] +#[derive(Debug, PartialEq, Default, Clone, ZeroCopyMut)] pub struct CompressedAccountData { pub discriminator: [u8; 8], pub data: Vec, @@ -266,12 +254,9 @@ pub fn hash_with_hashed_values( leaf_index: &u32, is_batched: bool, ) -> Result<[u8; 32], CompressedAccountError> { - // TODO: replace with array - let capacity = 3 - + std::cmp::min(*lamports, 1) as usize - + address.is_some() as usize - + data.is_some() as usize * 2; - let mut vec: Vec<&[u8]> = Vec::with_capacity(capacity); + // Use ArrayVec with max capacity of 7 elements: + // owner_hashed + leaf_index + merkle_tree_hashed + lamports + address + discriminator + data_hash + let mut vec: tinyvec::ArrayVec<[&[u8]; 7]> = tinyvec::ArrayVec::new(); vec.push(owner_hashed.as_slice()); // leaf index and merkle tree pubkey are used to make every compressed account hash unique @@ -342,6 +327,7 @@ impl CompressedAccount { leaf_index: &u32, is_batched: bool, ) -> Result<[u8; 32], CompressedAccountError> { + use light_hasher::hash_to_field_size::hash_to_bn254_field_size_be; let hashed_mt = hash_to_bn254_field_size_be(merkle_tree_pubkey.as_ref()); self.hash_with_hashed_values( @@ -381,6 +367,7 @@ impl ZCompressedAccount<'_> { leaf_index: &u32, is_batched: bool, ) -> Result<[u8; 32], CompressedAccountError> { + use light_hasher::hash_to_field_size::hash_to_bn254_field_size_be; self.hash_with_hashed_values( &hash_to_bn254_field_size_be(&self.owner.to_bytes()), &hash_to_bn254_field_size_be(merkle_tree_pubkey.as_slice()), @@ -392,7 +379,8 @@ impl ZCompressedAccount<'_> { #[cfg(all(not(feature = "pinocchio"), test, feature = "poseidon"))] mod tests { - use light_hasher::Poseidon; + use borsh::BorshSerialize; + use light_hasher::{hash_to_field_size::hash_to_bn254_field_size_be, Poseidon}; use light_zero_copy::traits::ZeroCopyAt; use num_bigint::BigUint; use rand::Rng; diff --git a/program-libs/compressed-account/src/instruction_data/compressed_proof.rs b/program-libs/compressed-account/src/instruction_data/compressed_proof.rs index a2cde10a10..7507bbf477 100644 --- a/program-libs/compressed-account/src/instruction_data/compressed_proof.rs +++ b/program-libs/compressed-account/src/instruction_data/compressed_proof.rs @@ -1,17 +1,21 @@ use light_zero_copy::{errors::ZeroCopyError, traits::ZeroCopyAt, ZeroCopyMut}; use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Ref, Unaligned}; -use crate::{AnchorDeserialize, AnchorSerialize}; - #[repr(C)] +#[cfg_attr( + all(feature = "std", feature = "anchor"), + derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize) +)] +#[cfg_attr( + not(feature = "anchor"), + derive(borsh::BorshDeserialize, borsh::BorshSerialize) +)] #[derive( Debug, Clone, Copy, PartialEq, Eq, - AnchorDeserialize, - AnchorSerialize, KnownLayout, Immutable, FromBytes, @@ -42,7 +46,15 @@ impl<'a> ZeroCopyAt<'a> for CompressedProof { } } -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, AnchorDeserialize, AnchorSerialize)] +#[cfg_attr( + all(feature = "std", feature = "anchor"), + derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize) +)] +#[cfg_attr( + not(feature = "anchor"), + derive(borsh::BorshDeserialize, borsh::BorshSerialize) +)] +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub struct ValidityProof(pub Option); impl ValidityProof { diff --git a/program-libs/compressed-account/src/instruction_data/cpi_context.rs b/program-libs/compressed-account/src/instruction_data/cpi_context.rs index 1a671fefb8..a20489c4aa 100644 --- a/program-libs/compressed-account/src/instruction_data/cpi_context.rs +++ b/program-libs/compressed-account/src/instruction_data/cpi_context.rs @@ -1,16 +1,19 @@ use light_zero_copy::ZeroCopyMut; -use crate::{ - instruction_data::{ - zero_copy::ZCompressedCpiContext, zero_copy_set::CompressedCpiContextTrait, - }, - AnchorDeserialize, AnchorSerialize, +use crate::instruction_data::{ + zero_copy::ZCompressedCpiContext, zero_copy_set::CompressedCpiContextTrait, }; #[repr(C)] -#[derive( - AnchorSerialize, AnchorDeserialize, Debug, Clone, Copy, PartialEq, Eq, Default, ZeroCopyMut, +#[cfg_attr( + all(feature = "std", feature = "anchor"), + derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize) )] +#[cfg_attr( + not(feature = "anchor"), + derive(borsh::BorshDeserialize, borsh::BorshSerialize) +)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, ZeroCopyMut)] pub struct CompressedCpiContext { /// Is set by the program that is invoking the CPI to signal that is should /// set the cpi context. diff --git a/program-libs/compressed-account/src/instruction_data/cpi_context.rs.tmp b/program-libs/compressed-account/src/instruction_data/cpi_context.rs.tmp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/program-libs/compressed-account/src/instruction_data/data.rs b/program-libs/compressed-account/src/instruction_data/data.rs index af5a86a3c7..86768f67aa 100644 --- a/program-libs/compressed-account/src/instruction_data/data.rs +++ b/program-libs/compressed-account/src/instruction_data/data.rs @@ -1,15 +1,21 @@ -use std::collections::HashMap; - use light_zero_copy::ZeroCopyMut; use crate::{ compressed_account::{CompressedAccount, PackedCompressedAccountWithMerkleContext}, discriminators::DISCRIMINATOR_INVOKE, instruction_data::{compressed_proof::CompressedProof, traits::LightInstructionData}, - AnchorDeserialize, AnchorSerialize, InstructionDiscriminator, Pubkey, + InstructionDiscriminator, Pubkey, Vec, }; -#[derive(Debug, PartialEq, Default, Clone, AnchorDeserialize, AnchorSerialize)] +#[cfg_attr( + all(feature = "std", feature = "anchor"), + derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize) +)] +#[cfg_attr( + not(feature = "anchor"), + derive(borsh::BorshDeserialize, borsh::BorshSerialize) +)] +#[derive(Debug, PartialEq, Default, Clone)] pub struct InstructionDataInvoke { pub proof: Option, pub input_compressed_accounts_with_merkle_context: @@ -80,23 +86,45 @@ impl InstructionDiscriminator for InstructionDataInvoke { impl LightInstructionData for InstructionDataInvoke {} -#[derive(Debug, PartialEq, Default, Clone, AnchorDeserialize, AnchorSerialize)] +#[cfg_attr( + all(feature = "std", feature = "anchor"), + derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize) +)] +#[cfg_attr( + not(feature = "anchor"), + derive(borsh::BorshDeserialize, borsh::BorshSerialize) +)] +#[derive(Debug, PartialEq, Default, Clone)] pub struct OutputCompressedAccountWithContext { pub compressed_account: CompressedAccount, pub merkle_tree: Pubkey, } #[repr(C)] -#[derive(Debug, PartialEq, Default, Clone, AnchorDeserialize, AnchorSerialize, ZeroCopyMut)] +#[cfg_attr( + all(feature = "std", feature = "anchor"), + derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize) +)] +#[cfg_attr( + not(feature = "anchor"), + derive(borsh::BorshDeserialize, borsh::BorshSerialize) +)] +#[derive(Debug, PartialEq, Default, Clone, ZeroCopyMut)] pub struct OutputCompressedAccountWithPackedContext { pub compressed_account: CompressedAccount, pub merkle_tree_index: u8, } #[repr(C)] -#[derive( - Debug, PartialEq, Default, Clone, Copy, AnchorDeserialize, AnchorSerialize, ZeroCopyMut, +#[cfg_attr( + all(feature = "std", feature = "anchor"), + derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize) +)] +#[cfg_attr( + not(feature = "anchor"), + derive(borsh::BorshDeserialize, borsh::BorshSerialize) )] +#[derive(Debug, PartialEq, Default, Clone, Copy, ZeroCopyMut)] pub struct NewAddressParamsPacked { pub seed: [u8; 32], pub address_queue_account_index: u8, @@ -105,9 +133,15 @@ pub struct NewAddressParamsPacked { } #[repr(C)] -#[derive( - Debug, PartialEq, Default, Clone, Copy, AnchorDeserialize, AnchorSerialize, ZeroCopyMut, +#[cfg_attr( + all(feature = "std", feature = "anchor"), + derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize) +)] +#[cfg_attr( + not(feature = "anchor"), + derive(borsh::BorshDeserialize, borsh::BorshSerialize) )] +#[derive(Debug, PartialEq, Default, Clone, Copy, ZeroCopyMut)] pub struct NewAddressParamsAssignedPacked { pub seed: [u8; 32], pub address_queue_account_index: u8, @@ -138,7 +172,15 @@ impl NewAddressParamsAssignedPacked { } } -#[derive(Debug, PartialEq, Default, Clone, AnchorDeserialize, AnchorSerialize)] +#[cfg_attr( + all(feature = "std", feature = "anchor"), + derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize) +)] +#[cfg_attr( + not(feature = "anchor"), + derive(borsh::BorshDeserialize, borsh::BorshSerialize) +)] +#[derive(Debug, PartialEq, Default, Clone)] pub struct NewAddressParams { pub seed: [u8; 32], pub address_queue_pubkey: Pubkey, @@ -146,7 +188,15 @@ pub struct NewAddressParams { pub address_merkle_tree_root_index: u16, } -#[derive(Debug, PartialEq, Default, Clone, AnchorDeserialize, AnchorSerialize)] +#[cfg_attr( + all(feature = "std", feature = "anchor"), + derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize) +)] +#[cfg_attr( + not(feature = "anchor"), + derive(borsh::BorshDeserialize, borsh::BorshSerialize) +)] +#[derive(Debug, PartialEq, Default, Clone)] pub struct NewAddressParamsAssigned { pub seed: [u8; 32], pub address_queue_pubkey: Pubkey, @@ -156,40 +206,32 @@ pub struct NewAddressParamsAssigned { } #[repr(C)] -#[derive( - Debug, PartialEq, Default, Clone, Copy, AnchorDeserialize, AnchorSerialize, ZeroCopyMut, +#[cfg_attr( + all(feature = "std", feature = "anchor"), + derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize) +)] +#[cfg_attr( + not(feature = "anchor"), + derive(borsh::BorshDeserialize, borsh::BorshSerialize) )] +#[derive(Debug, PartialEq, Default, Clone, Copy, ZeroCopyMut)] pub struct PackedReadOnlyAddress { pub address: [u8; 32], pub address_merkle_tree_root_index: u16, pub address_merkle_tree_account_index: u8, } -#[derive(Debug, PartialEq, Default, Clone, AnchorDeserialize, AnchorSerialize)] +#[cfg_attr( + all(feature = "std", feature = "anchor"), + derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize) +)] +#[cfg_attr( + not(feature = "anchor"), + derive(borsh::BorshDeserialize, borsh::BorshSerialize) +)] +#[derive(Debug, PartialEq, Default, Clone)] pub struct ReadOnlyAddress { pub address: [u8; 32], pub address_merkle_tree_pubkey: Pubkey, pub address_merkle_tree_root_index: u16, } -// TODO: move -pub fn pack_pubkey(pubkey: &Pubkey, hash_set: &mut HashMap) -> u8 { - match hash_set.get(pubkey) { - Some(index) => *index, - None => { - let index = hash_set.len() as u8; - hash_set.insert(*pubkey, index); - index - } - } -} - -pub fn pack_pubkey_usize(pubkey: &Pubkey, hash_set: &mut HashMap) -> u8 { - match hash_set.get(pubkey) { - Some(index) => (*index) as u8, - None => { - let index = hash_set.len(); - hash_set.insert(*pubkey, index); - index as u8 - } - } -} diff --git a/program-libs/compressed-account/src/instruction_data/insert_into_queues.rs b/program-libs/compressed-account/src/instruction_data/insert_into_queues.rs index 885df99b97..57206dbfef 100644 --- a/program-libs/compressed-account/src/instruction_data/insert_into_queues.rs +++ b/program-libs/compressed-account/src/instruction_data/insert_into_queues.rs @@ -1,4 +1,4 @@ -use std::{ +use core::{ mem::size_of, ops::{Deref, DerefMut}, }; @@ -98,7 +98,7 @@ impl InstructionDiscriminator for InsertIntoQueuesInstructionData<'_> { impl<'a> ZeroCopyAt<'a> for InsertIntoQueuesInstructionData<'a> { type ZeroCopyAt = Self; - fn zero_copy_at(bytes: &'a [u8]) -> std::result::Result<(Self, &'a [u8]), ZeroCopyError> { + fn zero_copy_at(bytes: &'a [u8]) -> core::result::Result<(Self, &'a [u8]), ZeroCopyError> { let (meta, bytes) = Ref::<&[u8], InsertIntoQueuesInstructionDataMeta>::from_prefix(bytes)?; let (leaves, bytes) = ZeroCopySlice::::from_bytes_at(bytes)?; @@ -261,7 +261,7 @@ impl<'a> InsertIntoQueuesInstructionDataMut<'a> { num_output_trees: u8, num_input_trees: u8, num_address_trees: u8, - ) -> std::result::Result<(Self, &'a mut [u8]), ZeroCopyError> { + ) -> core::result::Result<(Self, &'a mut [u8]), ZeroCopyError> { let (meta, bytes) = Ref::<&mut [u8], InsertIntoQueuesInstructionDataMeta>::from_prefix(bytes)?; let (leaves, bytes) = @@ -319,81 +319,86 @@ impl DerefMut for InsertIntoQueuesInstructionDataMut<'_> { } } -#[test] -fn test_rnd_insert_into_queues_ix_data() { - use rand::{rngs::StdRng, thread_rng, Rng, SeedableRng}; - let seed = thread_rng().gen(); - println!("seed {}", seed); - let mut rng = StdRng::seed_from_u64(seed); - let num_iters = 1000; +#[cfg(all(test, feature = "std"))] +mod test { - for _ in 0..num_iters { - let leaves_capacity: u8 = rng.gen(); - let nullifiers_capacity: u8 = rng.gen(); - let addresses_capacity: u8 = rng.gen(); - let num_output_trees: u8 = rng.gen(); - let num_input_trees: u8 = rng.gen(); - let num_address_trees: u8 = rng.gen(); - let size = InsertIntoQueuesInstructionDataMut::required_size_for_capacity( - leaves_capacity, - nullifiers_capacity, - addresses_capacity, - num_output_trees, - num_input_trees, - num_address_trees, - ); - let mut bytes = vec![0u8; size]; - let (mut new_data, _) = InsertIntoQueuesInstructionDataMut::new_at( - &mut bytes, - leaves_capacity, - nullifiers_capacity, - addresses_capacity, - num_output_trees, - num_input_trees, - num_address_trees, - ) - .unwrap(); - *new_data.meta = InsertIntoQueuesInstructionDataMeta { - is_invoked_by_program: rng.gen(), - bump: rng.gen(), - num_queues: rng.gen(), - num_output_queues: rng.gen(), - start_output_appends: rng.gen(), - num_address_queues: rng.gen(), - tx_hash: rng.gen(), - }; - for i in 0..leaves_capacity { - new_data.leaves[i as usize] = AppendLeavesInput { - account_index: rng.gen(), - leaf: rng.gen(), - }; - } - for i in 0..nullifiers_capacity { - new_data.nullifiers[i as usize] = InsertNullifierInput { - account_hash: rng.gen(), - leaf_index: rng.gen::().into(), - prove_by_index: rng.gen(), - tree_index: rng.gen(), - queue_index: rng.gen(), - }; - } - for i in 0..addresses_capacity { - new_data.addresses[i as usize] = InsertAddressInput { - address: rng.gen(), - tree_index: rng.gen(), - queue_index: rng.gen(), + use super::*; + #[test] + fn test_rnd_insert_into_queues_ix_data() { + use rand::{rngs::StdRng, thread_rng, Rng, SeedableRng}; + let seed = thread_rng().gen(); + println!("seed {}", seed); + let mut rng = StdRng::seed_from_u64(seed); + let num_iters = 1000; + + for _ in 0..num_iters { + let leaves_capacity: u8 = rng.gen(); + let nullifiers_capacity: u8 = rng.gen(); + let addresses_capacity: u8 = rng.gen(); + let num_output_trees: u8 = rng.gen(); + let num_input_trees: u8 = rng.gen(); + let num_address_trees: u8 = rng.gen(); + let size = InsertIntoQueuesInstructionDataMut::required_size_for_capacity( + leaves_capacity, + nullifiers_capacity, + addresses_capacity, + num_output_trees, + num_input_trees, + num_address_trees, + ); + let mut bytes = vec![0u8; size]; + let (mut new_data, _) = InsertIntoQueuesInstructionDataMut::new_at( + &mut bytes, + leaves_capacity, + nullifiers_capacity, + addresses_capacity, + num_output_trees, + num_input_trees, + num_address_trees, + ) + .unwrap(); + *new_data.meta = InsertIntoQueuesInstructionDataMeta { + is_invoked_by_program: rng.gen(), + bump: rng.gen(), + num_queues: rng.gen(), + num_output_queues: rng.gen(), + start_output_appends: rng.gen(), + num_address_queues: rng.gen(), + tx_hash: rng.gen(), }; + for i in 0..leaves_capacity { + new_data.leaves[i as usize] = AppendLeavesInput { + account_index: rng.gen(), + leaf: rng.gen(), + }; + } + for i in 0..nullifiers_capacity { + new_data.nullifiers[i as usize] = InsertNullifierInput { + account_hash: rng.gen(), + leaf_index: rng.gen::().into(), + prove_by_index: rng.gen(), + tree_index: rng.gen(), + queue_index: rng.gen(), + }; + } + for i in 0..addresses_capacity { + new_data.addresses[i as usize] = InsertAddressInput { + address: rng.gen(), + tree_index: rng.gen(), + queue_index: rng.gen(), + }; + } + let nullifiers = new_data.nullifiers.to_vec(); + let leaves = new_data.leaves.to_vec(); + let addresses = new_data.addresses.to_vec(); + let meta = *new_data.meta; + let zero_copy = InsertIntoQueuesInstructionData::zero_copy_at(&bytes) + .unwrap() + .0; + assert_eq!(meta, *zero_copy.meta); + assert_eq!(leaves.as_slice(), zero_copy.leaves.as_slice()); + assert_eq!(nullifiers.as_slice(), zero_copy.nullifiers.as_slice()); + assert_eq!(addresses.as_slice(), zero_copy.addresses.as_slice()); } - let nullifiers = new_data.nullifiers.to_vec(); - let leaves = new_data.leaves.to_vec(); - let addresses = new_data.addresses.to_vec(); - let meta = *new_data.meta; - let zero_copy = InsertIntoQueuesInstructionData::zero_copy_at(&bytes) - .unwrap() - .0; - assert_eq!(meta, *zero_copy.meta); - assert_eq!(leaves.as_slice(), zero_copy.leaves.as_slice()); - assert_eq!(nullifiers.as_slice(), zero_copy.nullifiers.as_slice()); - assert_eq!(addresses.as_slice(), zero_copy.addresses.as_slice()); } } diff --git a/program-libs/compressed-account/src/instruction_data/invoke_cpi.rs b/program-libs/compressed-account/src/instruction_data/invoke_cpi.rs index d693d5e186..f0b9c4fa6a 100644 --- a/program-libs/compressed-account/src/instruction_data/invoke_cpi.rs +++ b/program-libs/compressed-account/src/instruction_data/invoke_cpi.rs @@ -8,11 +8,19 @@ use crate::{ compressed_account::PackedCompressedAccountWithMerkleContext, discriminators::DISCRIMINATOR_INVOKE_CPI, instruction_data::{compressed_proof::CompressedProof, traits::LightInstructionData}, - AnchorDeserialize, AnchorSerialize, CompressedAccountError, InstructionDiscriminator, + InstructionDiscriminator, Vec, }; #[repr(C)] -#[derive(Debug, PartialEq, Default, Clone, AnchorDeserialize, AnchorSerialize, ZeroCopyMut)] +#[cfg_attr( + all(feature = "std", feature = "anchor"), + derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize) +)] +#[cfg_attr( + not(feature = "anchor"), + derive(borsh::BorshDeserialize, borsh::BorshSerialize) +)] +#[derive(Debug, PartialEq, Default, Clone, ZeroCopyMut)] pub struct InstructionDataInvokeCpi { pub proof: Option, pub new_address_params: Vec, @@ -26,10 +34,16 @@ pub struct InstructionDataInvokeCpi { } impl LightInstructionData for InstructionDataInvokeCpi { - fn data(&self) -> Result, CompressedAccountError> { + #[cfg(feature = "alloc")] + fn data(&self) -> Result, crate::CompressedAccountError> { + #[cfg(feature = "anchor")] + use anchor_lang::AnchorSerialize as BorshSerialize; + #[cfg(not(feature = "anchor"))] + use borsh::BorshSerialize; + let inputs = self .try_to_vec() - .map_err(|_| CompressedAccountError::InvalidArgument)?; + .map_err(|_| crate::CompressedAccountError::InvalidArgument)?; let mut data = Vec::with_capacity(12 + inputs.len()); data.extend_from_slice(self.discriminator()); data.extend_from_slice(&(inputs.len() as u32).to_le_bytes()); diff --git a/program-libs/compressed-account/src/instruction_data/traits.rs b/program-libs/compressed-account/src/instruction_data/traits.rs index 00114317b1..f5e8a95733 100644 --- a/program-libs/compressed-account/src/instruction_data/traits.rs +++ b/program-libs/compressed-account/src/instruction_data/traits.rs @@ -1,6 +1,12 @@ -use std::fmt::Debug; +use core::fmt::Debug; +#[cfg(all(feature = "std", feature = "anchor"))] +use anchor_lang::AnchorSerialize; +#[allow(unused_imports)] +#[cfg(not(all(feature = "std", feature = "anchor")))] +use borsh::BorshSerialize as AnchorSerialize; use light_program_profiler::profile; +use tinyvec::ArrayVec; use zerocopy::Ref; use super::{ @@ -8,26 +14,34 @@ use super::{ cpi_context::CompressedCpiContext, zero_copy::{ZPackedMerkleContext, ZPackedReadOnlyAddress, ZPackedReadOnlyCompressedAccount}, }; -use crate::{ - compressed_account::CompressedAccountData, pubkey::Pubkey, AnchorSerialize, - CompressedAccountError, -}; +use crate::{compressed_account::CompressedAccountData, pubkey::Pubkey, CompressedAccountError}; pub trait InstructionDiscriminator { fn discriminator(&self) -> &'static [u8]; } pub trait LightInstructionData: InstructionDiscriminator + AnchorSerialize { + #[cfg(feature = "alloc")] #[profile] - fn data(&self) -> Result, CompressedAccountError> { - let inputs = self - .try_to_vec() + fn data(&self) -> Result, CompressedAccountError> { + let inputs = AnchorSerialize::try_to_vec(self) .map_err(|_| CompressedAccountError::InvalidArgument)?; - let mut data = Vec::with_capacity(8 + inputs.len()); + let mut data = crate::Vec::with_capacity(8 + inputs.len()); data.extend_from_slice(self.discriminator()); data.extend_from_slice(inputs.as_slice()); Ok(data) } + + #[profile] + fn data_array(&self) -> Result, CompressedAccountError> { + let mut data = ArrayVec::new(); + // Add discriminator + data.extend_from_slice(self.discriminator()); + self.serialize(&mut data.as_mut_slice()) + .map_err(|_e| CompressedAccountError::InputTooLarge(data.len().saturating_sub(N)))?; + + Ok(data) + } } pub trait InstructionData<'a> { diff --git a/program-libs/compressed-account/src/instruction_data/with_account_info.rs b/program-libs/compressed-account/src/instruction_data/with_account_info.rs index 4e2f40c60b..01ae244321 100644 --- a/program-libs/compressed-account/src/instruction_data/with_account_info.rs +++ b/program-libs/compressed-account/src/instruction_data/with_account_info.rs @@ -1,4 +1,4 @@ -use std::ops::{Deref, DerefMut}; +use core::ops::{Deref, DerefMut}; use light_zero_copy::{errors::ZeroCopyError, slice::ZeroCopySliceBorsh, traits::ZeroCopyAt}; use zerocopy::{ @@ -28,10 +28,18 @@ use crate::{ with_readonly::InAccount, }, pubkey::Pubkey, - AnchorDeserialize, AnchorSerialize, CompressedAccountError, InstructionDiscriminator, + CompressedAccountError, InstructionDiscriminator, Vec, }; -#[derive(Debug, Default, PartialEq, Clone, AnchorSerialize, AnchorDeserialize)] +#[cfg_attr( + all(feature = "std", feature = "anchor"), + derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize) +)] +#[cfg_attr( + not(feature = "anchor"), + derive(borsh::BorshDeserialize, borsh::BorshSerialize) +)] +#[derive(Debug, Default, PartialEq, Clone)] pub struct InAccountInfo { pub discriminator: [u8; 8], /// Data hash @@ -85,7 +93,15 @@ pub struct ZInAccountInfo { pub lamports: U64, } -#[derive(Debug, Default, PartialEq, Clone, AnchorSerialize, AnchorDeserialize)] +#[cfg_attr( + all(feature = "std", feature = "anchor"), + derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize) +)] +#[cfg_attr( + not(feature = "anchor"), + derive(borsh::BorshDeserialize, borsh::BorshSerialize) +)] +#[derive(Debug, Default, PartialEq, Clone)] pub struct OutAccountInfo { pub discriminator: [u8; 8], /// Data hash @@ -310,7 +326,15 @@ impl DerefMut for ZOutAccountInfoMut<'_> { } } -#[derive(Debug, PartialEq, Clone, Default, AnchorSerialize, AnchorDeserialize)] +#[cfg_attr( + all(feature = "std", feature = "anchor"), + derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize) +)] +#[cfg_attr( + not(feature = "anchor"), + derive(borsh::BorshDeserialize, borsh::BorshSerialize) +)] +#[derive(Debug, PartialEq, Clone, Default)] pub struct CompressedAccountInfo { /// Address. pub address: Option<[u8; 32]>, @@ -351,7 +375,15 @@ impl<'a> CompressedAccountInfo { } } -#[derive(Debug, PartialEq, Default, Clone, AnchorSerialize, AnchorDeserialize)] +#[cfg_attr( + all(feature = "std", feature = "anchor"), + derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize) +)] +#[cfg_attr( + not(feature = "anchor"), + derive(borsh::BorshDeserialize, borsh::BorshSerialize) +)] +#[derive(Debug, PartialEq, Default, Clone)] pub struct InstructionDataInvokeCpiWithAccountInfo { /// 0 V1 instruction accounts. /// 1 Optimized V2 instruction accounts. diff --git a/program-libs/compressed-account/src/instruction_data/with_account_info.rs.tmp b/program-libs/compressed-account/src/instruction_data/with_account_info.rs.tmp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/program-libs/compressed-account/src/instruction_data/with_readonly.rs b/program-libs/compressed-account/src/instruction_data/with_readonly.rs index f623470484..d93524b19e 100644 --- a/program-libs/compressed-account/src/instruction_data/with_readonly.rs +++ b/program-libs/compressed-account/src/instruction_data/with_readonly.rs @@ -1,4 +1,4 @@ -use std::ops::Deref; +use core::ops::Deref; use light_zero_copy::{ errors::ZeroCopyError, slice::ZeroCopySliceBorsh, traits::ZeroCopyAt, ZeroCopyMut, @@ -31,11 +31,19 @@ use crate::{ discriminators::DISCRIMINATOR_INVOKE_CPI_WITH_READ_ONLY, instruction_data::traits::LightInstructionData, pubkey::Pubkey, - AnchorDeserialize, AnchorSerialize, CompressedAccountError, InstructionDiscriminator, + CompressedAccountError, InstructionDiscriminator, Vec, }; #[repr(C)] -#[derive(Debug, Default, PartialEq, Clone, AnchorSerialize, AnchorDeserialize, ZeroCopyMut)] +#[cfg_attr( + all(feature = "std", feature = "anchor"), + derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize) +)] +#[cfg_attr( + not(feature = "anchor"), + derive(borsh::BorshDeserialize, borsh::BorshSerialize) +)] +#[derive(Debug, Default, PartialEq, Clone, ZeroCopyMut)] pub struct InAccount { pub discriminator: [u8; 8], /// Data hash @@ -219,7 +227,15 @@ impl<'a> Deref for ZInAccount<'a> { } #[repr(C)] -#[derive(Debug, PartialEq, Default, Clone, AnchorSerialize, AnchorDeserialize, ZeroCopyMut)] +#[cfg_attr( + all(feature = "std", feature = "anchor"), + derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize) +)] +#[cfg_attr( + not(feature = "anchor"), + derive(borsh::BorshDeserialize, borsh::BorshSerialize) +)] +#[derive(Debug, PartialEq, Default, Clone, ZeroCopyMut)] pub struct InstructionDataInvokeCpiWithReadOnly { /// 0 With program ids /// 1 without program ids @@ -594,80 +610,6 @@ impl PartialEq for ZInstructionDataInvokeC } } -// TODO: add randomized tests. -#[test] -fn test_read_only_zero_copy() { - let borsh_struct = InstructionDataInvokeCpiWithReadOnly { - mode: 0, - bump: 0, - invoking_program_id: Pubkey::default(), - compress_or_decompress_lamports: 0, - is_compress: false, - with_cpi_context: false, - with_transaction_hash: true, - cpi_context: CompressedCpiContext { - set_context: false, - first_set_context: false, - cpi_context_account_index: 0, - }, - proof: None, - new_address_params: vec![NewAddressParamsAssignedPacked { - seed: [1; 32], - address_merkle_tree_account_index: 1, - address_queue_account_index: 2, - address_merkle_tree_root_index: 3, - assigned_to_account: true, - assigned_account_index: 2, - }], - input_compressed_accounts: vec![InAccount { - discriminator: [1, 2, 3, 4, 5, 6, 7, 8], - data_hash: [10; 32], - merkle_context: PackedMerkleContext { - merkle_tree_pubkey_index: 1, - queue_pubkey_index: 2, - leaf_index: 3, - prove_by_index: false, - }, - root_index: 3, - lamports: 1000, - address: Some([30; 32]), - }], - output_compressed_accounts: vec![OutputCompressedAccountWithPackedContext { - compressed_account: CompressedAccount { - owner: Pubkey::default(), - lamports: 2000, - address: Some([40; 32]), - data: Some(CompressedAccountData { - discriminator: [3, 4, 5, 6, 7, 8, 9, 10], - data: vec![], - data_hash: [50; 32], - }), - }, - merkle_tree_index: 3, - }], - read_only_addresses: vec![PackedReadOnlyAddress { - address: [70; 32], - address_merkle_tree_account_index: 4, - address_merkle_tree_root_index: 5, - }], - read_only_accounts: vec![PackedReadOnlyCompressedAccount { - account_hash: [80; 32], - merkle_context: PackedMerkleContext { - merkle_tree_pubkey_index: 5, - queue_pubkey_index: 6, - leaf_index: 7, - prove_by_index: false, - }, - root_index: 8, - }], - }; - let bytes = borsh_struct.try_to_vec().unwrap(); - - let (zero_copy, _) = InstructionDataInvokeCpiWithReadOnly::zero_copy_at(&bytes).unwrap(); - - assert_eq!(zero_copy, borsh_struct); -} - #[cfg(all(not(feature = "pinocchio"), feature = "new-unique"))] #[cfg(test)] mod test { @@ -679,6 +621,79 @@ mod test { use super::*; use crate::CompressedAccountError; + // TODO: add randomized tests. + #[test] + fn test_read_only_zero_copy() { + let borsh_struct = InstructionDataInvokeCpiWithReadOnly { + mode: 0, + bump: 0, + invoking_program_id: Pubkey::default(), + compress_or_decompress_lamports: 0, + is_compress: false, + with_cpi_context: false, + with_transaction_hash: true, + cpi_context: CompressedCpiContext { + set_context: false, + first_set_context: false, + cpi_context_account_index: 0, + }, + proof: None, + new_address_params: vec![NewAddressParamsAssignedPacked { + seed: [1; 32], + address_merkle_tree_account_index: 1, + address_queue_account_index: 2, + address_merkle_tree_root_index: 3, + assigned_to_account: true, + assigned_account_index: 2, + }], + input_compressed_accounts: vec![InAccount { + discriminator: [1, 2, 3, 4, 5, 6, 7, 8], + data_hash: [10; 32], + merkle_context: PackedMerkleContext { + merkle_tree_pubkey_index: 1, + queue_pubkey_index: 2, + leaf_index: 3, + prove_by_index: false, + }, + root_index: 3, + lamports: 1000, + address: Some([30; 32]), + }], + output_compressed_accounts: vec![OutputCompressedAccountWithPackedContext { + compressed_account: CompressedAccount { + owner: Pubkey::default(), + lamports: 2000, + address: Some([40; 32]), + data: Some(CompressedAccountData { + discriminator: [3, 4, 5, 6, 7, 8, 9, 10], + data: vec![], + data_hash: [50; 32], + }), + }, + merkle_tree_index: 3, + }], + read_only_addresses: vec![PackedReadOnlyAddress { + address: [70; 32], + address_merkle_tree_account_index: 4, + address_merkle_tree_root_index: 5, + }], + read_only_accounts: vec![PackedReadOnlyCompressedAccount { + account_hash: [80; 32], + merkle_context: PackedMerkleContext { + merkle_tree_pubkey_index: 5, + queue_pubkey_index: 6, + leaf_index: 7, + prove_by_index: false, + }, + root_index: 8, + }], + }; + let bytes = borsh_struct.try_to_vec().unwrap(); + + let (zero_copy, _) = InstructionDataInvokeCpiWithReadOnly::zero_copy_at(&bytes).unwrap(); + + assert_eq!(zero_copy, borsh_struct); + } /// Compare the original struct with its zero-copy counterpart fn compare_invoke_cpi_with_readonly( diff --git a/program-libs/compressed-account/src/instruction_data/with_readonly.rs.tmp b/program-libs/compressed-account/src/instruction_data/with_readonly.rs.tmp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/program-libs/compressed-account/src/instruction_data/zero_copy.rs b/program-libs/compressed-account/src/instruction_data/zero_copy.rs index e4a9b210d8..e024f26b18 100644 --- a/program-libs/compressed-account/src/instruction_data/zero_copy.rs +++ b/program-libs/compressed-account/src/instruction_data/zero_copy.rs @@ -1,4 +1,4 @@ -use std::{mem::size_of, ops::Deref}; +use core::{mem::size_of, ops::Deref}; use light_zero_copy::{errors::ZeroCopyError, slice::ZeroCopySliceBorsh, traits::ZeroCopyAt}; use zerocopy::{ @@ -20,7 +20,7 @@ use crate::{ data::OutputCompressedAccountWithPackedContext, }, pubkey::Pubkey, - CompressedAccountError, + CompressedAccountError, Vec, }; #[repr(C)] @@ -225,7 +225,11 @@ impl<'a> ZeroCopyAt<'a> for ZCompressedAccountData<'a> { ) -> Result<(ZCompressedAccountData<'a>, &'a [u8]), ZeroCopyError> { let (discriminator, bytes) = Ref::<&'a [u8], [u8; 8]>::from_prefix(bytes)?; let (len, bytes) = Ref::<&'a [u8], U32>::from_prefix(bytes)?; - let (data, bytes) = bytes.split_at(u64::from(*len) as usize); + let data_len = u64::from(*len) as usize; + if bytes.len() < data_len { + return Err(ZeroCopyError::InvalidConversion); + } + let (data, bytes) = bytes.split_at(data_len); let (data_hash, bytes) = Ref::<&'a [u8], [u8; 32]>::from_prefix(bytes)?; Ok(( @@ -753,7 +757,7 @@ impl From<&ZInstructionDataInvokeCpi<'_>> for InstructionDataInvokeCpi { fn from(data: &ZInstructionDataInvokeCpi<'_>) -> Self { Self { proof: None, - new_address_params: vec![], + new_address_params: crate::vec![], input_compressed_accounts_with_merkle_context: data .input_compressed_accounts_with_merkle_context .iter() diff --git a/program-libs/compressed-account/src/lib.rs b/program-libs/compressed-account/src/lib.rs index 807aa73f14..f30a29d165 100644 --- a/program-libs/compressed-account/src/lib.rs +++ b/program-libs/compressed-account/src/lib.rs @@ -1,6 +1,14 @@ #![allow(unexpected_cfgs)] +#![cfg_attr(not(feature = "std"), no_std)] -use std::fmt::Display; +#[cfg(not(feature = "std"))] +extern crate alloc; + +#[cfg(not(feature = "std"))] +pub use alloc::{vec, vec::Vec}; +use core::fmt::Display; +#[cfg(feature = "std")] +pub use std::{vec, vec::Vec}; use light_hasher::HasherError; use thiserror::Error; @@ -10,22 +18,15 @@ pub mod compressed_account; pub mod constants; pub mod discriminators; pub use light_hasher::hash_chain; -#[cfg(feature = "poseidon")] -pub mod indexer_event; pub mod instruction_data; pub mod nullifier; pub mod pubkey; pub mod tx_hash; - -// Re-export Pubkey type -#[cfg(feature = "anchor")] -use anchor_lang::{AnchorDeserialize, AnchorSerialize}; -#[cfg(not(feature = "anchor"))] -use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize}; pub use instruction_data::traits::{InstructionDiscriminator, LightInstructionData}; -pub use light_hasher::{ - bigint::bigint_to_be_bytes_array, - hash_to_field_size::{hash_to_bn254_field_size_be, hashv_to_bn254_field_size_be}, +pub use light_hasher::bigint::bigint_to_be_bytes_array; +#[cfg(feature = "alloc")] +pub use light_hasher::hash_to_field_size::{ + hash_to_bn254_field_size_be, hashv_to_bn254_field_size_be, }; pub use pubkey::Pubkey; @@ -37,9 +38,9 @@ pub enum CompressedAccountError { InvalidChunkSize, #[error("Invalid seeds")] InvalidSeeds, - #[error("Invalid rollover thresold")] + #[error("Invalid rollover threshold")] InvalidRolloverThreshold, - #[error("Invalid input lenght")] + #[error("Invalid input length")] InvalidInputLength, #[error("Hasher error {0}")] HasherError(#[from] HasherError), @@ -127,7 +128,15 @@ pub const INPUT_STATE_QUEUE_TYPE_V2: u64 = 3; pub const ADDRESS_QUEUE_TYPE_V2: u64 = 4; pub const OUTPUT_STATE_QUEUE_TYPE_V2: u64 = 5; -#[derive(AnchorDeserialize, AnchorSerialize, Debug, PartialEq, Clone, Copy)] +#[cfg_attr( + all(feature = "std", feature = "anchor"), + derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize) +)] +#[cfg_attr( + not(feature = "anchor"), + derive(borsh::BorshDeserialize, borsh::BorshSerialize) +)] +#[derive(Debug, PartialEq, Clone, Copy)] #[repr(u64)] pub enum QueueType { NullifierV1 = NULLIFIER_QUEUE_TYPE_V1, @@ -155,10 +164,16 @@ pub const ADDRESS_MERKLE_TREE_TYPE_V1: u64 = 2; pub const STATE_MERKLE_TREE_TYPE_V2: u64 = 3; pub const ADDRESS_MERKLE_TREE_TYPE_V2: u64 = 4; -#[repr(u64)] -#[derive( - Debug, Ord, PartialEq, PartialOrd, Eq, Clone, Copy, AnchorSerialize, AnchorDeserialize, +#[cfg_attr( + all(feature = "std", feature = "anchor"), + derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize) )] +#[cfg_attr( + not(feature = "anchor"), + derive(borsh::BorshDeserialize, borsh::BorshSerialize) +)] +#[derive(Debug, Ord, PartialEq, PartialOrd, Eq, Clone, Copy)] +#[repr(u64)] pub enum TreeType { StateV1 = STATE_MERKLE_TREE_TYPE_V1, AddressV1 = ADDRESS_MERKLE_TREE_TYPE_V1, @@ -167,7 +182,7 @@ pub enum TreeType { } impl Display for TreeType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { TreeType::StateV1 => write!(f, "StateV1"), TreeType::AddressV1 => write!(f, "AddressV1"), @@ -178,7 +193,7 @@ impl Display for TreeType { } #[allow(clippy::derivable_impls)] -impl std::default::Default for TreeType { +impl core::default::Default for TreeType { fn default() -> Self { TreeType::StateV2 } @@ -196,3 +211,11 @@ impl From for TreeType { } } } + +/// Configuration struct containing program ID, CPI signer, and bump for Light Protocol +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct CpiSigner { + pub program_id: [u8; 32], + pub cpi_signer: [u8; 32], + pub bump: u8, +} diff --git a/program-libs/compressed-account/src/pubkey.rs b/program-libs/compressed-account/src/pubkey.rs index 7a6d3cc99b..3d10d7a60c 100644 --- a/program-libs/compressed-account/src/pubkey.rs +++ b/program-libs/compressed-account/src/pubkey.rs @@ -7,8 +7,15 @@ use light_zero_copy::{ }; use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Ref, Unaligned}; -use crate::{AnchorDeserialize, AnchorSerialize}; #[cfg(feature = "bytemuck-des")] +#[cfg_attr( + all(feature = "std", feature = "anchor"), + derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize) +)] +#[cfg_attr( + not(feature = "anchor"), + derive(borsh::BorshDeserialize, borsh::BorshSerialize) +)] #[derive( Pod, Zeroable, @@ -22,8 +29,6 @@ use crate::{AnchorDeserialize, AnchorSerialize}; FromBytes, IntoBytes, KnownLayout, - AnchorDeserialize, - AnchorSerialize, Default, Unaligned, )] @@ -31,6 +36,14 @@ use crate::{AnchorDeserialize, AnchorSerialize}; pub struct Pubkey(pub(crate) [u8; 32]); #[cfg(not(feature = "bytemuck-des"))] +#[cfg_attr( + all(feature = "std", feature = "anchor"), + derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize) +)] +#[cfg_attr( + not(feature = "anchor"), + derive(borsh::BorshDeserialize, borsh::BorshSerialize) +)] #[derive( Debug, Copy, @@ -42,8 +55,6 @@ pub struct Pubkey(pub(crate) [u8; 32]); FromBytes, IntoBytes, KnownLayout, - AnchorDeserialize, - AnchorSerialize, Default, Unaligned, )] diff --git a/program-libs/compressible/Cargo.toml b/program-libs/compressible/Cargo.toml index 0e895451d1..a15e9190a9 100644 --- a/program-libs/compressible/Cargo.toml +++ b/program-libs/compressible/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [features] default = ["pinocchio", "solana"] solana = ["dep:solana-program-error", "light-compressed-account/solana", "solana-sysvar"] -anchor = ["anchor-lang", "light-compressed-account/anchor"] +anchor = ["anchor-lang", "light-compressed-account/anchor", "light-compressed-account/std"] pinocchio = ["light-compressed-account/pinocchio"] profile-program = [] profile-heap = ["dep:light-heap"] diff --git a/program-libs/concurrent-merkle-tree/Cargo.toml b/program-libs/concurrent-merkle-tree/Cargo.toml index 7c12401f08..12e720e424 100644 --- a/program-libs/concurrent-merkle-tree/Cargo.toml +++ b/program-libs/concurrent-merkle-tree/Cargo.toml @@ -25,6 +25,7 @@ ark-bn254 = { workspace = true } ark-ff = { workspace = true } light-merkle-tree-reference = { workspace = true } light-hash-set = { workspace = true } +light-hasher = { workspace = true, features = ["keccak", "sha256", "poseidon"] } rand = { workspace = true } # spl-concurrent-merkle-tree = { version = "0.2.0", default-features = false } tokio = { workspace = true } diff --git a/program-libs/ctoken-types/Cargo.toml b/program-libs/ctoken-types/Cargo.toml index fb388ba42c..a512613c06 100644 --- a/program-libs/ctoken-types/Cargo.toml +++ b/program-libs/ctoken-types/Cargo.toml @@ -18,7 +18,7 @@ solana-pubkey = { workspace = true } solana-account-info = { workspace = true } solana-program-error = { workspace = true, optional = true } light-zero-copy = { workspace = true, features = ["derive", "mut"] } -light-compressed-account = { workspace = true } +light-compressed-account = { workspace = true, features = ["std"] } light-hasher = { workspace = true } light-array-map = { workspace = true } tinyvec = { workspace = true } @@ -45,8 +45,12 @@ light-compressed-account = { workspace = true, features = ["new-unique"] } light-account-checks = { workspace = true, features = [ "test-only", "pinocchio", + "std", + "solana", ] } spl-token-metadata-interface = "0.6.0" +light-ctoken-types = { workspace = true, features = ["poseidon"] } +light-hasher = { workspace = true, features = ["keccak", "sha256"] } [lints.rust.unexpected_cfgs] level = "allow" diff --git a/program-libs/hash-set/Cargo.toml b/program-libs/hash-set/Cargo.toml index 099049b641..fe2f5f53c2 100644 --- a/program-libs/hash-set/Cargo.toml +++ b/program-libs/hash-set/Cargo.toml @@ -19,4 +19,5 @@ light-hasher = { workspace = true } [dev-dependencies] ark-bn254 = { workspace = true } ark-ff = { workspace = true } +light-hasher = { workspace = true, features = ["keccak", "sha256", "poseidon"] } rand = { workspace = true } diff --git a/program-libs/hasher/Cargo.toml b/program-libs/hasher/Cargo.toml index 10a7256043..45c122e715 100644 --- a/program-libs/hasher/Cargo.toml +++ b/program-libs/hasher/Cargo.toml @@ -7,31 +7,33 @@ license = "Apache-2.0" edition = "2021" [features] -default = [] -solana = ["solana-program-error", "solana-pubkey"] +default = ["std"] +alloc = ["dep:borsh"] +std = ["alloc"] +solana = ["solana-program-error"] pinocchio = ["dep:pinocchio"] zero-copy = ["dep:zerocopy"] poseidon = ["dep:light-poseidon", "dep:ark-bn254", "dep:ark-ff"] +sha256 = ["dep:sha2"] +keccak = ["dep:sha3"] [dependencies] light-poseidon = { workspace = true, optional = true } thiserror = { workspace = true } -arrayvec = { workspace = true } +tinyvec = { workspace = true } num-bigint = { workspace = true } # Optional import for ProgramError conversion solana-program-error = { workspace = true, optional = true } -solana-pubkey = { workspace = true, optional = true } pinocchio = { workspace = true, optional = true } zerocopy = { workspace = true, optional = true } -borsh = { workspace = true } -solana-nostd-keccak = "0.1.3" +borsh = { workspace = true, optional = true } [target.'cfg(not(target_os = "solana"))'.dependencies] ark-bn254 = { workspace = true, optional = true } -sha2 = "0.10" -sha3 = "0.10" +sha2 = { version = "0.10", optional = true } +sha3 = { version = "0.10", optional = true } ark-ff = { workspace = true, optional = true } [lints.rust.unexpected_cfgs] @@ -45,3 +47,5 @@ check-cfg = [ rand = { workspace = true } num-bigint = { workspace = true, features = ["rand"] } light-poseidon = { workspace = true } +solana-pubkey = { workspace = true } +light-hasher = { workspace = true, features = ["poseidon", "std", "keccak"] } diff --git a/program-libs/hasher/src/bigint.rs b/program-libs/hasher/src/bigint.rs index d553623a3a..43af5f9974 100644 --- a/program-libs/hasher/src/bigint.rs +++ b/program-libs/hasher/src/bigint.rs @@ -34,387 +34,3 @@ pub fn bigint_to_be_bytes_array( array[start_pos..].copy_from_slice(bytes.as_slice()); Ok(array) } - -#[cfg(test)] -mod test { - use num_bigint::{RandBigInt, ToBigUint}; - use rand::thread_rng; - - use super::*; - - const ITERATIONS: usize = 64; - - #[test] - fn test_bigint_conversion_rand() { - let mut rng = thread_rng(); - - for _ in 0..ITERATIONS { - let b64 = rng.gen_biguint(32); - let b64_converted: [u8; 8] = bigint_to_be_bytes_array(&b64).unwrap(); - let b64_converted = BigUint::from_bytes_be(&b64_converted); - assert_eq!(b64, b64_converted); - let b64_converted: [u8; 8] = bigint_to_le_bytes_array(&b64).unwrap(); - let b64_converted = BigUint::from_bytes_le(&b64_converted); - assert_eq!(b64, b64_converted); - - let b128 = rng.gen_biguint(128); - let b128_converted: [u8; 16] = bigint_to_be_bytes_array(&b128).unwrap(); - let b128_converted = BigUint::from_bytes_be(&b128_converted); - assert_eq!(b128, b128_converted); - let b128_converted: [u8; 16] = bigint_to_le_bytes_array(&b128).unwrap(); - let b128_converted = BigUint::from_bytes_le(&b128_converted); - assert_eq!(b128, b128_converted); - - let b256 = rng.gen_biguint(256); - let b256_converted: [u8; 32] = bigint_to_be_bytes_array(&b256).unwrap(); - let b256_converted = BigUint::from_bytes_be(&b256_converted); - assert_eq!(b256, b256_converted); - let b256_converted: [u8; 32] = bigint_to_le_bytes_array(&b256).unwrap(); - let b256_converted = BigUint::from_bytes_le(&b256_converted); - assert_eq!(b256, b256_converted); - - let b320 = rng.gen_biguint(320); - let b320_converted: [u8; 40] = bigint_to_be_bytes_array(&b320).unwrap(); - let b320_converted = BigUint::from_bytes_be(&b320_converted); - assert_eq!(b320, b320_converted); - let b320_converted: [u8; 40] = bigint_to_le_bytes_array(&b320).unwrap(); - let b320_converted = BigUint::from_bytes_le(&b320_converted); - assert_eq!(b320, b320_converted); - - let b384 = rng.gen_biguint(384); - let b384_converted: [u8; 48] = bigint_to_be_bytes_array(&b384).unwrap(); - let b384_converted = BigUint::from_bytes_be(&b384_converted); - assert_eq!(b384, b384_converted); - let b384_converted: [u8; 48] = bigint_to_le_bytes_array(&b384).unwrap(); - let b384_converted = BigUint::from_bytes_le(&b384_converted); - assert_eq!(b384, b384_converted); - - let b448 = rng.gen_biguint(448); - let b448_converted: [u8; 56] = bigint_to_be_bytes_array(&b448).unwrap(); - let b448_converted = BigUint::from_bytes_be(&b448_converted); - assert_eq!(b448, b448_converted); - let b448_converted: [u8; 56] = bigint_to_le_bytes_array(&b448).unwrap(); - let b448_converted = BigUint::from_bytes_le(&b448_converted); - assert_eq!(b448, b448_converted); - - let b768 = rng.gen_biguint(768); - let b768_converted: [u8; 96] = bigint_to_be_bytes_array(&b768).unwrap(); - let b768_converted = BigUint::from_bytes_be(&b768_converted); - assert_eq!(b768, b768_converted); - let b768_converted: [u8; 96] = bigint_to_le_bytes_array(&b768).unwrap(); - let b768_converted = BigUint::from_bytes_le(&b768_converted); - assert_eq!(b768, b768_converted); - - let b832 = rng.gen_biguint(832); - let b832_converted: [u8; 104] = bigint_to_be_bytes_array(&b832).unwrap(); - let b832_converted = BigUint::from_bytes_be(&b832_converted); - assert_eq!(b832, b832_converted); - let b832_converted: [u8; 104] = bigint_to_le_bytes_array(&b832).unwrap(); - let b832_converted = BigUint::from_bytes_le(&b832_converted); - assert_eq!(b832, b832_converted); - } - } - - #[test] - fn test_bigint_conversion_zero() { - let zero = 0_u32.to_biguint().unwrap(); - - let b64_converted: [u8; 8] = bigint_to_be_bytes_array(&zero).unwrap(); - let b64_converted = BigUint::from_bytes_be(&b64_converted); - assert_eq!(zero, b64_converted); - let b64_converted: [u8; 8] = bigint_to_le_bytes_array(&zero).unwrap(); - let b64_converted = BigUint::from_bytes_le(&b64_converted); - assert_eq!(zero, b64_converted); - - let b128_converted: [u8; 16] = bigint_to_be_bytes_array(&zero).unwrap(); - let b128_converted = BigUint::from_bytes_be(&b128_converted); - assert_eq!(zero, b128_converted); - let b128_converted: [u8; 16] = bigint_to_le_bytes_array(&zero).unwrap(); - let b128_converted = BigUint::from_bytes_le(&b128_converted); - assert_eq!(zero, b128_converted); - - let b256_converted: [u8; 32] = bigint_to_be_bytes_array(&zero).unwrap(); - let b256_converted = BigUint::from_bytes_be(&b256_converted); - assert_eq!(zero, b256_converted); - let b256_converted: [u8; 32] = bigint_to_le_bytes_array(&zero).unwrap(); - let b256_converted = BigUint::from_bytes_le(&b256_converted); - assert_eq!(zero, b256_converted); - - let b320_converted: [u8; 40] = bigint_to_be_bytes_array(&zero).unwrap(); - let b320_converted = BigUint::from_bytes_be(&b320_converted); - assert_eq!(zero, b320_converted); - let b320_converted: [u8; 40] = bigint_to_le_bytes_array(&zero).unwrap(); - let b320_converted = BigUint::from_bytes_le(&b320_converted); - assert_eq!(zero, b320_converted); - - let b384_converted: [u8; 48] = bigint_to_be_bytes_array(&zero).unwrap(); - let b384_converted = BigUint::from_bytes_be(&b384_converted); - assert_eq!(zero, b384_converted); - let b384_converted: [u8; 48] = bigint_to_le_bytes_array(&zero).unwrap(); - let b384_converted = BigUint::from_bytes_le(&b384_converted); - assert_eq!(zero, b384_converted); - - let b448_converted: [u8; 56] = bigint_to_be_bytes_array(&zero).unwrap(); - let b448_converted = BigUint::from_bytes_be(&b448_converted); - assert_eq!(zero, b448_converted); - let b448_converted: [u8; 56] = bigint_to_le_bytes_array(&zero).unwrap(); - let b448_converted = BigUint::from_bytes_le(&b448_converted); - assert_eq!(zero, b448_converted); - - let b768_converted: [u8; 96] = bigint_to_be_bytes_array(&zero).unwrap(); - let b768_converted = BigUint::from_bytes_be(&b768_converted); - assert_eq!(zero, b768_converted); - let b768_converted: [u8; 96] = bigint_to_le_bytes_array(&zero).unwrap(); - let b768_converted = BigUint::from_bytes_le(&b768_converted); - assert_eq!(zero, b768_converted); - - let b832_converted: [u8; 104] = bigint_to_be_bytes_array(&zero).unwrap(); - let b832_converted = BigUint::from_bytes_be(&b832_converted); - assert_eq!(zero, b832_converted); - let b832_converted: [u8; 104] = bigint_to_le_bytes_array(&zero).unwrap(); - let b832_converted = BigUint::from_bytes_le(&b832_converted); - assert_eq!(zero, b832_converted); - } - - #[test] - fn test_bigint_conversion_one() { - let one = 1_u32.to_biguint().unwrap(); - - let b64_converted: [u8; 8] = bigint_to_be_bytes_array(&one).unwrap(); - let b64_converted = BigUint::from_bytes_be(&b64_converted); - assert_eq!(one, b64_converted); - let b64_converted: [u8; 8] = bigint_to_le_bytes_array(&one).unwrap(); - let b64_converted = BigUint::from_bytes_le(&b64_converted); - assert_eq!(one, b64_converted); - let b64 = BigUint::from_bytes_be(&[0, 0, 0, 0, 0, 0, 0, 1]); - assert_eq!(one, b64); - let b64 = BigUint::from_bytes_le(&[1, 0, 0, 0, 0, 0, 0, 0]); - assert_eq!(one, b64); - - let b128_converted: [u8; 16] = bigint_to_be_bytes_array(&one).unwrap(); - let b128_converted = BigUint::from_bytes_be(&b128_converted); - assert_eq!(one, b128_converted); - let b128_converted: [u8; 16] = bigint_to_le_bytes_array(&one).unwrap(); - let b128_converted = BigUint::from_bytes_le(&b128_converted); - assert_eq!(one, b128_converted); - - let b256_converted: [u8; 32] = bigint_to_be_bytes_array(&one).unwrap(); - let b256_converted = BigUint::from_bytes_be(&b256_converted); - assert_eq!(one, b256_converted); - let b256_converted: [u8; 32] = bigint_to_le_bytes_array(&one).unwrap(); - let b256_converted = BigUint::from_bytes_le(&b256_converted); - assert_eq!(one, b256_converted); - - let b320_converted: [u8; 40] = bigint_to_be_bytes_array(&one).unwrap(); - let b320_converted = BigUint::from_bytes_be(&b320_converted); - assert_eq!(one, b320_converted); - let b320_converted: [u8; 40] = bigint_to_le_bytes_array(&one).unwrap(); - let b320_converted = BigUint::from_bytes_le(&b320_converted); - assert_eq!(one, b320_converted); - - let b384_converted: [u8; 48] = bigint_to_be_bytes_array(&one).unwrap(); - let b384_converted = BigUint::from_bytes_be(&b384_converted); - assert_eq!(one, b384_converted); - let b384_converted: [u8; 48] = bigint_to_le_bytes_array(&one).unwrap(); - let b384_converted = BigUint::from_bytes_le(&b384_converted); - assert_eq!(one, b384_converted); - - let b448_converted: [u8; 56] = bigint_to_be_bytes_array(&one).unwrap(); - let b448_converted = BigUint::from_bytes_be(&b448_converted); - assert_eq!(one, b448_converted); - let b448_converted: [u8; 56] = bigint_to_le_bytes_array(&one).unwrap(); - let b448_converted = BigUint::from_bytes_le(&b448_converted); - assert_eq!(one, b448_converted); - - let b768_converted: [u8; 96] = bigint_to_be_bytes_array(&one).unwrap(); - let b768_converted = BigUint::from_bytes_be(&b768_converted); - assert_eq!(one, b768_converted); - let b768_converted: [u8; 96] = bigint_to_le_bytes_array(&one).unwrap(); - let b768_converted = BigUint::from_bytes_le(&b768_converted); - assert_eq!(one, b768_converted); - - let b832_converted: [u8; 104] = bigint_to_be_bytes_array(&one).unwrap(); - let b832_converted = BigUint::from_bytes_be(&b832_converted); - assert_eq!(one, b832_converted); - let b832_converted: [u8; 104] = bigint_to_le_bytes_array(&one).unwrap(); - let b832_converted = BigUint::from_bytes_le(&b832_converted); - assert_eq!(one, b832_converted); - } - - #[test] - fn test_bigint_conversion_invalid_size() { - let b8 = BigUint::from_bytes_be(&[1; 8]); - let res: Result<[u8; 1], HasherError> = bigint_to_be_bytes_array(&b8); - assert!(matches!(res, Err(HasherError::InvalidInputLength(1, 8)))); - let res: Result<[u8; 7], HasherError> = bigint_to_be_bytes_array(&b8); - assert!(matches!(res, Err(HasherError::InvalidInputLength(7, 8)))); - let res: Result<[u8; 8], HasherError> = bigint_to_be_bytes_array(&b8); - assert!(res.is_ok()); - - let b8 = BigUint::from_bytes_le(&[1; 8]); - let res: Result<[u8; 1], HasherError> = bigint_to_le_bytes_array(&b8); - assert!(matches!(res, Err(HasherError::InvalidInputLength(1, 8)))); - let res: Result<[u8; 7], HasherError> = bigint_to_le_bytes_array(&b8); - assert!(matches!(res, Err(HasherError::InvalidInputLength(7, 8)))); - let res: Result<[u8; 8], HasherError> = bigint_to_le_bytes_array(&b8); - assert!(res.is_ok()); - - let b16 = BigUint::from_bytes_be(&[1; 16]); - let res: Result<[u8; 1], HasherError> = bigint_to_be_bytes_array(&b16); - assert!(matches!(res, Err(HasherError::InvalidInputLength(1, 16)))); - let res: Result<[u8; 15], HasherError> = bigint_to_be_bytes_array(&b16); - assert!(matches!(res, Err(HasherError::InvalidInputLength(15, 16)))); - let res: Result<[u8; 16], HasherError> = bigint_to_be_bytes_array(&b16); - assert!(res.is_ok()); - - let b16 = BigUint::from_bytes_le(&[1; 16]); - let res: Result<[u8; 1], HasherError> = bigint_to_le_bytes_array(&b16); - assert!(matches!(res, Err(HasherError::InvalidInputLength(1, 16)))); - let res: Result<[u8; 15], HasherError> = bigint_to_le_bytes_array(&b16); - assert!(matches!(res, Err(HasherError::InvalidInputLength(15, 16)))); - let res: Result<[u8; 16], HasherError> = bigint_to_le_bytes_array(&b16); - assert!(res.is_ok()); - - let b32 = BigUint::from_bytes_be(&[1; 32]); - let res: Result<[u8; 1], HasherError> = bigint_to_be_bytes_array(&b32); - assert!(matches!(res, Err(HasherError::InvalidInputLength(1, 32)))); - let res: Result<[u8; 31], HasherError> = bigint_to_be_bytes_array(&b32); - assert!(matches!(res, Err(HasherError::InvalidInputLength(31, 32)))); - let res: Result<[u8; 32], HasherError> = bigint_to_be_bytes_array(&b32); - assert!(res.is_ok()); - - let b32 = BigUint::from_bytes_le(&[1; 32]); - let res: Result<[u8; 1], HasherError> = bigint_to_le_bytes_array(&b32); - assert!(matches!(res, Err(HasherError::InvalidInputLength(1, 32)))); - let res: Result<[u8; 31], HasherError> = bigint_to_le_bytes_array(&b32); - assert!(matches!(res, Err(HasherError::InvalidInputLength(31, 32)))); - let res: Result<[u8; 32], HasherError> = bigint_to_le_bytes_array(&b32); - assert!(res.is_ok()); - - let b64 = BigUint::from_bytes_be(&[1; 64]); - let res: Result<[u8; 1], HasherError> = bigint_to_be_bytes_array(&b64); - assert!(matches!(res, Err(HasherError::InvalidInputLength(1, 64)))); - let res: Result<[u8; 63], HasherError> = bigint_to_be_bytes_array(&b64); - assert!(matches!(res, Err(HasherError::InvalidInputLength(63, 64)))); - let res: Result<[u8; 64], HasherError> = bigint_to_be_bytes_array(&b64); - assert!(res.is_ok()); - - let b64 = BigUint::from_bytes_le(&[1; 64]); - let res: Result<[u8; 1], HasherError> = bigint_to_le_bytes_array(&b64); - assert!(matches!(res, Err(HasherError::InvalidInputLength(1, 64)))); - let res: Result<[u8; 63], HasherError> = bigint_to_le_bytes_array(&b64); - assert!(matches!(res, Err(HasherError::InvalidInputLength(63, 64)))); - let res: Result<[u8; 64], HasherError> = bigint_to_le_bytes_array(&b64); - assert!(res.is_ok()); - - let b128 = BigUint::from_bytes_be(&[1; 128]); - let res: Result<[u8; 1], HasherError> = bigint_to_be_bytes_array(&b128); - assert!(matches!(res, Err(HasherError::InvalidInputLength(1, 128)))); - let res: Result<[u8; 127], HasherError> = bigint_to_be_bytes_array(&b128); - assert!(matches!( - res, - Err(HasherError::InvalidInputLength(127, 128)) - )); - let res: Result<[u8; 128], HasherError> = bigint_to_be_bytes_array(&b128); - assert!(res.is_ok()); - - let b128 = BigUint::from_bytes_le(&[1; 128]); - let res: Result<[u8; 1], HasherError> = bigint_to_le_bytes_array(&b128); - assert!(matches!(res, Err(HasherError::InvalidInputLength(1, 128)))); - let res: Result<[u8; 127], HasherError> = bigint_to_le_bytes_array(&b128); - assert!(matches!( - res, - Err(HasherError::InvalidInputLength(127, 128)) - )); - let res: Result<[u8; 128], HasherError> = bigint_to_le_bytes_array(&b128); - assert!(res.is_ok()); - - let b256 = BigUint::from_bytes_be(&[1; 256]); - let res: Result<[u8; 1], HasherError> = bigint_to_be_bytes_array(&b256); - assert!(matches!(res, Err(HasherError::InvalidInputLength(1, 256)))); - let res: Result<[u8; 255], HasherError> = bigint_to_be_bytes_array(&b256); - assert!(matches!( - res, - Err(HasherError::InvalidInputLength(255, 256)) - )); - let res: Result<[u8; 256], HasherError> = bigint_to_be_bytes_array(&b256); - assert!(res.is_ok()); - - let b256 = BigUint::from_bytes_le(&[1; 256]); - let res: Result<[u8; 1], HasherError> = bigint_to_le_bytes_array(&b256); - assert!(matches!(res, Err(HasherError::InvalidInputLength(1, 256)))); - let res: Result<[u8; 255], HasherError> = bigint_to_le_bytes_array(&b256); - assert!(matches!( - res, - Err(HasherError::InvalidInputLength(255, 256)) - )); - let res: Result<[u8; 256], HasherError> = bigint_to_le_bytes_array(&b256); - assert!(res.is_ok()); - - let b512 = BigUint::from_bytes_be(&[1; 512]); - let res: Result<[u8; 1], HasherError> = bigint_to_be_bytes_array(&b512); - assert!(matches!(res, Err(HasherError::InvalidInputLength(1, 512)))); - let res: Result<[u8; 511], HasherError> = bigint_to_be_bytes_array(&b512); - assert!(matches!( - res, - Err(HasherError::InvalidInputLength(511, 512)) - )); - let res: Result<[u8; 512], HasherError> = bigint_to_be_bytes_array(&b512); - assert!(res.is_ok()); - - let b512 = BigUint::from_bytes_le(&[1; 512]); - let res: Result<[u8; 1], HasherError> = bigint_to_le_bytes_array(&b512); - assert!(matches!(res, Err(HasherError::InvalidInputLength(1, 512)))); - let res: Result<[u8; 511], HasherError> = bigint_to_le_bytes_array(&b512); - assert!(matches!( - res, - Err(HasherError::InvalidInputLength(511, 512)) - )); - let res: Result<[u8; 512], HasherError> = bigint_to_le_bytes_array(&b512); - assert!(res.is_ok()); - - let b768 = BigUint::from_bytes_be(&[1; 768]); - let res: Result<[u8; 1], HasherError> = bigint_to_be_bytes_array(&b768); - assert!(matches!(res, Err(HasherError::InvalidInputLength(1, 768)))); - let res: Result<[u8; 767], HasherError> = bigint_to_be_bytes_array(&b768); - assert!(matches!( - res, - Err(HasherError::InvalidInputLength(767, 768)) - )); - let res: Result<[u8; 768], HasherError> = bigint_to_be_bytes_array(&b768); - assert!(res.is_ok()); - - let b768 = BigUint::from_bytes_le(&[1; 768]); - let res: Result<[u8; 1], HasherError> = bigint_to_le_bytes_array(&b768); - assert!(matches!(res, Err(HasherError::InvalidInputLength(1, 768)))); - let res: Result<[u8; 767], HasherError> = bigint_to_le_bytes_array(&b768); - assert!(matches!( - res, - Err(HasherError::InvalidInputLength(767, 768)) - )); - let res: Result<[u8; 768], HasherError> = bigint_to_le_bytes_array(&b768); - assert!(res.is_ok()); - - let b1024 = BigUint::from_bytes_be(&[1; 1024]); - let res: Result<[u8; 1], HasherError> = bigint_to_be_bytes_array(&b1024); - assert!(matches!(res, Err(HasherError::InvalidInputLength(1, 1024)))); - let res: Result<[u8; 1023], HasherError> = bigint_to_be_bytes_array(&b1024); - assert!(matches!( - res, - Err(HasherError::InvalidInputLength(1023, 1024)) - )); - let res: Result<[u8; 1024], HasherError> = bigint_to_be_bytes_array(&b1024); - assert!(res.is_ok()); - - let b1024 = BigUint::from_bytes_le(&[1; 1024]); - let res: Result<[u8; 1], HasherError> = bigint_to_le_bytes_array(&b1024); - assert!(matches!(res, Err(HasherError::InvalidInputLength(1, 1024)))); - let res: Result<[u8; 1023], HasherError> = bigint_to_le_bytes_array(&b1024); - assert!(matches!( - res, - Err(HasherError::InvalidInputLength(1023, 1024)) - )); - let res: Result<[u8; 1024], HasherError> = bigint_to_le_bytes_array(&b1024); - assert!(res.is_ok()); - } -} diff --git a/program-libs/hasher/src/data_hasher.rs b/program-libs/hasher/src/data_hasher.rs index 8bad90908e..ce09cbf455 100644 --- a/program-libs/hasher/src/data_hasher.rs +++ b/program-libs/hasher/src/data_hasher.rs @@ -56,146 +56,3 @@ impl_data_hasher_for_array! { impl_data_hasher_for_array! { 12 => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] } - -#[cfg(test)] -mod tests { - use super::*; - use crate::{Hasher, Poseidon}; - - // A simple test struct that implements DataHasher - #[derive(Default, Clone)] - struct TestHashable { - value: u8, - } - - impl TestHashable { - fn new(value: u8) -> Self { - Self { value } - } - } - - impl DataHasher for TestHashable { - fn hash(&self) -> Result<[u8; 32], HasherError> { - // Simple implementation that creates a predictable hash - let mut result = [0u8; 32]; - result[31] = self.value; - Ok(result) - } - } - - #[test] - fn test_data_hasher_array_1() { - let arr = [TestHashable::new(42)]; - let hash_result = arr.hash::().unwrap(); - - // The result should be the Poseidon hash of the single element's hash - let expected_input = [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 42, - ]; - let expected_hash = Poseidon::hash(&expected_input).unwrap(); - - assert_eq!(hash_result, expected_hash); - } - - #[test] - fn test_data_hasher_array_2() { - let arr = [TestHashable::new(1), TestHashable::new(2)]; - let hash_result = arr.hash::().unwrap(); - - // Expected inputs are the hashes of each TestHashable - let hash1 = [0u8; 32]; - let hash2 = [0u8; 32]; - - let mut hash1 = hash1; - hash1[31] = 1; - - let mut hash2 = hash2; - hash2[31] = 2; - - // The result should be the Poseidon hash of concatenated element hashes - let expected_hash = Poseidon::hashv(&[&hash1, &hash2]).unwrap(); - - assert_eq!(hash_result, expected_hash); - } - - #[test] - fn test_data_hasher_array_multiple_sizes() { - // Test arrays of each implemented size - for size in 1..=12 { - let mut array = Vec::with_capacity(size); - for i in 0..size { - array.push(TestHashable::new(i as u8)); - } - - // Convert the Vec to an array of the appropriate size - let array_slice = array.as_slice(); - - // Create expected inputs (hashes of each TestHashable) - let mut expected_inputs = Vec::with_capacity(size); - for i in 0..size { - let mut hash = [0u8; 32]; - hash[31] = i as u8; - expected_inputs.push(hash); - } - - // Dynamically test each array size - match size { - 1 => { - let arr: [TestHashable; 1] = [array_slice[0].clone()]; - let hash_result = arr.hash::().unwrap(); - - let expected_slices: Vec<&[u8]> = - expected_inputs.iter().map(|h| h.as_slice()).collect(); - let expected_hash = Poseidon::hashv(&expected_slices).unwrap(); - - assert_eq!(hash_result, expected_hash); - } - 2 => { - let arr: [TestHashable; 2] = [array_slice[0].clone(), array_slice[1].clone()]; - let hash_result = arr.hash::().unwrap(); - - let expected_slices: Vec<&[u8]> = - expected_inputs.iter().map(|h| h.as_slice()).collect(); - let expected_hash = Poseidon::hashv(&expected_slices).unwrap(); - - assert_eq!(hash_result, expected_hash); - } - 3 => { - let arr: [TestHashable; 3] = [ - array_slice[0].clone(), - array_slice[1].clone(), - array_slice[2].clone(), - ]; - let hash_result = arr.hash::().unwrap(); - - let expected_slices: Vec<&[u8]> = - expected_inputs.iter().map(|h| h.as_slice()).collect(); - let expected_hash = Poseidon::hashv(&expected_slices).unwrap(); - - assert_eq!(hash_result, expected_hash); - } - // We test one more size (4) to confirm the pattern works - 4 => { - let arr: [TestHashable; 4] = [ - array_slice[0].clone(), - array_slice[1].clone(), - array_slice[2].clone(), - array_slice[3].clone(), - ]; - let hash_result = arr.hash::().unwrap(); - - let expected_slices: Vec<&[u8]> = - expected_inputs.iter().map(|h| h.as_slice()).collect(); - let expected_hash = Poseidon::hashv(&expected_slices).unwrap(); - - assert_eq!(hash_result, expected_hash); - } - _ => { - // For sizes 5-12, we've verified the pattern with tests for sizes 1-4 - // We could add more tests here if needed - } - } - } - } -} diff --git a/program-libs/hasher/src/errors.rs b/program-libs/hasher/src/errors.rs index 050fac1f12..ce8bf5f31a 100644 --- a/program-libs/hasher/src/errors.rs +++ b/program-libs/hasher/src/errors.rs @@ -29,6 +29,10 @@ pub enum HasherError { OptionHashToFieldSizeZero, #[error("Poseidon feature is not enabled. Without feature poseidon only syscalls are accessible in target os solana")] PoseidonFeatureNotEnabled, + #[error("SHA256 feature is not enabled. Enable the sha256 feature to use SHA256 hashing in non-Solana targets")] + Sha256FeatureNotEnabled, + #[error("Keccak feature is not enabled. Enable the keccak feature to use Keccak hashing in non-Solana targets")] + KeccakFeatureNotEnabled, } // NOTE(vadorovsky): Unfortunately, we need to do it by hand. `num_derive::ToPrimitive` @@ -47,6 +51,8 @@ impl From for u32 { HasherError::BorshError => 7008, HasherError::OptionHashToFieldSizeZero => 7009, HasherError::PoseidonFeatureNotEnabled => 7010, + HasherError::Sha256FeatureNotEnabled => 7011, + HasherError::KeccakFeatureNotEnabled => 7012, } } } diff --git a/program-libs/hasher/src/hash_chain.rs b/program-libs/hasher/src/hash_chain.rs index b1ea9fd997..22ca4be6e8 100644 --- a/program-libs/hasher/src/hash_chain.rs +++ b/program-libs/hasher/src/hash_chain.rs @@ -68,186 +68,3 @@ pub fn create_two_inputs_hash_chain( } Ok(hash_chain) } - -#[cfg(test)] -mod hash_chain_tests { - - use super::*; - use crate::{Hasher, HasherError, Poseidon}; - - /// Tests for `create_hash_chain_from_slice` function: - /// Functional tests: - /// 1. Functional - with hardcoded values. - /// 2. Functional - with manual hash comparison. - /// 3. Functional - for determinism (hashing the same input twice). - /// 4. Functional - empty input case returns zero hash. - /// - /// Failing tests: - /// 5. Failing - input larger than modulus - #[test] - fn test_create_hash_chain_from_slice() { - // 1. Functional test with hardcoded values. - { - // Define hardcoded inputs. - let inputs: [[u8; 32]; 3] = [[1u8; 32], [2u8; 32], [3u8; 32]]; - - // Manually compute the expected hash chain using Poseidon. - // Note: The expected hash values should be precomputed using the same Poseidon parameters. - // For demonstration purposes, we'll assume hypothetical hash outputs. - // In a real scenario, replace these with actual expected values. - let intermediate_hash_1 = Poseidon::hashv(&[&inputs[0], &inputs[1]]).unwrap(); - let expected_hash = Poseidon::hashv(&[&intermediate_hash_1, &inputs[2]]).unwrap(); - - // Call the function under test. - let result = create_hash_chain_from_slice(&inputs).unwrap(); - - // Assert that the result matches the expected hash. - assert_eq!( - result, expected_hash, - "Functional test with hardcoded values failed." - ); - } - - // 2. Functional test with manual hash comparison. - { - let inputs: [[u8; 32]; 2] = [[4u8; 32], [5u8; 32]]; - - // Manually compute the expected hash. - let expected_hash = Poseidon::hashv(&[&inputs[0], &inputs[1]]).unwrap(); - let hard_coded_expected_hash = [ - 13, 250, 206, 124, 182, 159, 160, 87, 57, 23, 80, 155, 25, 43, 40, 136, 228, 255, - 201, 1, 22, 168, 211, 220, 176, 187, 23, 176, 46, 198, 140, 211, - ]; - - let result = create_hash_chain_from_slice(&inputs).unwrap(); - - assert_eq!( - result, expected_hash, - "Functional test with manual hash comparison failed." - ); - assert_eq!(result, hard_coded_expected_hash); - } - - // 2. Functional test with manual hash comparison. - { - let inputs = [[4u8; 32], [5u8; 32], [6u8; 32]]; - - let expected_hash = Poseidon::hashv(&[&inputs[0], &inputs[1]]).unwrap(); - let expected_hash = Poseidon::hashv(&[&expected_hash, &inputs[2]]).unwrap(); - let hard_coded_expected_hash = [ - 12, 74, 32, 81, 132, 82, 10, 115, 75, 248, 169, 125, 228, 230, 140, 167, 149, 181, - 244, 194, 63, 201, 26, 150, 142, 4, 60, 16, 77, 145, 194, 152, - ]; - - let result = create_hash_chain_from_slice(&inputs).unwrap(); - - assert_eq!( - result, expected_hash, - "Functional test with manual hash comparison failed." - ); - assert_eq!(result, hard_coded_expected_hash); - } - - // 3. Functional test for determinism (hashing the same input twice). - { - // Define inputs. - let inputs: [[u8; 32]; 2] = [[6u8; 32], [7u8; 32]]; - - // Compute hash chain the first time. - let first_hash = create_hash_chain_from_slice(&inputs).unwrap(); - - // Compute hash chain the second time. - let second_hash = create_hash_chain_from_slice(&inputs).unwrap(); - - // Assert that both hashes are identical. - assert_eq!( - first_hash, second_hash, - "Determinism test failed: Hashes do not match." - ); - } - - // 4. Test empty input case - { - let inputs: [[u8; 32]; 0] = []; - let result = create_hash_chain_from_slice(&inputs).unwrap(); - assert_eq!(result, [0u8; 32], "Empty input should return zero hash"); - } - // 5. Failing - input larger than modulus - #[cfg(feature = "poseidon")] - { - use ark_ff::PrimeField; - use light_poseidon::PoseidonError; - use num_bigint::BigUint; - - use crate::bigint::bigint_to_be_bytes_array; - let modulus: BigUint = ark_bn254::Fr::MODULUS.into(); - let modulus_bytes: [u8; 32] = bigint_to_be_bytes_array(&modulus).unwrap(); - let huge_input = vec![modulus_bytes, modulus_bytes]; - let result = create_hash_chain_from_slice(&huge_input); - assert!( - matches!(result, Err(HasherError::Poseidon(error)) if error == PoseidonError::InputLargerThanModulus), - ); - } - } - - /// Tests for `create_two_inputs_hash_chain` function: - /// 1. Functional - empty inputs. - /// 2. Functional - 1 input each. - /// 3. Functional - 2 inputs each. - /// 4. Failing - invalid input length for hashes_first. - /// 5. Failing - invalid input length for hashes_second. - #[test] - fn test_create_two_inputs_hash_chain() { - // 1. Functional test with empty inputs. - { - let hashes_first: &[[u8; 32]] = &[]; - let hashes_second: &[[u8; 32]] = &[]; - let result = create_two_inputs_hash_chain(hashes_first, hashes_second).unwrap(); - assert_eq!(result, [0u8; 32], "Empty input should return zero hash"); - } - - // 2. Functional test with 1 input each. - { - let hashes_first: &[[u8; 32]] = &[[1u8; 32]]; - let hashes_second: &[[u8; 32]] = &[[2u8; 32]]; - let expected_hash = Poseidon::hashv(&[&hashes_first[0], &hashes_second[0]]).unwrap(); - let result = create_two_inputs_hash_chain(hashes_first, hashes_second).unwrap(); - assert_eq!(result, expected_hash, "Single input each test failed"); - } - - // 3. Functional test with 2 inputs each. - { - let hashes_first: &[[u8; 32]] = &[[1u8; 32], [2u8; 32]]; - let hashes_second: &[[u8; 32]] = &[[3u8; 32], [4u8; 32]]; - let intermediate_hash = - Poseidon::hashv(&[&hashes_first[0], &hashes_second[0]]).unwrap(); - let expected_hash = - Poseidon::hashv(&[&intermediate_hash, &hashes_first[1], &hashes_second[1]]) - .unwrap(); - let result = create_two_inputs_hash_chain(hashes_first, hashes_second).unwrap(); - assert_eq!(result, expected_hash, "Two inputs each test failed"); - } - - // 4. Failing test with invalid input length for hashes_first. - { - let hashes_first: &[[u8; 32]] = &[[1u8; 32]]; - let hashes_second: &[[u8; 32]] = &[[2u8; 32], [3u8; 32]]; - let result = create_two_inputs_hash_chain(hashes_first, hashes_second); - assert!( - matches!(result, Err(HasherError::InvalidInputLength(1, 2))), - "Invalid input length for hashes_first test failed" - ); - } - - // 5. Failing test with invalid input length for hashes_second. - { - let hashes_first: &[[u8; 32]] = &[[1u8; 32], [2u8; 32]]; - let hashes_second: &[[u8; 32]] = &[[3u8; 32]]; - let result = create_two_inputs_hash_chain(hashes_first, hashes_second); - assert!( - matches!(result, Err(HasherError::InvalidInputLength(2, 1))), - "Invalid input length for hashes_second test failed" - ); - } - } -} diff --git a/program-libs/hasher/src/hash_to_field_size.rs b/program-libs/hasher/src/hash_to_field_size.rs index 08c203ac4e..9099a97ea2 100644 --- a/program-libs/hasher/src/hash_to_field_size.rs +++ b/program-libs/hasher/src/hash_to_field_size.rs @@ -1,6 +1,9 @@ -use arrayvec::ArrayVec; +#[cfg(feature = "alloc")] use borsh::BorshSerialize; +use tinyvec::ArrayVec; +#[cfg(feature = "alloc")] +use crate::Vec; use crate::{keccak::Keccak, Hasher, HasherError}; pub const HASH_TO_FIELD_SIZE_SEED: u8 = u8::MAX; @@ -9,13 +12,14 @@ pub trait HashToFieldSize { fn hash_to_field_size(&self) -> Result<[u8; 32], HasherError>; } +#[cfg(feature = "alloc")] impl HashToFieldSize for T where T: BorshSerialize, { fn hash_to_field_size(&self) -> Result<[u8; 32], HasherError> { let borsh_vec = self.try_to_vec().map_err(|_| HasherError::BorshError)?; - #[cfg(debug_assertions)] + #[cfg(all(debug_assertions, feature = "std"))] { if std::env::var("RUST_BACKTRACE").is_ok() { println!( @@ -26,8 +30,8 @@ where } let bump_seed = [HASH_TO_FIELD_SIZE_SEED]; let slices = [borsh_vec.as_slice(), bump_seed.as_slice()]; - // SAFETY: cannot panic Hasher::hashv returns an error because Poseidon can panic. - let mut hashed_value = Keccak::hashv(slices.as_slice()).unwrap(); + // Keccak::hashv is fallible (trait-unified), propagate errors instead of panicking. + let mut hashed_value = Keccak::hashv(slices.as_slice())?; // Truncates to 31 bytes so that value is less than bn254 Fr modulo // field size. hashed_value[0] = 0; @@ -35,6 +39,7 @@ where } } +#[cfg(feature = "alloc")] pub fn hashv_to_bn254_field_size_be(bytes: &[&[u8]]) -> [u8; 32] { let mut slices = Vec::with_capacity(bytes.len() + 1); bytes.iter().for_each(|x| slices.push(*x)); @@ -48,6 +53,7 @@ pub fn hashv_to_bn254_field_size_be(bytes: &[&[u8]]) -> [u8; 32] { hashed_value } +#[cfg(feature = "alloc")] pub fn hashv_to_bn254_field_size_be_array(bytes: &[[u8; 32]]) -> [u8; 32] { let mut slices = Vec::with_capacity(bytes.len() + 1); bytes.iter().for_each(|x| slices.push(x.as_slice())); @@ -65,11 +71,11 @@ pub fn hashv_to_bn254_field_size_be_const_array( bytes: &[&[u8]], ) -> Result<[u8; 32], HasherError> { let bump_seed = [HASH_TO_FIELD_SIZE_SEED]; - let mut slices = ArrayVec::<&[u8], MAX_SLICES>::new(); + let mut slices = ArrayVec::<[&[u8]; MAX_SLICES]>::new(); if bytes.len() > MAX_SLICES - 1 { return Err(HasherError::InvalidInputLength(MAX_SLICES, bytes.len())); } - bytes.iter().for_each(|x| slices.push(x)); + bytes.iter().for_each(|x| slices.push(*x)); slices.push(bump_seed.as_slice()); let mut hashed_value: [u8; 32] = Keccak::hashv(&slices)?; // Truncates to 31 bytes so that value is less than bn254 Fr modulo @@ -84,9 +90,11 @@ pub fn hashv_to_bn254_field_size_be_const_array( /// # Examples /// /// ``` -/// use light_hasher::hash_to_field_size::hashv_to_bn254_field_size_be; -/// -/// hashv_to_bn254_field_size_be(&[&[0u8;32][..]]); +/// # #[cfg(feature = "keccak")] +/// # use light_hasher::hash_to_field_size::hash_to_bn254_field_size_be; +/// # +/// # #[cfg(feature = "keccak")] +/// hash_to_bn254_field_size_be(&[0u8; 32]); /// ``` pub fn hash_to_bn254_field_size_be(bytes: &[u8]) -> [u8; 32] { hashv_to_bn254_field_size_be_const_array::<2>(&[bytes]).unwrap() @@ -99,88 +107,3 @@ pub fn is_smaller_than_bn254_field_size_be(bytes: &[u8; 32]) -> bool { let bigint = BigUint::from_bytes_be(bytes); bigint < ark_bn254::Fr::MODULUS.into() } - -#[cfg(feature = "poseidon")] -#[cfg(test)] -mod tests { - use ark_ff::PrimeField; - use borsh::BorshDeserialize; - use num_bigint::{BigUint, ToBigUint}; - - use super::*; - use crate::bigint::bigint_to_be_bytes_array; - - #[test] - fn test_is_smaller_than_bn254_field_size_be() { - let modulus: BigUint = ark_bn254::Fr::MODULUS.into(); - let modulus_bytes: [u8; 32] = bigint_to_be_bytes_array(&modulus).unwrap(); - assert!(!is_smaller_than_bn254_field_size_be(&modulus_bytes)); - - let bigint = modulus.clone() - 1.to_biguint().unwrap(); - let bigint_bytes: [u8; 32] = bigint_to_be_bytes_array(&bigint).unwrap(); - assert!(is_smaller_than_bn254_field_size_be(&bigint_bytes)); - - let bigint = modulus + 1.to_biguint().unwrap(); - let bigint_bytes: [u8; 32] = bigint_to_be_bytes_array(&bigint).unwrap(); - assert!(!is_smaller_than_bn254_field_size_be(&bigint_bytes)); - } - - #[test] - fn hash_to_field_size_borsh() { - #[derive(BorshSerialize, BorshDeserialize)] - pub struct TestStruct { - a: u32, - b: u32, - c: u64, - } - let test_struct = TestStruct { a: 1, b: 2, c: 3 }; - let serialized = test_struct.try_to_vec().unwrap(); - let hash = test_struct.hash_to_field_size().unwrap(); - let manual_hash = hash_to_bn254_field_size_be(&serialized); - assert_eq!(hash, manual_hash); - } - - #[cfg(feature = "solana")] - #[test] - fn test_hash_to_bn254_field_size_be() { - use solana_pubkey::Pubkey; - for _ in 0..10_000 { - let input_bytes = Pubkey::new_unique().to_bytes(); // Sample input - let hashed_value = hash_to_bn254_field_size_be(input_bytes.as_slice()); - assert!( - is_smaller_than_bn254_field_size_be(&hashed_value), - "Hashed value should be within BN254 field size" - ); - } - - let max_input = [u8::MAX; 32]; - let hashed_value = hash_to_bn254_field_size_be(max_input.as_slice()); - assert!( - is_smaller_than_bn254_field_size_be(&hashed_value), - "Hashed value should be within BN254 field size" - ); - } - - #[cfg(feature = "solana")] - #[test] - fn test_hashv_to_bn254_field_size_be() { - use solana_pubkey::Pubkey; - for _ in 0..10_000 { - let input_bytes = [Pubkey::new_unique().to_bytes(); 4]; - let input_bytes = input_bytes.iter().map(|x| x.as_slice()).collect::>(); - let hashed_value = hashv_to_bn254_field_size_be(input_bytes.as_slice()); - assert!( - is_smaller_than_bn254_field_size_be(&hashed_value), - "Hashed value should be within BN254 field size" - ); - } - - let max_input = [[u8::MAX; 32]; 16]; - let max_input = max_input.iter().map(|x| x.as_slice()).collect::>(); - let hashed_value = hashv_to_bn254_field_size_be(max_input.as_slice()); - assert!( - is_smaller_than_bn254_field_size_be(&hashed_value), - "Hashed value should be within BN254 field size" - ); - } -} diff --git a/program-libs/hasher/src/keccak.rs b/program-libs/hasher/src/keccak.rs index ab1c666ee8..c8138ffb3f 100644 --- a/program-libs/hasher/src/keccak.rs +++ b/program-libs/hasher/src/keccak.rs @@ -15,8 +15,36 @@ impl Hasher for Keccak { Self::hashv(&[val]) } - fn hashv(vals: &[&[u8]]) -> Result { - Ok(solana_nostd_keccak::hashv(vals)) + fn hashv(_vals: &[&[u8]]) -> Result { + #[cfg(all(not(target_os = "solana"), feature = "keccak"))] + { + use sha3::{Digest, Keccak256}; + + let mut hasher = Keccak256::default(); + for val in _vals { + hasher.update(val); + } + Ok(hasher.finalize().into()) + } + #[cfg(all(not(target_os = "solana"), not(feature = "keccak")))] + { + Err(HasherError::KeccakFeatureNotEnabled) + } + // Call via a system call to perform the calculation + #[cfg(target_os = "solana")] + { + use crate::HASH_BYTES; + + let mut hash_result = [0; HASH_BYTES]; + unsafe { + crate::syscalls::sol_keccak256( + _vals as *const _ as *const u8, + _vals.len() as u64, + &mut hash_result as *mut _ as *mut u8, + ); + } + Ok(hash_result) + } } fn zero_bytes() -> ZeroBytes { diff --git a/program-libs/hasher/src/lib.rs b/program-libs/hasher/src/lib.rs index 83a0875ae9..a9aadc19dd 100644 --- a/program-libs/hasher/src/lib.rs +++ b/program-libs/hasher/src/lib.rs @@ -1,3 +1,15 @@ +#![allow(unexpected_cfgs)] +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(feature = "alloc", not(feature = "std")))] +extern crate alloc; + +#[cfg(feature = "alloc")] +#[cfg(not(feature = "std"))] +pub use alloc::{string::String, vec, vec::Vec}; +#[cfg(feature = "std")] +pub use std::{string::String, vec, vec::Vec}; + pub mod bigint; mod data_hasher; pub mod errors; diff --git a/program-libs/hasher/src/sha256.rs b/program-libs/hasher/src/sha256.rs index a032a10734..d8cda59cab 100644 --- a/program-libs/hasher/src/sha256.rs +++ b/program-libs/hasher/src/sha256.rs @@ -25,17 +25,21 @@ impl Hasher for Sha256 { Self::hashv(&[val]) } - fn hashv(vals: &[&[u8]]) -> Result { - #[cfg(not(target_os = "solana"))] + fn hashv(_vals: &[&[u8]]) -> Result { + #[cfg(all(not(target_os = "solana"), feature = "sha256"))] { use sha2::{Digest, Sha256}; let mut hasher = Sha256::default(); - for val in vals { + for val in _vals { hasher.update(val); } Ok(hasher.finalize().into()) } + #[cfg(all(not(target_os = "solana"), not(feature = "sha256")))] + { + Err(HasherError::Sha256FeatureNotEnabled) + } // Call via a system call to perform the calculation #[cfg(target_os = "solana")] { @@ -44,8 +48,8 @@ impl Hasher for Sha256 { let mut hash_result = [0; HASH_BYTES]; unsafe { crate::syscalls::sol_sha256( - vals as *const _ as *const u8, - vals.len() as u64, + _vals as *const _ as *const u8, + _vals.len() as u64, &mut hash_result as *mut _ as *mut u8, ); } diff --git a/program-libs/hasher/src/to_byte_array.rs b/program-libs/hasher/src/to_byte_array.rs index fcb0351fb2..a8f1f4edb3 100644 --- a/program-libs/hasher/src/to_byte_array.rs +++ b/program-libs/hasher/src/to_byte_array.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "alloc")] +use crate::String; use crate::{Hasher, HasherError, Poseidon}; pub trait ToByteArray { @@ -16,7 +18,7 @@ macro_rules! impl_to_byte_array_for_integer_type { fn to_byte_array(&self) -> Result<[u8; 32], HasherError> { let bytes = self.to_be_bytes(); let mut result = [0; 32]; - result[32 - std::mem::size_of::<$int_ty>()..].copy_from_slice(&bytes); + result[32 - core::mem::size_of::<$int_ty>()..].copy_from_slice(&bytes); Ok(result) } } @@ -34,7 +36,7 @@ impl ToByteArray for Option { let byte_array = if T::IS_PRIMITIVE { let mut byte_array = value.to_byte_array()?; // Prefix with 1 to indicate Some(). - byte_array[32 - std::mem::size_of::() - 1] = 1; + byte_array[32 - core::mem::size_of::() - 1] = 1; byte_array } else { let byte_array = value.to_byte_array()?; @@ -172,6 +174,7 @@ impl_to_byte_array_for_u8_array!(29); impl_to_byte_array_for_u8_array!(30); impl_to_byte_array_for_u8_array!(31); +#[cfg(feature = "alloc")] impl ToByteArray for String { const NUM_FIELDS: usize = 1; @@ -188,245 +191,3 @@ impl ToByteArray for String { Ok(result) } } - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_to_byte_array_integers() { - // i8 tests - let i8_min_result = i8::MIN.to_byte_array().unwrap(); - let mut expected_i8_min = [0u8; 32]; - expected_i8_min[31] = 128; // i8::MIN.to_be_bytes() = [128] - assert_eq!(i8_min_result, expected_i8_min); - - let i8_max_result = i8::MAX.to_byte_array().unwrap(); - let mut expected_i8_max = [0u8; 32]; - expected_i8_max[31] = 127; // i8::MAX.to_be_bytes() = [127] - assert_eq!(i8_max_result, expected_i8_max); - - // u8 tests - let u8_min_result = u8::MIN.to_byte_array().unwrap(); - let mut expected_u8_min = [0u8; 32]; - expected_u8_min[31] = 0; // u8::MIN.to_be_bytes() = [0] - assert_eq!(u8_min_result, expected_u8_min); - - let u8_max_result = u8::MAX.to_byte_array().unwrap(); - let mut expected_u8_max = [0u8; 32]; - expected_u8_max[31] = 255; // u8::MAX.to_be_bytes() = [255] - assert_eq!(u8_max_result, expected_u8_max); - - // i16 tests - let i16_min_result = i16::MIN.to_byte_array().unwrap(); - let mut expected_i16_min = [0u8; 32]; - expected_i16_min[30..32].copy_from_slice(&i16::MIN.to_be_bytes()); // [128, 0] - assert_eq!(i16_min_result, expected_i16_min); - - let i16_max_result = i16::MAX.to_byte_array().unwrap(); - let mut expected_i16_max = [0u8; 32]; - expected_i16_max[30..32].copy_from_slice(&i16::MAX.to_be_bytes()); // [127, 255] - assert_eq!(i16_max_result, expected_i16_max); - - // u16 tests - let u16_min_result = u16::MIN.to_byte_array().unwrap(); - let mut expected_u16_min = [0u8; 32]; - expected_u16_min[30..32].copy_from_slice(&u16::MIN.to_be_bytes()); // [0, 0] - assert_eq!(u16_min_result, expected_u16_min); - - let u16_max_result = u16::MAX.to_byte_array().unwrap(); - let mut expected_u16_max = [0u8; 32]; - expected_u16_max[30..32].copy_from_slice(&u16::MAX.to_be_bytes()); // [255, 255] - assert_eq!(u16_max_result, expected_u16_max); - - // i32 tests - let i32_min_result = i32::MIN.to_byte_array().unwrap(); - let mut expected_i32_min = [0u8; 32]; - expected_i32_min[28..32].copy_from_slice(&i32::MIN.to_be_bytes()); // [128, 0, 0, 0] - assert_eq!(i32_min_result, expected_i32_min); - - let i32_max_result = i32::MAX.to_byte_array().unwrap(); - let mut expected_i32_max = [0u8; 32]; - expected_i32_max[28..32].copy_from_slice(&i32::MAX.to_be_bytes()); // [127, 255, 255, 255] - assert_eq!(i32_max_result, expected_i32_max); - - // u32 tests - let u32_min_result = u32::MIN.to_byte_array().unwrap(); - let mut expected_u32_min = [0u8; 32]; - expected_u32_min[28..32].copy_from_slice(&u32::MIN.to_be_bytes()); // [0, 0, 0, 0] - assert_eq!(u32_min_result, expected_u32_min); - - let u32_max_result = u32::MAX.to_byte_array().unwrap(); - let mut expected_u32_max = [0u8; 32]; - expected_u32_max[28..32].copy_from_slice(&u32::MAX.to_be_bytes()); // [255, 255, 255, 255] - assert_eq!(u32_max_result, expected_u32_max); - - // i64 tests - let i64_min_result = i64::MIN.to_byte_array().unwrap(); - let mut expected_i64_min = [0u8; 32]; - expected_i64_min[24..32].copy_from_slice(&i64::MIN.to_be_bytes()); // [128, 0, 0, 0, 0, 0, 0, 0] - assert_eq!(i64_min_result, expected_i64_min); - - let i64_max_result = i64::MAX.to_byte_array().unwrap(); - let mut expected_i64_max = [0u8; 32]; - expected_i64_max[24..32].copy_from_slice(&i64::MAX.to_be_bytes()); // [127, 255, 255, 255, 255, 255, 255, 255] - assert_eq!(i64_max_result, expected_i64_max); - - // u64 tests - let u64_min_result = u64::MIN.to_byte_array().unwrap(); - let mut expected_u64_min = [0u8; 32]; - expected_u64_min[24..32].copy_from_slice(&u64::MIN.to_be_bytes()); // [0, 0, 0, 0, 0, 0, 0, 0] - assert_eq!(u64_min_result, expected_u64_min); - - let u64_max_result = u64::MAX.to_byte_array().unwrap(); - let mut expected_u64_max = [0u8; 32]; - expected_u64_max[24..32].copy_from_slice(&u64::MAX.to_be_bytes()); // [255, 255, 255, 255, 255, 255, 255, 255] - assert_eq!(u64_max_result, expected_u64_max); - - // i128 tests - let i128_min_result = i128::MIN.to_byte_array().unwrap(); - let mut expected_i128_min = [0u8; 32]; - expected_i128_min[16..32].copy_from_slice(&i128::MIN.to_be_bytes()); // [128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - assert_eq!(i128_min_result, expected_i128_min); - - let i128_max_result = i128::MAX.to_byte_array().unwrap(); - let mut expected_i128_max = [0u8; 32]; - expected_i128_max[16..32].copy_from_slice(&i128::MAX.to_be_bytes()); // [127, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255] - assert_eq!(i128_max_result, expected_i128_max); - - // u128 tests - let u128_min_result = u128::MIN.to_byte_array().unwrap(); - let mut expected_u128_min = [0u8; 32]; - expected_u128_min[16..32].copy_from_slice(&u128::MIN.to_be_bytes()); // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - assert_eq!(u128_min_result, expected_u128_min); - - let u128_max_result = u128::MAX.to_byte_array().unwrap(); - let mut expected_u128_max = [0u8; 32]; - expected_u128_max[16..32].copy_from_slice(&u128::MAX.to_be_bytes()); // [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255] - assert_eq!(u128_max_result, expected_u128_max); - } - - #[test] - fn test_to_byte_array_primitives() { - // Test bool::to_byte_array - let bool_false_result = false.to_byte_array().unwrap(); - let mut expected_bool_false = [0u8; 32]; - expected_bool_false[31] = 0; - assert_eq!(bool_false_result, expected_bool_false); - - let bool_true_result = true.to_byte_array().unwrap(); - let mut expected_bool_true = [0u8; 32]; - expected_bool_true[31] = 1; - assert_eq!(bool_true_result, expected_bool_true); - } - - #[test] - fn test_to_byte_array_option() { - // Very important property - `None` and `Some(0)` always have to be - // different and should produce different hashes! - - // Test Option::to_byte_array - let u8_none: Option = None; - let u8_none_result = u8_none.to_byte_array().unwrap(); - assert_eq!(u8_none_result, [0u8; 32]); - - let u8_some_zero: Option = Some(0); - let u8_some_zero_result = u8_some_zero.to_byte_array().unwrap(); - let mut expected_u8_some_zero = [0u8; 32]; - expected_u8_some_zero[32 - std::mem::size_of::() - 1] = 1; // Mark as Some - assert_eq!(u8_some_zero_result, expected_u8_some_zero); - - // Test Option::to_byte_array - let u16_none: Option = None; - let u16_none_result = u16_none.to_byte_array().unwrap(); - assert_eq!(u16_none_result, [0u8; 32]); - - let u16_some_zero: Option = Some(0); - let u16_some_zero_result = u16_some_zero.to_byte_array().unwrap(); - let mut expected_u16_some_zero = [0u8; 32]; - expected_u16_some_zero[32 - std::mem::size_of::() - 1] = 1; // Mark as Some - assert_eq!(u16_some_zero_result, expected_u16_some_zero); - - // Test Option::to_byte_array - let u32_none: Option = None; - let u32_none_result = u32_none.to_byte_array().unwrap(); - assert_eq!(u32_none_result, [0u8; 32]); - - let u32_some_zero: Option = Some(0); - let u32_some_zero_result = u32_some_zero.to_byte_array().unwrap(); - let mut expected_u32_some_zero = [0u8; 32]; - expected_u32_some_zero[32 - std::mem::size_of::() - 1] = 1; // Mark as Some - assert_eq!(u32_some_zero_result, expected_u32_some_zero); - - // Test Option::to_byte_array - let u64_none: Option = None; - let u64_none_result = u64_none.to_byte_array().unwrap(); - assert_eq!(u64_none_result, [0u8; 32]); - - let u64_some_zero: Option = Some(0); - let u64_some_zero_result = u64_some_zero.to_byte_array().unwrap(); - let mut expected_u64_some_zero = [0u8; 32]; - expected_u64_some_zero[32 - std::mem::size_of::() - 1] = 1; // Mark as Some - assert_eq!(u64_some_zero_result, expected_u64_some_zero); - - // Test Option::to_byte_array - let u128_none: Option = None; - let u128_none_result = u128_none.to_byte_array().unwrap(); - assert_eq!(u128_none_result, [0u8; 32]); - - let u128_some_zero: Option = Some(0); - let u128_some_zero_result = u128_some_zero.to_byte_array().unwrap(); - let mut expected_u128_some_zero = [0u8; 32]; - expected_u128_some_zero[32 - std::mem::size_of::() - 1] = 1; // Mark as Some - assert_eq!(u128_some_zero_result, expected_u128_some_zero); - } - - #[test] - fn test_to_byte_array_u8_arrays() { - // Test with single element array - let single_element_arr: [u8; 1] = [255]; - let result = single_element_arr.to_byte_array().unwrap(); - let mut expected = [0u8; 32]; - expected[31] = 255; - assert_eq!(result, expected); - - // Test with multi-element array - let multi_element_arr: [u8; 4] = [1, 2, 3, 4]; - let result = multi_element_arr.to_byte_array().unwrap(); - let mut expected = [0u8; 32]; - expected[32 - 4..].copy_from_slice(&multi_element_arr); - assert_eq!(result, expected); - - // Test with full 32-byte array - let full_arr: [u8; 31] = [ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, - 25, 26, 27, 28, 29, 30, 31, - ]; - let result = full_arr.to_byte_array().unwrap(); - assert_eq!(result[0], 0); - assert_eq!(&result[1..], full_arr.as_slice()); - } - - #[test] - fn test_to_byte_array_string() { - // Test with empty string - let empty_string = "".to_string(); - let result = empty_string.to_byte_array().unwrap(); - let expected = [0u8; 32]; - assert_eq!(result, expected); - - // Test with short string - let short_string = "foobar".to_string(); - let result = short_string.to_byte_array().unwrap(); - let mut expected = [0u8; 32]; - expected[32 - 6..].copy_from_slice(b"foobar"); - assert_eq!(result, expected); - - // Test with longer string that gets truncated - let long_string = - "this is a string that is longer than 32 bytes and will be fail".to_string(); - let byte_len = long_string.len(); - let result = long_string.to_byte_array(); - assert_eq!(result, Err(HasherError::InvalidInputLength(31, byte_len))); - } -} diff --git a/program-libs/hasher/tests/bigint.rs b/program-libs/hasher/tests/bigint.rs new file mode 100644 index 0000000000..528aca8c83 --- /dev/null +++ b/program-libs/hasher/tests/bigint.rs @@ -0,0 +1,382 @@ +use light_hasher::{ + bigint::{bigint_to_be_bytes_array, bigint_to_le_bytes_array}, + HasherError, +}; +use num_bigint::{BigUint, RandBigInt, ToBigUint}; +use rand::thread_rng; + +const ITERATIONS: usize = 64; + +#[test] +fn test_bigint_conversion_rand() { + let mut rng = thread_rng(); + + for _ in 0..ITERATIONS { + let b64 = rng.gen_biguint(32); + let b64_converted: [u8; 8] = bigint_to_be_bytes_array(&b64).unwrap(); + let b64_converted = BigUint::from_bytes_be(&b64_converted); + assert_eq!(b64, b64_converted); + let b64_converted: [u8; 8] = bigint_to_le_bytes_array(&b64).unwrap(); + let b64_converted = BigUint::from_bytes_le(&b64_converted); + assert_eq!(b64, b64_converted); + + let b128 = rng.gen_biguint(128); + let b128_converted: [u8; 16] = bigint_to_be_bytes_array(&b128).unwrap(); + let b128_converted = BigUint::from_bytes_be(&b128_converted); + assert_eq!(b128, b128_converted); + let b128_converted: [u8; 16] = bigint_to_le_bytes_array(&b128).unwrap(); + let b128_converted = BigUint::from_bytes_le(&b128_converted); + assert_eq!(b128, b128_converted); + + let b256 = rng.gen_biguint(256); + let b256_converted: [u8; 32] = bigint_to_be_bytes_array(&b256).unwrap(); + let b256_converted = BigUint::from_bytes_be(&b256_converted); + assert_eq!(b256, b256_converted); + let b256_converted: [u8; 32] = bigint_to_le_bytes_array(&b256).unwrap(); + let b256_converted = BigUint::from_bytes_le(&b256_converted); + assert_eq!(b256, b256_converted); + + let b320 = rng.gen_biguint(320); + let b320_converted: [u8; 40] = bigint_to_be_bytes_array(&b320).unwrap(); + let b320_converted = BigUint::from_bytes_be(&b320_converted); + assert_eq!(b320, b320_converted); + let b320_converted: [u8; 40] = bigint_to_le_bytes_array(&b320).unwrap(); + let b320_converted = BigUint::from_bytes_le(&b320_converted); + assert_eq!(b320, b320_converted); + + let b384 = rng.gen_biguint(384); + let b384_converted: [u8; 48] = bigint_to_be_bytes_array(&b384).unwrap(); + let b384_converted = BigUint::from_bytes_be(&b384_converted); + assert_eq!(b384, b384_converted); + let b384_converted: [u8; 48] = bigint_to_le_bytes_array(&b384).unwrap(); + let b384_converted = BigUint::from_bytes_le(&b384_converted); + assert_eq!(b384, b384_converted); + + let b448 = rng.gen_biguint(448); + let b448_converted: [u8; 56] = bigint_to_be_bytes_array(&b448).unwrap(); + let b448_converted = BigUint::from_bytes_be(&b448_converted); + assert_eq!(b448, b448_converted); + let b448_converted: [u8; 56] = bigint_to_le_bytes_array(&b448).unwrap(); + let b448_converted = BigUint::from_bytes_le(&b448_converted); + assert_eq!(b448, b448_converted); + + let b768 = rng.gen_biguint(768); + let b768_converted: [u8; 96] = bigint_to_be_bytes_array(&b768).unwrap(); + let b768_converted = BigUint::from_bytes_be(&b768_converted); + assert_eq!(b768, b768_converted); + let b768_converted: [u8; 96] = bigint_to_le_bytes_array(&b768).unwrap(); + let b768_converted = BigUint::from_bytes_le(&b768_converted); + assert_eq!(b768, b768_converted); + + let b832 = rng.gen_biguint(832); + let b832_converted: [u8; 104] = bigint_to_be_bytes_array(&b832).unwrap(); + let b832_converted = BigUint::from_bytes_be(&b832_converted); + assert_eq!(b832, b832_converted); + let b832_converted: [u8; 104] = bigint_to_le_bytes_array(&b832).unwrap(); + let b832_converted = BigUint::from_bytes_le(&b832_converted); + assert_eq!(b832, b832_converted); + } +} + +#[test] +fn test_bigint_conversion_zero() { + let zero = 0_u32.to_biguint().unwrap(); + + let b64_converted: [u8; 8] = bigint_to_be_bytes_array(&zero).unwrap(); + let b64_converted = BigUint::from_bytes_be(&b64_converted); + assert_eq!(zero, b64_converted); + let b64_converted: [u8; 8] = bigint_to_le_bytes_array(&zero).unwrap(); + let b64_converted = BigUint::from_bytes_le(&b64_converted); + assert_eq!(zero, b64_converted); + + let b128_converted: [u8; 16] = bigint_to_be_bytes_array(&zero).unwrap(); + let b128_converted = BigUint::from_bytes_be(&b128_converted); + assert_eq!(zero, b128_converted); + let b128_converted: [u8; 16] = bigint_to_le_bytes_array(&zero).unwrap(); + let b128_converted = BigUint::from_bytes_le(&b128_converted); + assert_eq!(zero, b128_converted); + + let b256_converted: [u8; 32] = bigint_to_be_bytes_array(&zero).unwrap(); + let b256_converted = BigUint::from_bytes_be(&b256_converted); + assert_eq!(zero, b256_converted); + let b256_converted: [u8; 32] = bigint_to_le_bytes_array(&zero).unwrap(); + let b256_converted = BigUint::from_bytes_le(&b256_converted); + assert_eq!(zero, b256_converted); + + let b320_converted: [u8; 40] = bigint_to_be_bytes_array(&zero).unwrap(); + let b320_converted = BigUint::from_bytes_be(&b320_converted); + assert_eq!(zero, b320_converted); + let b320_converted: [u8; 40] = bigint_to_le_bytes_array(&zero).unwrap(); + let b320_converted = BigUint::from_bytes_le(&b320_converted); + assert_eq!(zero, b320_converted); + + let b384_converted: [u8; 48] = bigint_to_be_bytes_array(&zero).unwrap(); + let b384_converted = BigUint::from_bytes_be(&b384_converted); + assert_eq!(zero, b384_converted); + let b384_converted: [u8; 48] = bigint_to_le_bytes_array(&zero).unwrap(); + let b384_converted = BigUint::from_bytes_le(&b384_converted); + assert_eq!(zero, b384_converted); + + let b448_converted: [u8; 56] = bigint_to_be_bytes_array(&zero).unwrap(); + let b448_converted = BigUint::from_bytes_be(&b448_converted); + assert_eq!(zero, b448_converted); + let b448_converted: [u8; 56] = bigint_to_le_bytes_array(&zero).unwrap(); + let b448_converted = BigUint::from_bytes_le(&b448_converted); + assert_eq!(zero, b448_converted); + + let b768_converted: [u8; 96] = bigint_to_be_bytes_array(&zero).unwrap(); + let b768_converted = BigUint::from_bytes_be(&b768_converted); + assert_eq!(zero, b768_converted); + let b768_converted: [u8; 96] = bigint_to_le_bytes_array(&zero).unwrap(); + let b768_converted = BigUint::from_bytes_le(&b768_converted); + assert_eq!(zero, b768_converted); + + let b832_converted: [u8; 104] = bigint_to_be_bytes_array(&zero).unwrap(); + let b832_converted = BigUint::from_bytes_be(&b832_converted); + assert_eq!(zero, b832_converted); + let b832_converted: [u8; 104] = bigint_to_le_bytes_array(&zero).unwrap(); + let b832_converted = BigUint::from_bytes_le(&b832_converted); + assert_eq!(zero, b832_converted); +} + +#[test] +fn test_bigint_conversion_one() { + let one = 1_u32.to_biguint().unwrap(); + + let b64_converted: [u8; 8] = bigint_to_be_bytes_array(&one).unwrap(); + let b64_converted = BigUint::from_bytes_be(&b64_converted); + assert_eq!(one, b64_converted); + let b64_converted: [u8; 8] = bigint_to_le_bytes_array(&one).unwrap(); + let b64_converted = BigUint::from_bytes_le(&b64_converted); + assert_eq!(one, b64_converted); + let b64 = BigUint::from_bytes_be(&[0, 0, 0, 0, 0, 0, 0, 1]); + assert_eq!(one, b64); + let b64 = BigUint::from_bytes_le(&[1, 0, 0, 0, 0, 0, 0, 0]); + assert_eq!(one, b64); + + let b128_converted: [u8; 16] = bigint_to_be_bytes_array(&one).unwrap(); + let b128_converted = BigUint::from_bytes_be(&b128_converted); + assert_eq!(one, b128_converted); + let b128_converted: [u8; 16] = bigint_to_le_bytes_array(&one).unwrap(); + let b128_converted = BigUint::from_bytes_le(&b128_converted); + assert_eq!(one, b128_converted); + + let b256_converted: [u8; 32] = bigint_to_be_bytes_array(&one).unwrap(); + let b256_converted = BigUint::from_bytes_be(&b256_converted); + assert_eq!(one, b256_converted); + let b256_converted: [u8; 32] = bigint_to_le_bytes_array(&one).unwrap(); + let b256_converted = BigUint::from_bytes_le(&b256_converted); + assert_eq!(one, b256_converted); + + let b320_converted: [u8; 40] = bigint_to_be_bytes_array(&one).unwrap(); + let b320_converted = BigUint::from_bytes_be(&b320_converted); + assert_eq!(one, b320_converted); + let b320_converted: [u8; 40] = bigint_to_le_bytes_array(&one).unwrap(); + let b320_converted = BigUint::from_bytes_le(&b320_converted); + assert_eq!(one, b320_converted); + + let b384_converted: [u8; 48] = bigint_to_be_bytes_array(&one).unwrap(); + let b384_converted = BigUint::from_bytes_be(&b384_converted); + assert_eq!(one, b384_converted); + let b384_converted: [u8; 48] = bigint_to_le_bytes_array(&one).unwrap(); + let b384_converted = BigUint::from_bytes_le(&b384_converted); + assert_eq!(one, b384_converted); + + let b448_converted: [u8; 56] = bigint_to_be_bytes_array(&one).unwrap(); + let b448_converted = BigUint::from_bytes_be(&b448_converted); + assert_eq!(one, b448_converted); + let b448_converted: [u8; 56] = bigint_to_le_bytes_array(&one).unwrap(); + let b448_converted = BigUint::from_bytes_le(&b448_converted); + assert_eq!(one, b448_converted); + + let b768_converted: [u8; 96] = bigint_to_be_bytes_array(&one).unwrap(); + let b768_converted = BigUint::from_bytes_be(&b768_converted); + assert_eq!(one, b768_converted); + let b768_converted: [u8; 96] = bigint_to_le_bytes_array(&one).unwrap(); + let b768_converted = BigUint::from_bytes_le(&b768_converted); + assert_eq!(one, b768_converted); + + let b832_converted: [u8; 104] = bigint_to_be_bytes_array(&one).unwrap(); + let b832_converted = BigUint::from_bytes_be(&b832_converted); + assert_eq!(one, b832_converted); + let b832_converted: [u8; 104] = bigint_to_le_bytes_array(&one).unwrap(); + let b832_converted = BigUint::from_bytes_le(&b832_converted); + assert_eq!(one, b832_converted); +} + +#[test] +fn test_bigint_conversion_invalid_size() { + let b8 = BigUint::from_bytes_be(&[1; 8]); + let res: Result<[u8; 1], HasherError> = bigint_to_be_bytes_array(&b8); + assert!(matches!(res, Err(HasherError::InvalidInputLength(1, 8)))); + let res: Result<[u8; 7], HasherError> = bigint_to_be_bytes_array(&b8); + assert!(matches!(res, Err(HasherError::InvalidInputLength(7, 8)))); + let res: Result<[u8; 8], HasherError> = bigint_to_be_bytes_array(&b8); + assert!(res.is_ok()); + + let b8 = BigUint::from_bytes_le(&[1; 8]); + let res: Result<[u8; 1], HasherError> = bigint_to_le_bytes_array(&b8); + assert!(matches!(res, Err(HasherError::InvalidInputLength(1, 8)))); + let res: Result<[u8; 7], HasherError> = bigint_to_le_bytes_array(&b8); + assert!(matches!(res, Err(HasherError::InvalidInputLength(7, 8)))); + let res: Result<[u8; 8], HasherError> = bigint_to_le_bytes_array(&b8); + assert!(res.is_ok()); + + let b16 = BigUint::from_bytes_be(&[1; 16]); + let res: Result<[u8; 1], HasherError> = bigint_to_be_bytes_array(&b16); + assert!(matches!(res, Err(HasherError::InvalidInputLength(1, 16)))); + let res: Result<[u8; 15], HasherError> = bigint_to_be_bytes_array(&b16); + assert!(matches!(res, Err(HasherError::InvalidInputLength(15, 16)))); + let res: Result<[u8; 16], HasherError> = bigint_to_be_bytes_array(&b16); + assert!(res.is_ok()); + + let b16 = BigUint::from_bytes_le(&[1; 16]); + let res: Result<[u8; 1], HasherError> = bigint_to_le_bytes_array(&b16); + assert!(matches!(res, Err(HasherError::InvalidInputLength(1, 16)))); + let res: Result<[u8; 15], HasherError> = bigint_to_le_bytes_array(&b16); + assert!(matches!(res, Err(HasherError::InvalidInputLength(15, 16)))); + let res: Result<[u8; 16], HasherError> = bigint_to_le_bytes_array(&b16); + assert!(res.is_ok()); + + let b32 = BigUint::from_bytes_be(&[1; 32]); + let res: Result<[u8; 1], HasherError> = bigint_to_be_bytes_array(&b32); + assert!(matches!(res, Err(HasherError::InvalidInputLength(1, 32)))); + let res: Result<[u8; 31], HasherError> = bigint_to_be_bytes_array(&b32); + assert!(matches!(res, Err(HasherError::InvalidInputLength(31, 32)))); + let res: Result<[u8; 32], HasherError> = bigint_to_be_bytes_array(&b32); + assert!(res.is_ok()); + + let b32 = BigUint::from_bytes_le(&[1; 32]); + let res: Result<[u8; 1], HasherError> = bigint_to_le_bytes_array(&b32); + assert!(matches!(res, Err(HasherError::InvalidInputLength(1, 32)))); + let res: Result<[u8; 31], HasherError> = bigint_to_le_bytes_array(&b32); + assert!(matches!(res, Err(HasherError::InvalidInputLength(31, 32)))); + let res: Result<[u8; 32], HasherError> = bigint_to_le_bytes_array(&b32); + assert!(res.is_ok()); + + let b64 = BigUint::from_bytes_be(&[1; 64]); + let res: Result<[u8; 1], HasherError> = bigint_to_be_bytes_array(&b64); + assert!(matches!(res, Err(HasherError::InvalidInputLength(1, 64)))); + let res: Result<[u8; 63], HasherError> = bigint_to_be_bytes_array(&b64); + assert!(matches!(res, Err(HasherError::InvalidInputLength(63, 64)))); + let res: Result<[u8; 64], HasherError> = bigint_to_be_bytes_array(&b64); + assert!(res.is_ok()); + + let b64 = BigUint::from_bytes_le(&[1; 64]); + let res: Result<[u8; 1], HasherError> = bigint_to_le_bytes_array(&b64); + assert!(matches!(res, Err(HasherError::InvalidInputLength(1, 64)))); + let res: Result<[u8; 63], HasherError> = bigint_to_le_bytes_array(&b64); + assert!(matches!(res, Err(HasherError::InvalidInputLength(63, 64)))); + let res: Result<[u8; 64], HasherError> = bigint_to_le_bytes_array(&b64); + assert!(res.is_ok()); + + let b128 = BigUint::from_bytes_be(&[1; 128]); + let res: Result<[u8; 1], HasherError> = bigint_to_be_bytes_array(&b128); + assert!(matches!(res, Err(HasherError::InvalidInputLength(1, 128)))); + let res: Result<[u8; 127], HasherError> = bigint_to_be_bytes_array(&b128); + assert!(matches!( + res, + Err(HasherError::InvalidInputLength(127, 128)) + )); + let res: Result<[u8; 128], HasherError> = bigint_to_be_bytes_array(&b128); + assert!(res.is_ok()); + + let b128 = BigUint::from_bytes_le(&[1; 128]); + let res: Result<[u8; 1], HasherError> = bigint_to_le_bytes_array(&b128); + assert!(matches!(res, Err(HasherError::InvalidInputLength(1, 128)))); + let res: Result<[u8; 127], HasherError> = bigint_to_le_bytes_array(&b128); + assert!(matches!( + res, + Err(HasherError::InvalidInputLength(127, 128)) + )); + let res: Result<[u8; 128], HasherError> = bigint_to_le_bytes_array(&b128); + assert!(res.is_ok()); + + let b256 = BigUint::from_bytes_be(&[1; 256]); + let res: Result<[u8; 1], HasherError> = bigint_to_be_bytes_array(&b256); + assert!(matches!(res, Err(HasherError::InvalidInputLength(1, 256)))); + let res: Result<[u8; 255], HasherError> = bigint_to_be_bytes_array(&b256); + assert!(matches!( + res, + Err(HasherError::InvalidInputLength(255, 256)) + )); + let res: Result<[u8; 256], HasherError> = bigint_to_be_bytes_array(&b256); + assert!(res.is_ok()); + + let b256 = BigUint::from_bytes_le(&[1; 256]); + let res: Result<[u8; 1], HasherError> = bigint_to_le_bytes_array(&b256); + assert!(matches!(res, Err(HasherError::InvalidInputLength(1, 256)))); + let res: Result<[u8; 255], HasherError> = bigint_to_le_bytes_array(&b256); + assert!(matches!( + res, + Err(HasherError::InvalidInputLength(255, 256)) + )); + let res: Result<[u8; 256], HasherError> = bigint_to_le_bytes_array(&b256); + assert!(res.is_ok()); + + let b512 = BigUint::from_bytes_be(&[1; 512]); + let res: Result<[u8; 1], HasherError> = bigint_to_be_bytes_array(&b512); + assert!(matches!(res, Err(HasherError::InvalidInputLength(1, 512)))); + let res: Result<[u8; 511], HasherError> = bigint_to_be_bytes_array(&b512); + assert!(matches!( + res, + Err(HasherError::InvalidInputLength(511, 512)) + )); + let res: Result<[u8; 512], HasherError> = bigint_to_be_bytes_array(&b512); + assert!(res.is_ok()); + + let b512 = BigUint::from_bytes_le(&[1; 512]); + let res: Result<[u8; 1], HasherError> = bigint_to_le_bytes_array(&b512); + assert!(matches!(res, Err(HasherError::InvalidInputLength(1, 512)))); + let res: Result<[u8; 511], HasherError> = bigint_to_le_bytes_array(&b512); + assert!(matches!( + res, + Err(HasherError::InvalidInputLength(511, 512)) + )); + let res: Result<[u8; 512], HasherError> = bigint_to_le_bytes_array(&b512); + assert!(res.is_ok()); + + let b768 = BigUint::from_bytes_be(&[1; 768]); + let res: Result<[u8; 1], HasherError> = bigint_to_be_bytes_array(&b768); + assert!(matches!(res, Err(HasherError::InvalidInputLength(1, 768)))); + let res: Result<[u8; 767], HasherError> = bigint_to_be_bytes_array(&b768); + assert!(matches!( + res, + Err(HasherError::InvalidInputLength(767, 768)) + )); + let res: Result<[u8; 768], HasherError> = bigint_to_be_bytes_array(&b768); + assert!(res.is_ok()); + + let b768 = BigUint::from_bytes_le(&[1; 768]); + let res: Result<[u8; 1], HasherError> = bigint_to_le_bytes_array(&b768); + assert!(matches!(res, Err(HasherError::InvalidInputLength(1, 768)))); + let res: Result<[u8; 767], HasherError> = bigint_to_le_bytes_array(&b768); + assert!(matches!( + res, + Err(HasherError::InvalidInputLength(767, 768)) + )); + let res: Result<[u8; 768], HasherError> = bigint_to_le_bytes_array(&b768); + assert!(res.is_ok()); + + let b1024 = BigUint::from_bytes_be(&[1; 1024]); + let res: Result<[u8; 1], HasherError> = bigint_to_be_bytes_array(&b1024); + assert!(matches!(res, Err(HasherError::InvalidInputLength(1, 1024)))); + let res: Result<[u8; 1023], HasherError> = bigint_to_be_bytes_array(&b1024); + assert!(matches!( + res, + Err(HasherError::InvalidInputLength(1023, 1024)) + )); + let res: Result<[u8; 1024], HasherError> = bigint_to_be_bytes_array(&b1024); + assert!(res.is_ok()); + + let b1024 = BigUint::from_bytes_le(&[1; 1024]); + let res: Result<[u8; 1], HasherError> = bigint_to_le_bytes_array(&b1024); + assert!(matches!(res, Err(HasherError::InvalidInputLength(1, 1024)))); + let res: Result<[u8; 1023], HasherError> = bigint_to_le_bytes_array(&b1024); + assert!(matches!( + res, + Err(HasherError::InvalidInputLength(1023, 1024)) + )); + let res: Result<[u8; 1024], HasherError> = bigint_to_le_bytes_array(&b1024); + assert!(res.is_ok()); +} diff --git a/program-libs/hasher/tests/data_hasher.rs b/program-libs/hasher/tests/data_hasher.rs new file mode 100644 index 0000000000..4d53bd7971 --- /dev/null +++ b/program-libs/hasher/tests/data_hasher.rs @@ -0,0 +1,140 @@ +use std::vec::Vec; + +use light_hasher::{DataHasher, Hasher, HasherError, Poseidon}; + +// A simple test struct that implements DataHasher +#[derive(Default, Clone)] +struct TestHashable { + value: u8, +} + +impl TestHashable { + fn new(value: u8) -> Self { + Self { value } + } +} + +impl DataHasher for TestHashable { + fn hash(&self) -> Result<[u8; 32], HasherError> { + // Simple implementation that creates a predictable hash + let mut result = [0u8; 32]; + result[31] = self.value; + Ok(result) + } +} + +#[test] +fn test_data_hasher_array_1() { + let arr = [TestHashable::new(42)]; + let hash_result = arr.hash::().unwrap(); + + // The result should be the Poseidon hash of the single element's hash + let expected_input = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 42, + ]; + let expected_hash = Poseidon::hash(&expected_input).unwrap(); + + assert_eq!(hash_result, expected_hash); +} + +#[test] +fn test_data_hasher_array_2() { + let arr = [TestHashable::new(1), TestHashable::new(2)]; + let hash_result = arr.hash::().unwrap(); + + // Expected inputs are the hashes of each TestHashable + let hash1 = [0u8; 32]; + let hash2 = [0u8; 32]; + + let mut hash1 = hash1; + hash1[31] = 1; + + let mut hash2 = hash2; + hash2[31] = 2; + + // The result should be the Poseidon hash of concatenated element hashes + let expected_hash = Poseidon::hashv(&[&hash1, &hash2]).unwrap(); + + assert_eq!(hash_result, expected_hash); +} + +#[test] +fn test_data_hasher_array_multiple_sizes() { + // Test arrays of each implemented size + for size in 1..=12 { + let mut array = Vec::with_capacity(size); + for i in 0..size { + array.push(TestHashable::new(i as u8)); + } + + // Convert the Vec to an array of the appropriate size + let array_slice = array.as_slice(); + + // Create expected inputs (hashes of each TestHashable) + let mut expected_inputs = Vec::with_capacity(size); + for i in 0..size { + let mut hash = [0u8; 32]; + hash[31] = i as u8; + expected_inputs.push(hash); + } + + // Dynamically test each array size + match size { + 1 => { + let arr: [TestHashable; 1] = [array_slice[0].clone()]; + let hash_result = arr.hash::().unwrap(); + + let expected_slices: Vec<&[u8]> = + expected_inputs.iter().map(|h| h.as_slice()).collect(); + let expected_hash = Poseidon::hashv(&expected_slices).unwrap(); + + assert_eq!(hash_result, expected_hash); + } + 2 => { + let arr: [TestHashable; 2] = [array_slice[0].clone(), array_slice[1].clone()]; + let hash_result = arr.hash::().unwrap(); + + let expected_slices: Vec<&[u8]> = + expected_inputs.iter().map(|h| h.as_slice()).collect(); + let expected_hash = Poseidon::hashv(&expected_slices).unwrap(); + + assert_eq!(hash_result, expected_hash); + } + 3 => { + let arr: [TestHashable; 3] = [ + array_slice[0].clone(), + array_slice[1].clone(), + array_slice[2].clone(), + ]; + let hash_result = arr.hash::().unwrap(); + + let expected_slices: Vec<&[u8]> = + expected_inputs.iter().map(|h| h.as_slice()).collect(); + let expected_hash = Poseidon::hashv(&expected_slices).unwrap(); + + assert_eq!(hash_result, expected_hash); + } + // We test one more size (4) to confirm the pattern works + 4 => { + let arr: [TestHashable; 4] = [ + array_slice[0].clone(), + array_slice[1].clone(), + array_slice[2].clone(), + array_slice[3].clone(), + ]; + let hash_result = arr.hash::().unwrap(); + + let expected_slices: Vec<&[u8]> = + expected_inputs.iter().map(|h| h.as_slice()).collect(); + let expected_hash = Poseidon::hashv(&expected_slices).unwrap(); + + assert_eq!(hash_result, expected_hash); + } + _ => { + // For sizes 5-12, we've verified the pattern with tests for sizes 1-4 + // We could add more tests here if needed + } + } + } +} diff --git a/program-libs/hasher/tests/hash_chain.rs b/program-libs/hasher/tests/hash_chain.rs new file mode 100644 index 0000000000..44388960df --- /dev/null +++ b/program-libs/hasher/tests/hash_chain.rs @@ -0,0 +1,177 @@ +use light_hasher::{ + hash_chain::{create_hash_chain_from_slice, create_two_inputs_hash_chain}, + Hasher, HasherError, Poseidon, +}; + +/// Tests for `create_hash_chain_from_slice` function: +/// Functional tests: +/// 1. Functional - with hardcoded values. +/// 2. Functional - with manual hash comparison. +/// 3. Functional - for determinism (hashing the same input twice). +/// 4. Functional - empty input case returns zero hash. +/// +/// Failing tests: +/// 5. Failing - input larger than modulus +#[test] +fn test_create_hash_chain_from_slice() { + // 1. Functional test with hardcoded values. + { + // Define hardcoded inputs. + let inputs: [[u8; 32]; 3] = [[1u8; 32], [2u8; 32], [3u8; 32]]; + + // Manually compute the expected hash chain using Poseidon. + // Note: The expected hash values should be precomputed using the same Poseidon parameters. + // For demonstration purposes, we'll assume hypothetical hash outputs. + // In a real scenario, replace these with actual expected values. + let intermediate_hash_1 = Poseidon::hashv(&[&inputs[0], &inputs[1]]).unwrap(); + let expected_hash = Poseidon::hashv(&[&intermediate_hash_1, &inputs[2]]).unwrap(); + + // Call the function under test. + let result = create_hash_chain_from_slice(&inputs).unwrap(); + + // Assert that the result matches the expected hash. + assert_eq!( + result, expected_hash, + "Functional test with hardcoded values failed." + ); + } + + // 2. Functional test with manual hash comparison. + { + let inputs: [[u8; 32]; 2] = [[4u8; 32], [5u8; 32]]; + + // Manually compute the expected hash. + let expected_hash = Poseidon::hashv(&[&inputs[0], &inputs[1]]).unwrap(); + let hard_coded_expected_hash = [ + 13, 250, 206, 124, 182, 159, 160, 87, 57, 23, 80, 155, 25, 43, 40, 136, 228, 255, 201, + 1, 22, 168, 211, 220, 176, 187, 23, 176, 46, 198, 140, 211, + ]; + + let result = create_hash_chain_from_slice(&inputs).unwrap(); + + assert_eq!( + result, expected_hash, + "Functional test with manual hash comparison failed." + ); + assert_eq!(result, hard_coded_expected_hash); + } + + // 2. Functional test with manual hash comparison. + { + let inputs = [[4u8; 32], [5u8; 32], [6u8; 32]]; + + let expected_hash = Poseidon::hashv(&[&inputs[0], &inputs[1]]).unwrap(); + let expected_hash = Poseidon::hashv(&[&expected_hash, &inputs[2]]).unwrap(); + let hard_coded_expected_hash = [ + 12, 74, 32, 81, 132, 82, 10, 115, 75, 248, 169, 125, 228, 230, 140, 167, 149, 181, 244, + 194, 63, 201, 26, 150, 142, 4, 60, 16, 77, 145, 194, 152, + ]; + + let result = create_hash_chain_from_slice(&inputs).unwrap(); + + assert_eq!( + result, expected_hash, + "Functional test with manual hash comparison failed." + ); + assert_eq!(result, hard_coded_expected_hash); + } + + // 3. Functional test for determinism (hashing the same input twice). + { + // Define inputs. + let inputs: [[u8; 32]; 2] = [[6u8; 32], [7u8; 32]]; + + // Compute hash chain the first time. + let first_hash = create_hash_chain_from_slice(&inputs).unwrap(); + + // Compute hash chain the second time. + let second_hash = create_hash_chain_from_slice(&inputs).unwrap(); + + // Assert that both hashes are identical. + assert_eq!( + first_hash, second_hash, + "Determinism test failed: Hashes do not match." + ); + } + + // 4. Test empty input case + { + let inputs: [[u8; 32]; 0] = []; + let result = create_hash_chain_from_slice(&inputs).unwrap(); + assert_eq!(result, [0u8; 32], "Empty input should return zero hash"); + } + // 5. Failing - input larger than modulus + #[cfg(feature = "poseidon")] + { + use ark_ff::PrimeField; + use light_hasher::bigint::bigint_to_be_bytes_array; + use light_poseidon::PoseidonError; + use num_bigint::BigUint; + let modulus: BigUint = ark_bn254::Fr::MODULUS.into(); + let modulus_bytes: [u8; 32] = bigint_to_be_bytes_array(&modulus).unwrap(); + let huge_input = vec![modulus_bytes, modulus_bytes]; + let result = create_hash_chain_from_slice(&huge_input); + assert!( + matches!(result, Err(HasherError::Poseidon(error)) if error == PoseidonError::InputLargerThanModulus), + ); + } +} + +/// Tests for `create_two_inputs_hash_chain` function: +/// 1. Functional - empty inputs. +/// 2. Functional - 1 input each. +/// 3. Functional - 2 inputs each. +/// 4. Failing - invalid input length for hashes_first. +/// 5. Failing - invalid input length for hashes_second. +#[test] +fn test_create_two_inputs_hash_chain() { + // 1. Functional test with empty inputs. + { + let hashes_first: &[[u8; 32]] = &[]; + let hashes_second: &[[u8; 32]] = &[]; + let result = create_two_inputs_hash_chain(hashes_first, hashes_second).unwrap(); + assert_eq!(result, [0u8; 32], "Empty input should return zero hash"); + } + + // 2. Functional test with 1 input each. + { + let hashes_first: &[[u8; 32]] = &[[1u8; 32]]; + let hashes_second: &[[u8; 32]] = &[[2u8; 32]]; + let expected_hash = Poseidon::hashv(&[&hashes_first[0], &hashes_second[0]]).unwrap(); + let result = create_two_inputs_hash_chain(hashes_first, hashes_second).unwrap(); + assert_eq!(result, expected_hash, "Single input each test failed"); + } + + // 3. Functional test with 2 inputs each. + { + let hashes_first: &[[u8; 32]] = &[[1u8; 32], [2u8; 32]]; + let hashes_second: &[[u8; 32]] = &[[3u8; 32], [4u8; 32]]; + let intermediate_hash = Poseidon::hashv(&[&hashes_first[0], &hashes_second[0]]).unwrap(); + let expected_hash = + Poseidon::hashv(&[&intermediate_hash, &hashes_first[1], &hashes_second[1]]).unwrap(); + let result = create_two_inputs_hash_chain(hashes_first, hashes_second).unwrap(); + assert_eq!(result, expected_hash, "Two inputs each test failed"); + } + + // 4. Failing test with invalid input length for hashes_first. + { + let hashes_first: &[[u8; 32]] = &[[1u8; 32]]; + let hashes_second: &[[u8; 32]] = &[[2u8; 32], [3u8; 32]]; + let result = create_two_inputs_hash_chain(hashes_first, hashes_second); + assert!( + matches!(result, Err(HasherError::InvalidInputLength(1, 2))), + "Invalid input length for hashes_first test failed" + ); + } + + // 5. Failing test with invalid input length for hashes_second. + { + let hashes_first: &[[u8; 32]] = &[[1u8; 32], [2u8; 32]]; + let hashes_second: &[[u8; 32]] = &[[3u8; 32]]; + let result = create_two_inputs_hash_chain(hashes_first, hashes_second); + assert!( + matches!(result, Err(HasherError::InvalidInputLength(2, 1))), + "Invalid input length for hashes_second test failed" + ); + } +} diff --git a/program-libs/hasher/tests/hash_to_field_size.rs b/program-libs/hasher/tests/hash_to_field_size.rs new file mode 100644 index 0000000000..29ad1223f4 --- /dev/null +++ b/program-libs/hasher/tests/hash_to_field_size.rs @@ -0,0 +1,82 @@ +use ark_ff::PrimeField; +use borsh::{BorshDeserialize, BorshSerialize}; +use light_hasher::{ + bigint::bigint_to_be_bytes_array, + hash_to_field_size::{ + hash_to_bn254_field_size_be, hashv_to_bn254_field_size_be, + is_smaller_than_bn254_field_size_be, HashToFieldSize, + }, +}; +use num_bigint::{BigUint, ToBigUint}; + +#[test] +fn test_is_smaller_than_bn254_field_size_be() { + let modulus: BigUint = ark_bn254::Fr::MODULUS.into(); + let modulus_bytes: [u8; 32] = bigint_to_be_bytes_array(&modulus).unwrap(); + assert!(!is_smaller_than_bn254_field_size_be(&modulus_bytes)); + + let bigint = modulus.clone() - 1.to_biguint().unwrap(); + let bigint_bytes: [u8; 32] = bigint_to_be_bytes_array(&bigint).unwrap(); + assert!(is_smaller_than_bn254_field_size_be(&bigint_bytes)); + + let bigint = modulus + 1.to_biguint().unwrap(); + let bigint_bytes: [u8; 32] = bigint_to_be_bytes_array(&bigint).unwrap(); + assert!(!is_smaller_than_bn254_field_size_be(&bigint_bytes)); +} + +#[test] +fn hash_to_field_size_borsh() { + #[derive(BorshSerialize, BorshDeserialize)] + pub struct TestStruct { + a: u32, + b: u32, + c: u64, + } + let test_struct = TestStruct { a: 1, b: 2, c: 3 }; + let serialized = test_struct.try_to_vec().unwrap(); + let hash = test_struct.hash_to_field_size().unwrap(); + let manual_hash = hash_to_bn254_field_size_be(&serialized); + assert_eq!(hash, manual_hash); +} + +#[test] +fn test_hash_to_bn254_field_size_be() { + use solana_pubkey::Pubkey; + for _ in 0..10_000 { + let input_bytes = Pubkey::new_unique().to_bytes(); // Sample input + let hashed_value = hash_to_bn254_field_size_be(input_bytes.as_slice()); + assert!( + is_smaller_than_bn254_field_size_be(&hashed_value), + "Hashed value should be within BN254 field size" + ); + } + + let max_input = [u8::MAX; 32]; + let hashed_value = hash_to_bn254_field_size_be(max_input.as_slice()); + assert!( + is_smaller_than_bn254_field_size_be(&hashed_value), + "Hashed value should be within BN254 field size" + ); +} + +#[test] +fn test_hashv_to_bn254_field_size_be() { + use solana_pubkey::Pubkey; + for _ in 0..10_000 { + let input_bytes = [Pubkey::new_unique().to_bytes(); 4]; + let input_bytes = input_bytes.iter().map(|x| x.as_slice()).collect::>(); + let hashed_value = hashv_to_bn254_field_size_be(input_bytes.as_slice()); + assert!( + is_smaller_than_bn254_field_size_be(&hashed_value), + "Hashed value should be within BN254 field size" + ); + } + + let max_input = [[u8::MAX; 32]; 16]; + let max_input = max_input.iter().map(|x| x.as_slice()).collect::>(); + let hashed_value = hashv_to_bn254_field_size_be(max_input.as_slice()); + assert!( + is_smaller_than_bn254_field_size_be(&hashed_value), + "Hashed value should be within BN254 field size" + ); +} diff --git a/program-libs/hasher/tests/to_byte_array.rs b/program-libs/hasher/tests/to_byte_array.rs new file mode 100644 index 0000000000..71b2ad69e8 --- /dev/null +++ b/program-libs/hasher/tests/to_byte_array.rs @@ -0,0 +1,237 @@ +use light_hasher::{to_byte_array::ToByteArray, HasherError}; + +#[test] +fn test_to_byte_array_integers() { + // i8 tests + let i8_min_result = i8::MIN.to_byte_array().unwrap(); + let mut expected_i8_min = [0u8; 32]; + expected_i8_min[31] = 128; // i8::MIN.to_be_bytes() = [128] + assert_eq!(i8_min_result, expected_i8_min); + + let i8_max_result = i8::MAX.to_byte_array().unwrap(); + let mut expected_i8_max = [0u8; 32]; + expected_i8_max[31] = 127; // i8::MAX.to_be_bytes() = [127] + assert_eq!(i8_max_result, expected_i8_max); + + // u8 tests + let u8_min_result = u8::MIN.to_byte_array().unwrap(); + let mut expected_u8_min = [0u8; 32]; + expected_u8_min[31] = 0; // u8::MIN.to_be_bytes() = [0] + assert_eq!(u8_min_result, expected_u8_min); + + let u8_max_result = u8::MAX.to_byte_array().unwrap(); + let mut expected_u8_max = [0u8; 32]; + expected_u8_max[31] = 255; // u8::MAX.to_be_bytes() = [255] + assert_eq!(u8_max_result, expected_u8_max); + + // i16 tests + let i16_min_result = i16::MIN.to_byte_array().unwrap(); + let mut expected_i16_min = [0u8; 32]; + expected_i16_min[30..32].copy_from_slice(&i16::MIN.to_be_bytes()); // [128, 0] + assert_eq!(i16_min_result, expected_i16_min); + + let i16_max_result = i16::MAX.to_byte_array().unwrap(); + let mut expected_i16_max = [0u8; 32]; + expected_i16_max[30..32].copy_from_slice(&i16::MAX.to_be_bytes()); // [127, 255] + assert_eq!(i16_max_result, expected_i16_max); + + // u16 tests + let u16_min_result = u16::MIN.to_byte_array().unwrap(); + let mut expected_u16_min = [0u8; 32]; + expected_u16_min[30..32].copy_from_slice(&u16::MIN.to_be_bytes()); // [0, 0] + assert_eq!(u16_min_result, expected_u16_min); + + let u16_max_result = u16::MAX.to_byte_array().unwrap(); + let mut expected_u16_max = [0u8; 32]; + expected_u16_max[30..32].copy_from_slice(&u16::MAX.to_be_bytes()); // [255, 255] + assert_eq!(u16_max_result, expected_u16_max); + + // i32 tests + let i32_min_result = i32::MIN.to_byte_array().unwrap(); + let mut expected_i32_min = [0u8; 32]; + expected_i32_min[28..32].copy_from_slice(&i32::MIN.to_be_bytes()); // [128, 0, 0, 0] + assert_eq!(i32_min_result, expected_i32_min); + + let i32_max_result = i32::MAX.to_byte_array().unwrap(); + let mut expected_i32_max = [0u8; 32]; + expected_i32_max[28..32].copy_from_slice(&i32::MAX.to_be_bytes()); // [127, 255, 255, 255] + assert_eq!(i32_max_result, expected_i32_max); + + // u32 tests + let u32_min_result = u32::MIN.to_byte_array().unwrap(); + let mut expected_u32_min = [0u8; 32]; + expected_u32_min[28..32].copy_from_slice(&u32::MIN.to_be_bytes()); // [0, 0, 0, 0] + assert_eq!(u32_min_result, expected_u32_min); + + let u32_max_result = u32::MAX.to_byte_array().unwrap(); + let mut expected_u32_max = [0u8; 32]; + expected_u32_max[28..32].copy_from_slice(&u32::MAX.to_be_bytes()); // [255, 255, 255, 255] + assert_eq!(u32_max_result, expected_u32_max); + + // i64 tests + let i64_min_result = i64::MIN.to_byte_array().unwrap(); + let mut expected_i64_min = [0u8; 32]; + expected_i64_min[24..32].copy_from_slice(&i64::MIN.to_be_bytes()); // [128, 0, 0, 0, 0, 0, 0, 0] + assert_eq!(i64_min_result, expected_i64_min); + + let i64_max_result = i64::MAX.to_byte_array().unwrap(); + let mut expected_i64_max = [0u8; 32]; + expected_i64_max[24..32].copy_from_slice(&i64::MAX.to_be_bytes()); // [127, 255, 255, 255, 255, 255, 255, 255] + assert_eq!(i64_max_result, expected_i64_max); + + // u64 tests + let u64_min_result = u64::MIN.to_byte_array().unwrap(); + let mut expected_u64_min = [0u8; 32]; + expected_u64_min[24..32].copy_from_slice(&u64::MIN.to_be_bytes()); // [0, 0, 0, 0, 0, 0, 0, 0] + assert_eq!(u64_min_result, expected_u64_min); + + let u64_max_result = u64::MAX.to_byte_array().unwrap(); + let mut expected_u64_max = [0u8; 32]; + expected_u64_max[24..32].copy_from_slice(&u64::MAX.to_be_bytes()); // [255, 255, 255, 255, 255, 255, 255, 255] + assert_eq!(u64_max_result, expected_u64_max); + + // i128 tests + let i128_min_result = i128::MIN.to_byte_array().unwrap(); + let mut expected_i128_min = [0u8; 32]; + expected_i128_min[16..32].copy_from_slice(&i128::MIN.to_be_bytes()); // [128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + assert_eq!(i128_min_result, expected_i128_min); + + let i128_max_result = i128::MAX.to_byte_array().unwrap(); + let mut expected_i128_max = [0u8; 32]; + expected_i128_max[16..32].copy_from_slice(&i128::MAX.to_be_bytes()); // [127, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255] + assert_eq!(i128_max_result, expected_i128_max); + + // u128 tests + let u128_min_result = u128::MIN.to_byte_array().unwrap(); + let mut expected_u128_min = [0u8; 32]; + expected_u128_min[16..32].copy_from_slice(&u128::MIN.to_be_bytes()); // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + assert_eq!(u128_min_result, expected_u128_min); + + let u128_max_result = u128::MAX.to_byte_array().unwrap(); + let mut expected_u128_max = [0u8; 32]; + expected_u128_max[16..32].copy_from_slice(&u128::MAX.to_be_bytes()); // [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255] + assert_eq!(u128_max_result, expected_u128_max); +} + +#[test] +fn test_to_byte_array_primitives() { + // Test bool::to_byte_array + let bool_false_result = false.to_byte_array().unwrap(); + let mut expected_bool_false = [0u8; 32]; + expected_bool_false[31] = 0; + assert_eq!(bool_false_result, expected_bool_false); + + let bool_true_result = true.to_byte_array().unwrap(); + let mut expected_bool_true = [0u8; 32]; + expected_bool_true[31] = 1; + assert_eq!(bool_true_result, expected_bool_true); +} + +#[test] +fn test_to_byte_array_option() { + // Very important property - `None` and `Some(0)` always have to be + // different and should produce different hashes! + + // Test Option::to_byte_array + let u8_none: Option = None; + let u8_none_result = u8_none.to_byte_array().unwrap(); + assert_eq!(u8_none_result, [0u8; 32]); + + let u8_some_zero: Option = Some(0); + let u8_some_zero_result = u8_some_zero.to_byte_array().unwrap(); + let mut expected_u8_some_zero = [0u8; 32]; + expected_u8_some_zero[32 - std::mem::size_of::() - 1] = 1; // Mark as Some + assert_eq!(u8_some_zero_result, expected_u8_some_zero); + + // Test Option::to_byte_array + let u16_none: Option = None; + let u16_none_result = u16_none.to_byte_array().unwrap(); + assert_eq!(u16_none_result, [0u8; 32]); + + let u16_some_zero: Option = Some(0); + let u16_some_zero_result = u16_some_zero.to_byte_array().unwrap(); + let mut expected_u16_some_zero = [0u8; 32]; + expected_u16_some_zero[32 - std::mem::size_of::() - 1] = 1; // Mark as Some + assert_eq!(u16_some_zero_result, expected_u16_some_zero); + + // Test Option::to_byte_array + let u32_none: Option = None; + let u32_none_result = u32_none.to_byte_array().unwrap(); + assert_eq!(u32_none_result, [0u8; 32]); + + let u32_some_zero: Option = Some(0); + let u32_some_zero_result = u32_some_zero.to_byte_array().unwrap(); + let mut expected_u32_some_zero = [0u8; 32]; + expected_u32_some_zero[32 - std::mem::size_of::() - 1] = 1; // Mark as Some + assert_eq!(u32_some_zero_result, expected_u32_some_zero); + + // Test Option::to_byte_array + let u64_none: Option = None; + let u64_none_result = u64_none.to_byte_array().unwrap(); + assert_eq!(u64_none_result, [0u8; 32]); + + let u64_some_zero: Option = Some(0); + let u64_some_zero_result = u64_some_zero.to_byte_array().unwrap(); + let mut expected_u64_some_zero = [0u8; 32]; + expected_u64_some_zero[32 - std::mem::size_of::() - 1] = 1; // Mark as Some + assert_eq!(u64_some_zero_result, expected_u64_some_zero); + + // Test Option::to_byte_array + let u128_none: Option = None; + let u128_none_result = u128_none.to_byte_array().unwrap(); + assert_eq!(u128_none_result, [0u8; 32]); + + let u128_some_zero: Option = Some(0); + let u128_some_zero_result = u128_some_zero.to_byte_array().unwrap(); + let mut expected_u128_some_zero = [0u8; 32]; + expected_u128_some_zero[32 - std::mem::size_of::() - 1] = 1; // Mark as Some + assert_eq!(u128_some_zero_result, expected_u128_some_zero); +} + +#[test] +fn test_to_byte_array_u8_arrays() { + // Test with single element array + let single_element_arr: [u8; 1] = [255]; + let result = single_element_arr.to_byte_array().unwrap(); + let mut expected = [0u8; 32]; + expected[31] = 255; + assert_eq!(result, expected); + + // Test with multi-element array + let multi_element_arr: [u8; 4] = [1, 2, 3, 4]; + let result = multi_element_arr.to_byte_array().unwrap(); + let mut expected = [0u8; 32]; + expected[32 - 4..].copy_from_slice(&multi_element_arr); + assert_eq!(result, expected); + + // Test with full 32-byte array + let full_arr: [u8; 31] = [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, + ]; + let result = full_arr.to_byte_array().unwrap(); + assert_eq!(result[0], 0); + assert_eq!(&result[1..], full_arr.as_slice()); +} + +#[test] +fn test_to_byte_array_string() { + // Test with empty string + let empty_string = "".to_string(); + let result = empty_string.to_byte_array().unwrap(); + let expected = [0u8; 32]; + assert_eq!(result, expected); + + // Test with short string + let short_string = "foobar".to_string(); + let result = short_string.to_byte_array().unwrap(); + let mut expected = [0u8; 32]; + expected[32 - 6..].copy_from_slice(b"foobar"); + assert_eq!(result, expected); + + // Test with longer string that gets truncated + let long_string = "this is a string that is longer than 32 bytes and will be fail".to_string(); + let byte_len = long_string.len(); + let result = long_string.to_byte_array(); + assert_eq!(result, Err(HasherError::InvalidInputLength(31, byte_len))); +} diff --git a/program-libs/indexed-array/Cargo.toml b/program-libs/indexed-array/Cargo.toml index 18b01a319b..899ec562a4 100644 --- a/program-libs/indexed-array/Cargo.toml +++ b/program-libs/indexed-array/Cargo.toml @@ -13,5 +13,6 @@ num-traits = { workspace = true } thiserror = { workspace = true } [dev-dependencies] +light-hasher = { workspace = true, features = ["keccak", "sha256", "poseidon"] } rand = { workspace = true } num-bigint = { workspace = true, features = ["rand"] } diff --git a/program-libs/indexed-merkle-tree/Cargo.toml b/program-libs/indexed-merkle-tree/Cargo.toml index 0629fe0405..07a62ce56a 100644 --- a/program-libs/indexed-merkle-tree/Cargo.toml +++ b/program-libs/indexed-merkle-tree/Cargo.toml @@ -34,5 +34,6 @@ thiserror = { workspace = true } [dev-dependencies] light-hash-set = { workspace = true } +light-hasher = { workspace = true, features = ["keccak", "sha256", "poseidon"] } rand = { workspace = true } num-bigint = { workspace = true, features = ["rand"] } diff --git a/program-libs/macros/Cargo.toml b/program-libs/macros/Cargo.toml index 4e40757741..f905c1b1b1 100644 --- a/program-libs/macros/Cargo.toml +++ b/program-libs/macros/Cargo.toml @@ -9,13 +9,14 @@ edition = "2021" [features] default = ["solana"] solana = [] -pinocchio = [] +pinocchio = [] # No-op feature for backward compatibility [dependencies] bs58 = "0.5" proc-macro2 = { workspace = true } quote = { workspace = true } syn = { workspace = true } +solana-pubkey = { workspace = true, features = ["sha2", "curve25519"] } [lib] proc-macro = true diff --git a/sdk-libs/macros/src/cpi_signer.rs b/program-libs/macros/src/cpi_signer.rs similarity index 59% rename from sdk-libs/macros/src/cpi_signer.rs rename to program-libs/macros/src/cpi_signer.rs index d27403df1d..d8c6d71370 100644 --- a/sdk-libs/macros/src/cpi_signer.rs +++ b/program-libs/macros/src/cpi_signer.rs @@ -2,15 +2,30 @@ use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, LitStr}; +const CPI_AUTHORITY_SEED: &[u8] = b"cpi_authority"; + +/// Derives a Light Protocol CPI signer PDA at compile time +/// +/// This macro computes the CPI signer PDA using the "cpi_authority" seed +/// for the given program ID at compile time. +/// +/// ## Usage +/// +/// ```rust +/// # use light_macros::derive_light_cpi_signer_pda; +/// // Derive CPI signer for your program +/// const CPI_SIGNER_DATA: ([u8; 32], u8) = derive_light_cpi_signer_pda!("SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7"); +/// const CPI_SIGNER: [u8; 32] = CPI_SIGNER_DATA.0; +/// const CPI_SIGNER_BUMP: u8 = CPI_SIGNER_DATA.1; +/// ``` +/// +/// Returns a tuple of `([u8; 32], u8)` containing the derived PDA address and bump seed. pub fn derive_light_cpi_signer_pda(input: TokenStream) -> TokenStream { - // Parse the input - just a program ID string literal let program_id_lit = parse_macro_input!(input as LitStr); let program_id_str = program_id_lit.value(); - // Compute the PDA at compile time using solana-pubkey with "cpi_authority" seed - use std::str::FromStr; - // Parse program ID at compile time + use std::str::FromStr; let program_id = match solana_pubkey::Pubkey::from_str(&program_id_str) { Ok(id) => id, Err(_) => { @@ -24,7 +39,7 @@ pub fn derive_light_cpi_signer_pda(input: TokenStream) -> TokenStream { }; // Use fixed "cpi_authority" seed - let seeds = &[b"cpi_authority".as_slice()]; + let seeds = &[CPI_AUTHORITY_SEED]; // Compute the PDA at compile time let (pda, bump) = solana_pubkey::Pubkey::find_program_address(seeds, &program_id); @@ -42,15 +57,35 @@ pub fn derive_light_cpi_signer_pda(input: TokenStream) -> TokenStream { output.into() } +/// Derives a complete Light Protocol CPI signer configuration at compile time +/// +/// This macro computes the CPI signer configuration for the given program ID +/// at compile time. +/// +/// ## Usage +/// +/// ```rust +/// # use light_macros::derive_light_cpi_signer; +/// // Requires CpiSigner struct to be in scope +/// struct CpiSigner { +/// program_id: [u8; 32], +/// cpi_signer: [u8; 32], +/// bump: u8, +/// } +/// // In a Solana program +/// const LIGHT_CPI_SIGNER: CpiSigner = derive_light_cpi_signer!("SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7"); +/// ``` +/// +/// Returns a `CpiSigner` struct (must be in scope) containing: +/// - `program_id`: Program ID bytes +/// - `cpi_signer`: CPI signer PDA address +/// - `bump`: Bump seed pub fn derive_light_cpi_signer(input: TokenStream) -> TokenStream { - // Parse the input - just a program ID string literal let program_id_lit = parse_macro_input!(input as LitStr); let program_id_str = program_id_lit.value(); - // Compute the PDA at compile time using solana-pubkey with "cpi_authority" seed - use std::str::FromStr; - // Parse program ID at compile time + use std::str::FromStr; let program_id = match solana_pubkey::Pubkey::from_str(&program_id_str) { Ok(id) => id, Err(_) => { @@ -64,7 +99,7 @@ pub fn derive_light_cpi_signer(input: TokenStream) -> TokenStream { }; // Use fixed "cpi_authority" seed - let seeds = &[b"cpi_authority".as_slice()]; + let seeds = &[CPI_AUTHORITY_SEED]; // Compute the PDA at compile time let (pda, bump) = solana_pubkey::Pubkey::find_program_address(seeds, &program_id); @@ -81,13 +116,10 @@ pub fn derive_light_cpi_signer(input: TokenStream) -> TokenStream { .map(|b| proc_macro2::Literal::u8_unsuffixed(*b)); let output = quote! { - { - // Use the CpiSigner type with absolute path to avoid import dependency - ::light_sdk_types::CpiSigner { - program_id: [#(#program_id_literals),*], - cpi_signer: [#(#cpi_signer_literals),*], - bump: #bump, - } + CpiSigner { + program_id: [#(#program_id_literals),*], + cpi_signer: [#(#cpi_signer_literals),*], + bump: #bump, } }; diff --git a/program-libs/macros/src/lib.rs b/program-libs/macros/src/lib.rs index 588850f086..8cddf9dc9b 100644 --- a/program-libs/macros/src/lib.rs +++ b/program-libs/macros/src/lib.rs @@ -4,6 +4,7 @@ use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, parse_quote, ItemFn}; +mod cpi_signer; mod pubkey; /// Converts a base58 encoded public key into a byte array. @@ -56,3 +57,54 @@ pub fn heap_neutral(_: TokenStream, input: TokenStream) -> TokenStream { function.block.stmts.insert(len - 1, cleanup_code); TokenStream::from(quote! { #function }) } + +/// No-op derive macro that does nothing. +/// Used as a placeholder for serialization derives when not needed. +#[proc_macro_derive(Noop)] +pub fn derive_noop(_input: TokenStream) -> TokenStream { + TokenStream::new() +} + +/// Derives a Light Protocol CPI signer PDA at compile time +/// +/// This macro computes the CPI signer PDA using the "cpi_authority" seed +/// for the given program ID. Uses `solana_pubkey` with `solana` feature, +/// otherwise uses `pinocchio` (default). +/// +/// ## Usage +/// +/// ```rust +/// # use light_macros::derive_light_cpi_signer_pda; +/// // In a Solana program +/// let (cpi_signer, bump) = derive_light_cpi_signer_pda!("SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7"); +/// ``` +/// +/// Returns a tuple `([u8; 32], u8)` containing the PDA address and bump seed. +#[proc_macro] +pub fn derive_light_cpi_signer_pda(input: TokenStream) -> TokenStream { + cpi_signer::derive_light_cpi_signer_pda(input) +} + +/// Derives a complete Light Protocol CPI configuration at runtime +/// +/// This macro computes the program ID, CPI signer PDA, and bump seed +/// for the given program ID. Uses `solana_pubkey` with `solana` feature, +/// otherwise uses `pinocchio` (default). +/// +/// ## Usage +/// +/// ```rust +/// # use light_macros::derive_light_cpi_signer; +/// # struct CpiSigner { program_id: [u8; 32], cpi_signer: [u8; 32], bump: u8 } +/// // In a Solana program +/// const LIGHT_CPI_SIGNER: CpiSigner = derive_light_cpi_signer!("SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7"); +/// ``` +/// +/// Returns a `CpiSigner` struct (must be in scope) containing: +/// - `program_id`: Program ID bytes +/// - `cpi_signer`: CPI signer PDA address +/// - `bump`: Bump seed +#[proc_macro] +pub fn derive_light_cpi_signer(input: TokenStream) -> TokenStream { + cpi_signer::derive_light_cpi_signer(input) +} diff --git a/program-libs/verifier/Cargo.toml b/program-libs/verifier/Cargo.toml index f4d1c03545..374b4ee0a8 100644 --- a/program-libs/verifier/Cargo.toml +++ b/program-libs/verifier/Cargo.toml @@ -19,7 +19,7 @@ pinocchio = ["dep:pinocchio", "light-compressed-account/pinocchio"] [dependencies] groth16-solana = { workspace = true } thiserror = { workspace = true } -light-compressed-account = { workspace = true } +light-compressed-account = { workspace = true, features = ["std"] } # Optional import for ProgramError conversion solana-program-error = { workspace = true, optional = true } diff --git a/program-libs/zero-copy/Cargo.toml b/program-libs/zero-copy/Cargo.toml index 078b548e25..c8a35a333f 100644 --- a/program-libs/zero-copy/Cargo.toml +++ b/program-libs/zero-copy/Cargo.toml @@ -8,9 +8,10 @@ edition = "2021" [features] default = [] +alloc = [] +std = ["alloc"] solana = ["solana-program-error"] pinocchio = ["dep:pinocchio"] -std = [] derive = ["light-zero-copy-derive"] mut = ["light-zero-copy-derive/mut"] @@ -26,3 +27,4 @@ zerocopy = { workspace = true, features = ["derive"] } borsh = { workspace = true } trybuild = "1.0" light-zero-copy-derive = { workspace = true } +light-zero-copy = { workspace = true, features = ["std", "derive", "mut"] } diff --git a/program-libs/zero-copy/src/lib.rs b/program-libs/zero-copy/src/lib.rs index f725600455..973165fb26 100644 --- a/program-libs/zero-copy/src/lib.rs +++ b/program-libs/zero-copy/src/lib.rs @@ -1,4 +1,6 @@ #![no_std] +#[cfg(feature = "alloc")] +extern crate alloc; pub mod cyclic_vec; pub mod errors; @@ -8,15 +10,15 @@ pub mod slice; pub mod slice_mut; pub mod vec; use core::mem::{align_of, size_of}; -#[cfg(feature = "std")] +#[cfg(feature = "alloc")] pub mod traits; #[cfg(all(feature = "derive", feature = "mut"))] pub use light_zero_copy_derive::ZeroCopyMut; #[cfg(feature = "derive")] pub use light_zero_copy_derive::{ZeroCopy, ZeroCopyEq}; -#[cfg(feature = "std")] +#[cfg(feature = "alloc")] pub use traits::ZeroCopyNew; -#[cfg(feature = "std")] +#[cfg(feature = "alloc")] pub use traits::ZeroCopyStructInner; #[cfg(feature = "derive")] pub use zerocopy::{ diff --git a/program-libs/zero-copy/src/traits/mod.rs b/program-libs/zero-copy/src/traits/mod.rs index 360842ce02..749aea6f9e 100644 --- a/program-libs/zero-copy/src/traits/mod.rs +++ b/program-libs/zero-copy/src/traits/mod.rs @@ -1,9 +1,13 @@ +#[cfg(feature = "alloc")] mod vec_u8; mod zero_copy_at; mod zero_copy_at_mut; +#[cfg(feature = "alloc")] mod zero_copy_new; +#[cfg(feature = "alloc")] pub use vec_u8::VecU8; pub use zero_copy_at::{borsh_vec_u8_as_slice, ZeroCopyAt, ZeroCopyStructInner}; pub use zero_copy_at_mut::{borsh_vec_u8_as_slice_mut, ZeroCopyAtMut, ZeroCopyStructInnerMut}; +#[cfg(feature = "alloc")] pub use zero_copy_new::ZeroCopyNew; diff --git a/program-libs/zero-copy/src/traits/vec_u8.rs b/program-libs/zero-copy/src/traits/vec_u8.rs index 4b4f29ea9d..a41b55764d 100644 --- a/program-libs/zero-copy/src/traits/vec_u8.rs +++ b/program-libs/zero-copy/src/traits/vec_u8.rs @@ -1,5 +1,6 @@ +#[cfg(feature = "alloc")] +use alloc::vec::Vec; use core::ops::{Deref, DerefMut}; -use std::vec::Vec; use zerocopy::Ref; @@ -8,15 +9,18 @@ use crate::{ traits::{ZeroCopyAt, ZeroCopyAtMut}, }; +#[cfg(feature = "alloc")] #[derive(Clone, Debug, Default, PartialEq)] pub struct VecU8(Vec); +#[cfg(feature = "alloc")] impl VecU8 { pub fn new() -> Self { Self(Vec::new()) } } +#[cfg(feature = "alloc")] impl Deref for VecU8 { type Target = Vec; fn deref(&self) -> &Self::Target { @@ -24,12 +28,14 @@ impl Deref for VecU8 { } } +#[cfg(feature = "alloc")] impl DerefMut for VecU8 { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } +#[cfg(feature = "alloc")] impl<'a, T: ZeroCopyAt<'a>> ZeroCopyAt<'a> for VecU8 { type ZeroCopyAt = Vec; @@ -47,6 +53,7 @@ impl<'a, T: ZeroCopyAt<'a>> ZeroCopyAt<'a> for VecU8 { } } +#[cfg(feature = "alloc")] impl<'a, T: ZeroCopyAtMut<'a>> ZeroCopyAtMut<'a> for VecU8 { type ZeroCopyAtMut = Vec; diff --git a/program-libs/zero-copy/src/traits/zero_copy_at.rs b/program-libs/zero-copy/src/traits/zero_copy_at.rs index b6d88adf0a..2f394cb912 100644 --- a/program-libs/zero-copy/src/traits/zero_copy_at.rs +++ b/program-libs/zero-copy/src/traits/zero_copy_at.rs @@ -1,5 +1,6 @@ +#[cfg(feature = "alloc")] +use alloc::vec::Vec; use core::mem::size_of; -use std::vec::Vec; use zerocopy::{ little_endian::{I16, I32, I64, U16, U32, U64}, @@ -113,6 +114,7 @@ impl<'a, T: KnownLayout + Immutable + FromBytes, const N: usize> ZeroCopyAt<'a> } } +#[cfg(feature = "alloc")] impl<'a, T: ZeroCopyAt<'a>> ZeroCopyAt<'a> for Vec { type ZeroCopyAt = Vec; #[inline] diff --git a/program-libs/zero-copy/src/traits/zero_copy_at_mut.rs b/program-libs/zero-copy/src/traits/zero_copy_at_mut.rs index 724207762c..a7bb50d6d1 100644 --- a/program-libs/zero-copy/src/traits/zero_copy_at_mut.rs +++ b/program-libs/zero-copy/src/traits/zero_copy_at_mut.rs @@ -1,5 +1,6 @@ +#[cfg(feature = "alloc")] +use alloc::vec::Vec; use core::mem::size_of; -use std::vec::Vec; use zerocopy::{ little_endian::{I16, I32, I64, U16, U32, U64}, @@ -89,6 +90,7 @@ impl<'a, T: KnownLayout + Immutable + FromBytes> ZeroCopyAtMut<'a> for Ref<&'a m } } +#[cfg(feature = "alloc")] impl<'a, T: ZeroCopyAtMut<'a>> ZeroCopyAtMut<'a> for Vec { type ZeroCopyAtMut = Vec; #[inline] @@ -182,6 +184,7 @@ impl ZeroCopyStructInnerMut for U16 { type ZeroCopyInnerMut = U16; } +#[cfg(feature = "alloc")] impl ZeroCopyStructInnerMut for Vec { type ZeroCopyInnerMut = Vec; } diff --git a/program-libs/zero-copy/src/traits/zero_copy_new.rs b/program-libs/zero-copy/src/traits/zero_copy_new.rs index 69ce57bd46..f7c32258cf 100644 --- a/program-libs/zero-copy/src/traits/zero_copy_new.rs +++ b/program-libs/zero-copy/src/traits/zero_copy_new.rs @@ -1,5 +1,6 @@ +#[cfg(feature = "alloc")] +use alloc::vec::Vec; use core::mem::size_of; -use std::vec::Vec; use crate::{errors::ZeroCopyError, traits::ZeroCopyAtMut}; @@ -234,6 +235,7 @@ impl<'a> ZeroCopyNew<'a> for zerocopy::little_endian::U64 { } // Implementation for Vec +#[cfg(feature = "alloc")] impl<'a, T: ZeroCopyNew<'a>> ZeroCopyNew<'a> for Vec { type ZeroCopyConfig = Vec; // Vector of configs for each item type Output = Vec; diff --git a/program-libs/zero-copy/tests/borsh.rs b/program-libs/zero-copy/tests/borsh.rs index 427f52b7d3..f5c9bceedb 100644 --- a/program-libs/zero-copy/tests/borsh.rs +++ b/program-libs/zero-copy/tests/borsh.rs @@ -285,13 +285,13 @@ pub struct ArrayStruct { } #[test] -fn test_array_struct() -> Result<(), Box> { +fn test_array_struct() { let array_struct = ArrayStruct { a: [1u8; 32], b: [2u8; 64], c: [3u8; 32], }; - let bytes = array_struct.try_to_vec()?; + let bytes = array_struct.try_to_vec().unwrap(); let (zero_copy, remaining) = ArrayStruct::zero_copy_at(&bytes).unwrap(); assert_eq!(zero_copy.a, [1u8; 32]); @@ -299,7 +299,6 @@ fn test_array_struct() -> Result<(), Box> { assert_eq!(zero_copy.c, [3u8; 32]); assert_eq!(zero_copy, array_struct); assert_eq!(remaining, &[]); - Ok(()) } #[repr(C)] diff --git a/program-tests/account-compression-test/Cargo.toml b/program-tests/account-compression-test/Cargo.toml index 1629a9701a..5c5fb01c91 100644 --- a/program-tests/account-compression-test/Cargo.toml +++ b/program-tests/account-compression-test/Cargo.toml @@ -38,7 +38,7 @@ light-concurrent-merkle-tree = { workspace = true } light-indexed-merkle-tree = { workspace = true } light-merkle-tree-reference = { workspace = true } light-bounded-vec = { workspace = true } -light-compressed-account = { workspace = true } +light-compressed-account = { workspace = true, features = ["std"] } light-verifier = { workspace = true } rand = { workspace = true } solana-sdk = { workspace = true } diff --git a/program-tests/compressed-token-test/Cargo.toml b/program-tests/compressed-token-test/Cargo.toml index f5128951f6..4ef9ec9580 100644 --- a/program-tests/compressed-token-test/Cargo.toml +++ b/program-tests/compressed-token-test/Cargo.toml @@ -25,7 +25,7 @@ anchor-lang = { workspace = true } light-compressed-token = { workspace = true } light-system-program-anchor = { workspace = true } account-compression = { workspace = true } -light-compressed-account = { workspace = true } +light-compressed-account = { workspace = true, features = ["std"] } light-batched-merkle-tree = { workspace = true } light-registry = { workspace = true } solana-sdk = { workspace = true } diff --git a/program-tests/create-address-test-program/src/lib.rs b/program-tests/create-address-test-program/src/lib.rs index f272cf581c..cfba3c54be 100644 --- a/program-tests/create-address-test-program/src/lib.rs +++ b/program-tests/create-address-test-program/src/lib.rs @@ -28,6 +28,31 @@ use light_sdk::{ v2::lowlevel::to_account_metas, }, }; +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug, PartialEq)] +pub struct CpiAccountsConfigLocal { + pub cpi_context: bool, + pub sol_compression_recipient: bool, + pub sol_pool_pda: bool, +} +impl From for CpiAccountsConfig { + fn from(config: CpiAccountsConfigLocal) -> Self { + CpiAccountsConfig { + cpi_context: config.cpi_context, + sol_compression_recipient: config.sol_compression_recipient, + sol_pool_pda: config.sol_pool_pda, + cpi_signer: LIGHT_CPI_SIGNER, + } + } +} +impl From for CpiAccountsConfigLocal { + fn from(config: CpiAccountsConfig) -> Self { + CpiAccountsConfigLocal { + cpi_context: config.cpi_context, + sol_compression_recipient: config.sol_compression_recipient, + sol_pool_pda: config.sol_pool_pda, + } + } +} declare_id!("FNt7byTHev1k5x2cXZLBr8TdWiC3zoP5vcnZR4P682Uy"); @@ -63,7 +88,7 @@ pub mod system_cpi_test { /// Test wrapper, for with read-only and with account info instructions. pub fn invoke_with_read_only<'info>( ctx: Context<'_, '_, '_, 'info, InvokeCpiReadOnly<'info>>, - config: CpiAccountsConfig, + config: CpiAccountsConfigLocal, v2_ix: bool, inputs: Vec, write_cpi_context: bool, @@ -72,12 +97,16 @@ pub mod system_cpi_test { let (account_infos, account_metas) = if v2_ix { let cpi_accounts = - CpiAccounts::new_with_config(&fee_payer, ctx.remaining_accounts, config); + CpiAccounts::new_with_config(&fee_payer, ctx.remaining_accounts, config.into()); let account_infos = cpi_accounts.to_account_infos(); let account_metas = if !write_cpi_context { to_account_metas(&cpi_accounts).map_err(|_| ErrorCode::AccountNotEnoughKeys)? } else { + require!( + ctx.remaining_accounts.len() >= 3, + ErrorCode::AccountNotEnoughKeys + ); let mut account_metas = vec![]; account_metas.push(AccountMeta { pubkey: *cpi_accounts.fee_payer().key, @@ -101,7 +130,7 @@ pub mod system_cpi_test { } else { use light_sdk::cpi::v1::CpiAccounts; let cpi_accounts = - CpiAccounts::new_with_config(&fee_payer, ctx.remaining_accounts, config); + CpiAccounts::new_with_config(&fee_payer, ctx.remaining_accounts, config.into()); let account_infos = cpi_accounts.to_account_infos(); @@ -242,7 +271,7 @@ pub fn create_invoke_read_only_account_info_instruction( let ix_data = crate::instruction::InvokeWithReadOnly { v2_ix, inputs, - config, + config: config.into(), write_cpi_context, } .data(); @@ -258,8 +287,11 @@ pub fn to_account_metas_small( cpi_accounts: CpiAccounts<'_, '_>, ) -> light_sdk::error::Result> { // TODO: do a version with a const array instead of vector. - let mut account_metas = - Vec::with_capacity(1 + cpi_accounts.account_infos().len() - PROGRAM_ACCOUNTS_LEN); + let extra = cpi_accounts + .account_infos() + .len() + .saturating_sub(PROGRAM_ACCOUNTS_LEN); + let mut account_metas = Vec::with_capacity(1 + extra); account_metas.push(AccountMeta { pubkey: *cpi_accounts.fee_payer().key, diff --git a/program-tests/pinocchio-nostd-test/Cargo.toml b/program-tests/pinocchio-nostd-test/Cargo.toml new file mode 100644 index 0000000000..6142cb2210 --- /dev/null +++ b/program-tests/pinocchio-nostd-test/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "pinocchio-nostd-test" +version = "0.1.0" +description = "Test program using pinocchio and light-sdk-pinocchio in no_std mode" +repository = "https://github.com/Lightprotocol/light-protocol" +license = "Apache-2.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib", "lib"] +name = "pinocchio_nostd_test" + +[features] +no-entrypoint = [] +test-sbf = [] +default = [] + +[dependencies] +# Pinocchio is no_std by default +pinocchio = { workspace = true } +# Light SDK dependencies - none have std in their default features +light-sdk-pinocchio = { workspace = true, features = ["v2"] } +light-sdk-types = { workspace = true, features = ["v2"] } +# Don't include poseidon to avoid light-poseidon -> thiserror v1.0 with std +light-hasher = { workspace = true } +light-macros = { workspace = true, features = ["pinocchio"] } +borsh = { workspace = true } + +[dev-dependencies] +light-program-test = { workspace = true, features = ["devenv"] } +tokio = { workspace = true } +solana-sdk = { workspace = true } +light-hasher = { workspace = true, features = ["solana", "poseidon"] } +light-compressed-account = { workspace = true, features = ["solana"] } +light-sdk = { workspace = true, features = ["v2"] } + +[lints.rust.unexpected_cfgs] +level = "allow" +check-cfg = [ + 'cfg(target_os, values("solana"))', + 'cfg(feature, values("frozen-abi", "no-entrypoint"))', +] diff --git a/program-tests/pinocchio-nostd-test/build.rs b/program-tests/pinocchio-nostd-test/build.rs new file mode 100644 index 0000000000..090d352892 --- /dev/null +++ b/program-tests/pinocchio-nostd-test/build.rs @@ -0,0 +1,16 @@ +fn main() { + // Verify we're building for the Solana BPF target or running tests + let target = std::env::var("TARGET").unwrap_or_default(); + + // When building for Solana, verify we're in no_std mode + if target.contains("bpf") || target.contains("sbf") { + // This will cause a compile error if std is somehow enabled + // for the Solana target + if cfg!(feature = "std") { + panic!("FATAL: std feature is enabled for no_std target!"); + } + } + + // Set a cfg flag to indicate we're in strict no_std mode + println!("cargo:rustc-cfg=strict_nostd"); +} diff --git a/program-tests/pinocchio-nostd-test/src/lib.rs b/program-tests/pinocchio-nostd-test/src/lib.rs new file mode 100644 index 0000000000..4a85f900b4 --- /dev/null +++ b/program-tests/pinocchio-nostd-test/src/lib.rs @@ -0,0 +1,78 @@ +#![no_std] +#![deny(warnings)] +#![cfg_attr(target_os = "solana", forbid(unsafe_code))] +// Ensure we're truly no_std by forbidding these +#![cfg_attr(not(test), no_main)] + +use light_macros::pubkey_array; +use light_sdk_pinocchio::{derive_light_cpi_signer, error::LightSdkError, CpiSigner}; +use pinocchio::{ + account_info::AccountInfo, entrypoint, program_error::ProgramError, pubkey::Pubkey, +}; + +pub const ID: Pubkey = pubkey_array!("NoStDPinocchio11111111111111111111111111111"); +pub const LIGHT_CPI_SIGNER: CpiSigner = + derive_light_cpi_signer!("NoStDPinocchio11111111111111111111111111111"); + +// Use the pinocchio entrypoint! macro which sets up: +// - Program entrypoint +// - Default bump allocator +// - Default panic handler +entrypoint!(process_instruction); + +#[repr(u8)] +pub enum InstructionType { + TestBasic = 0, +} + +impl TryFrom for InstructionType { + type Error = LightSdkError; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(InstructionType::TestBasic), + _ => panic!("Invalid instruction discriminator."), + } + } +} + +pub fn process_instruction( + _program_id: &Pubkey, + _accounts: &[AccountInfo], + instruction_data: &[u8], +) -> Result<(), ProgramError> { + let discriminator = InstructionType::try_from(instruction_data[0])?; + match discriminator { + InstructionType::TestBasic => { + // Basic test - just verify we can execute in no_std environment + Ok(()) + } + } +} + +// Compile-time assertion that we're truly no_std +// This will fail to compile if std is somehow available +#[cfg(all(target_os = "solana", feature = "std"))] +compile_error!("std feature must not be enabled for Solana target!"); + +// Verify that the std crate is not available +#[cfg(target_os = "solana")] +const _: () = { + // This would fail to compile if std was available + // because we've declared #![no_std] at the crate level + #[cfg(feature = "std")] + compile_error!("ERROR: std is available in a no_std crate!"); +}; + +#[cfg(not(target_os = "solana"))] +pub mod test_helpers { + use super::*; + + pub fn get_program_id() -> Pubkey { + ID + } + + pub fn get_cpi_signer() -> CpiSigner { + LIGHT_CPI_SIGNER + } +} diff --git a/program-tests/pinocchio-nostd-test/tests/basic.rs b/program-tests/pinocchio-nostd-test/tests/basic.rs new file mode 100644 index 0000000000..cec5178ea5 --- /dev/null +++ b/program-tests/pinocchio-nostd-test/tests/basic.rs @@ -0,0 +1,33 @@ +#![cfg(feature = "test-sbf")] + +use light_program_test::{program_test::LightProgramTest, ProgramTestConfig, Rpc}; +use pinocchio_nostd_test::test_helpers::get_program_id; +use solana_sdk::{instruction::Instruction, pubkey::Pubkey, signature::Signer}; + +#[tokio::test] +async fn test_nostd_basic() { + let config = ProgramTestConfig::new_v2( + false, + Some(vec![( + "pinocchio_nostd_test", + Pubkey::new_from_array(get_program_id()), + )]), + ); + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + // Create a basic instruction + let instruction_data = vec![0u8]; // InstructionType::TestBasic + let ix = Instruction { + program_id: Pubkey::new_from_array(get_program_id()), + accounts: vec![], + data: instruction_data, + }; + + // Execute the instruction + let result = rpc + .create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) + .await; + + assert!(result.is_ok(), "Transaction should succeed"); +} diff --git a/program-tests/registry-test/Cargo.toml b/program-tests/registry-test/Cargo.toml index f815f994dd..93ce8cdd95 100644 --- a/program-tests/registry-test/Cargo.toml +++ b/program-tests/registry-test/Cargo.toml @@ -31,7 +31,7 @@ forester-utils = { workspace = true } light-registry = { workspace = true } account-compression = { workspace = true } light-hasher = { workspace = true, features = ["poseidon"] } -light-compressed-account = { workspace = true } +light-compressed-account = { workspace = true, features = ["std"] } solana-sdk = { workspace = true } serial_test = { workspace = true } light-batched-merkle-tree = { workspace = true } diff --git a/program-tests/system-cpi-test/src/sdk.rs b/program-tests/system-cpi-test/src/sdk.rs index 7abb189bf8..8914ff7b74 100644 --- a/program-tests/system-cpi-test/src/sdk.rs +++ b/program-tests/system-cpi-test/src/sdk.rs @@ -8,7 +8,6 @@ use account_compression::{ }; use anchor_lang::{InstructionData, ToAccountMetas}; use light_compressed_account::{ - address::{pack_new_address_params, pack_read_only_accounts, pack_read_only_address_params}, compressed_account::{ CompressedAccountWithMerkleContext, PackedCompressedAccountWithMerkleContext, ReadOnlyCompressedAccount, @@ -22,7 +21,13 @@ use light_compressed_token::{ get_token_pool_pda, process_transfer::transfer_sdk::to_account_metas, }; use light_system_program::utils::get_registered_program_pda; -use light_test_utils::e2e_test_env::to_account_metas_light; +use light_test_utils::{ + compressed_account_pack::{ + pack_compressed_account, pack_new_address_params, pack_read_only_accounts, + pack_read_only_address_params, + }, + e2e_test_env::to_account_metas_light, +}; use solana_sdk::{instruction::Instruction, pubkey::Pubkey}; use crate::CreatePdaMode; @@ -76,7 +81,8 @@ pub fn create_pda_instruction(input_params: CreateCompressedPdaInstructionInputs .iter() .enumerate() .map(|(i, x)| { - x.pack( + pack_compressed_account( + x, input_params.state_roots.as_ref().unwrap()[i], &mut remaining_accounts, ) diff --git a/program-tests/system-cpi-v2-test/Cargo.toml b/program-tests/system-cpi-v2-test/Cargo.toml index 479d3a4562..1b975e3656 100644 --- a/program-tests/system-cpi-v2-test/Cargo.toml +++ b/program-tests/system-cpi-v2-test/Cargo.toml @@ -31,6 +31,7 @@ light-account-checks = { workspace = true } [target.'cfg(not(target_os = "solana"))'.dependencies] solana-sdk = { workspace = true } light-client = { workspace = true, features = ["devenv"] } +light-event = { workspace = true } light-sdk = { workspace = true, features = ["anchor"] } light-sdk-types = { workspace = true } light-program-test = { workspace = true, features = ["devenv"] } diff --git a/program-tests/system-cpi-v2-test/tests/event.rs b/program-tests/system-cpi-v2-test/tests/event.rs index c96746fc02..47625f9ef9 100644 --- a/program-tests/system-cpi-v2-test/tests/event.rs +++ b/program-tests/system-cpi-v2-test/tests/event.rs @@ -15,10 +15,6 @@ use light_compressed_account::{ CompressedAccount, CompressedAccountData, CompressedAccountWithMerkleContext, MerkleContext, PackedCompressedAccountWithMerkleContext, }, - indexer_event::event::{ - BatchNullifyContext, BatchPublicTransactionEvent, MerkleTreeSequenceNumber, - MerkleTreeSequenceNumberV1, NewAddress, PublicTransactionEvent, - }, instruction_data::{ compressed_proof::CompressedProof, data::{OutputCompressedAccountWithContext, OutputCompressedAccountWithPackedContext}, @@ -29,6 +25,10 @@ use light_compressed_account::{ TreeType, }; use light_compressed_token::process_transfer::transfer_sdk::to_account_metas; +use light_event::event::{ + BatchNullifyContext, BatchPublicTransactionEvent, MerkleTreeSequenceNumber, + MerkleTreeSequenceNumberV1, NewAddress, PublicTransactionEvent, +}; use light_program_test::{ accounts::test_accounts::TestAccounts, LightProgramTest, ProgramTestConfig, }; diff --git a/program-tests/system-cpi-v2-test/tests/invoke_cpi_with_read_only.rs b/program-tests/system-cpi-v2-test/tests/invoke_cpi_with_read_only.rs index 8472abdb7a..00aad1a13c 100644 --- a/program-tests/system-cpi-v2-test/tests/invoke_cpi_with_read_only.rs +++ b/program-tests/system-cpi-v2-test/tests/invoke_cpi_with_read_only.rs @@ -2952,7 +2952,6 @@ pub mod local_sdk { CompressedAccountWithMerkleContext, MerkleContext, PackedCompressedAccountWithMerkleContext, ReadOnlyCompressedAccount, }, - indexer_event::event::BatchPublicTransactionEvent, instruction_data::{ compressed_proof::CompressedProof, cpi_context::CompressedCpiContext, @@ -2962,6 +2961,7 @@ pub mod local_sdk { }, }; use light_compressed_token::process_transfer::transfer_sdk::to_account_metas; + use light_event::event::BatchPublicTransactionEvent; use light_program_test::indexer::TestIndexerExtensions; use light_sdk::{ address::{NewAddressParamsAssigned, ReadOnlyAddress}, diff --git a/program-tests/utils/Cargo.toml b/program-tests/utils/Cargo.toml index db00f731a0..056f368cc4 100644 --- a/program-tests/utils/Cargo.toml +++ b/program-tests/utils/Cargo.toml @@ -26,12 +26,13 @@ light-system-program-anchor = { workspace = true, features = ["cpi"] } light-registry = { workspace = true, features = ["cpi"] } spl-token = { workspace = true, features = ["no-entrypoint"] } light-prover-client = { workspace = true, features = ["devenv"] } -light-hasher = { workspace = true, features = ["poseidon"] } +light-hasher = { workspace = true, features = ["poseidon", "sha256", "keccak", "std"] } light-merkle-tree-reference = { workspace = true } light-concurrent-merkle-tree = { workspace = true } light-indexed-merkle-tree = { workspace = true } light-indexed-array = { workspace = true } -light-compressed-account = { workspace = true } +light-compressed-account = { workspace = true, features = ["std"] } +light-event = { workspace = true } light-program-test = { workspace = true, features = ["devenv"] } forester-utils = { workspace = true, features = ["devenv"] } light-sdk = { workspace = true, features = ["anchor"] } diff --git a/program-tests/utils/src/assert_compressed_tx.rs b/program-tests/utils/src/assert_compressed_tx.rs index 6f0595d1ca..b983f4c233 100644 --- a/program-tests/utils/src/assert_compressed_tx.rs +++ b/program-tests/utils/src/assert_compressed_tx.rs @@ -13,9 +13,9 @@ use light_client::{ }; use light_compressed_account::{ compressed_account::{CompressedAccount, CompressedAccountWithMerkleContext}, - indexer_event::event::{MerkleTreeSequenceNumber, PublicTransactionEvent}, TreeType, }; +use light_event::event::{MerkleTreeSequenceNumber, PublicTransactionEvent}; use light_hasher::Poseidon; use light_program_test::indexer::TestIndexerExtensions; use num_bigint::BigUint; diff --git a/program-tests/utils/src/assert_token_tx.rs b/program-tests/utils/src/assert_token_tx.rs index 0934d869bd..f59326447c 100644 --- a/program-tests/utils/src/assert_token_tx.rs +++ b/program-tests/utils/src/assert_token_tx.rs @@ -1,10 +1,8 @@ use anchor_lang::AnchorSerialize; use light_client::{indexer::Indexer, rpc::Rpc}; -use light_compressed_account::{ - compressed_account::CompressedAccountWithMerkleContext, - indexer_event::event::PublicTransactionEvent, -}; +use light_compressed_account::compressed_account::CompressedAccountWithMerkleContext; use light_compressed_token::process_transfer::{get_cpi_authority_pda, TokenTransferOutputData}; +use light_event::event::PublicTransactionEvent; use light_program_test::indexer::TestIndexerExtensions; use light_sdk::token::TokenDataWithMerkleContext; use solana_sdk::{program_pack::Pack, pubkey::Pubkey}; diff --git a/program-tests/utils/src/compressed_account_pack.rs b/program-tests/utils/src/compressed_account_pack.rs new file mode 100644 index 0000000000..46558b0d02 --- /dev/null +++ b/program-tests/utils/src/compressed_account_pack.rs @@ -0,0 +1,326 @@ +use std::collections::HashMap; + +use light_compressed_account::{ + compressed_account::{ + CompressedAccount, CompressedAccountWithMerkleContext, + PackedCompressedAccountWithMerkleContext, PackedReadOnlyCompressedAccount, + ReadOnlyCompressedAccount, + }, + instruction_data::data::OutputCompressedAccountWithPackedContext, + CompressedAccountError, Pubkey, +}; +use light_sdk::{ + address::{ + NewAddressParams, NewAddressParamsAssigned, NewAddressParamsAssignedPacked, + PackedReadOnlyAddress, ReadOnlyAddress, + }, + instruction::{MerkleContext, PackedMerkleContext}, +}; + +pub fn pack_compressed_account( + account: &CompressedAccountWithMerkleContext, + root_index: Option, + remaining_accounts: &mut HashMap, +) -> Result { + Ok(PackedCompressedAccountWithMerkleContext { + compressed_account: account.compressed_account.clone(), + merkle_context: PackedMerkleContext { + merkle_tree_pubkey_index: pack_account( + &account.merkle_context.merkle_tree_pubkey, + remaining_accounts, + ), + queue_pubkey_index: pack_account( + &account.merkle_context.queue_pubkey, + remaining_accounts, + ), + leaf_index: account.merkle_context.leaf_index, + prove_by_index: root_index.is_none(), + }, + root_index: root_index.unwrap_or_default(), + read_only: false, + }) +} +pub fn pack_pubkey_usize(pubkey: &Pubkey, hash_set: &mut HashMap) -> u8 { + match hash_set.get(pubkey) { + Some(index) => (*index) as u8, + None => { + let index = hash_set.len(); + hash_set.insert(*pubkey, index); + index as u8 + } + } +} +pub fn pack_compressed_accounts( + compressed_accounts: &[CompressedAccountWithMerkleContext], + root_indices: &[Option], + remaining_accounts: &mut HashMap, +) -> Vec { + compressed_accounts + .iter() + .zip(root_indices.iter()) + .map(|(x, root_index)| { + let mut merkle_context = x.merkle_context; + let root_index = if let Some(root) = root_index { + *root + } else { + merkle_context.prove_by_index = true; + 0 + }; + + PackedCompressedAccountWithMerkleContext { + compressed_account: x.compressed_account.clone(), + merkle_context: pack_merkle_context(&[merkle_context], remaining_accounts)[0], + root_index, + read_only: false, + } + }) + .collect::>() +} + +pub fn pack_output_compressed_accounts( + compressed_accounts: &[CompressedAccount], + merkle_trees: &[Pubkey], + remaining_accounts: &mut HashMap, +) -> Vec { + compressed_accounts + .iter() + .zip(merkle_trees.iter()) + .map(|(x, tree)| OutputCompressedAccountWithPackedContext { + compressed_account: x.clone(), + merkle_tree_index: pack_account(tree, remaining_accounts), + }) + .collect::>() +} + +pub fn pack_merkle_context( + merkle_context: &[MerkleContext], + remaining_accounts: &mut HashMap, +) -> Vec { + merkle_context + .iter() + .map(|merkle_context| PackedMerkleContext { + leaf_index: merkle_context.leaf_index, + merkle_tree_pubkey_index: pack_account( + &merkle_context.merkle_tree_pubkey, + remaining_accounts, + ), + queue_pubkey_index: pack_account(&merkle_context.queue_pubkey, remaining_accounts), + prove_by_index: merkle_context.prove_by_index, + }) + .collect::>() +} +// Helper function to pack new address params for instruction data in rust clients +pub fn pack_new_address_params( + new_address_params: &[NewAddressParams], + remaining_accounts: &mut HashMap, +) -> Vec { + let mut new_address_params_packed = new_address_params + .iter() + .map( + |x| light_compressed_account::instruction_data::data::NewAddressParamsPacked { + seed: x.seed, + address_merkle_tree_root_index: x.address_merkle_tree_root_index, + address_merkle_tree_account_index: 0, // will be assigned later + address_queue_account_index: 0, // will be assigned later + }, + ) + .collect::>(); + let mut next_index: usize = remaining_accounts.len(); + for (i, params) in new_address_params.iter().enumerate() { + match remaining_accounts.get(¶ms.address_merkle_tree_pubkey) { + Some(_) => {} + None => { + remaining_accounts.insert(params.address_merkle_tree_pubkey, next_index); + next_index += 1; + } + }; + new_address_params_packed[i].address_merkle_tree_account_index = *remaining_accounts + .get(¶ms.address_merkle_tree_pubkey) + .unwrap() + as u8; + } + + for (i, params) in new_address_params.iter().enumerate() { + match remaining_accounts.get(¶ms.address_queue_pubkey) { + Some(_) => {} + None => { + remaining_accounts.insert(params.address_queue_pubkey, next_index); + next_index += 1; + } + }; + new_address_params_packed[i].address_queue_account_index = *remaining_accounts + .get(¶ms.address_queue_pubkey) + .unwrap() as u8; + } + new_address_params_packed +} + +pub fn add_and_get_remaining_account_indices( + pubkeys: &[Pubkey], + remaining_accounts: &mut HashMap, +) -> Vec { + let mut vec = Vec::new(); + let mut next_index: usize = remaining_accounts.len(); + for pubkey in pubkeys.iter() { + match remaining_accounts.get(pubkey) { + Some(_) => {} + None => { + remaining_accounts.insert(*pubkey, next_index); + next_index += 1; + } + }; + vec.push(*remaining_accounts.get(pubkey).unwrap() as u8); + } + vec +} + +pub fn pack_new_address_params_assigned( + new_address_params: &[NewAddressParamsAssigned], + remaining_accounts: &mut HashMap, +) -> Vec { + let mut vec = Vec::new(); + for new_address_param in new_address_params.iter() { + let address_merkle_tree_account_index = pack_pubkey_usize( + &new_address_param.address_merkle_tree_pubkey, + remaining_accounts, + ); + let address_queue_account_index = + pack_pubkey_usize(&new_address_param.address_queue_pubkey, remaining_accounts); + vec.push(NewAddressParamsAssignedPacked { + seed: new_address_param.seed, + address_queue_account_index, + address_merkle_tree_root_index: new_address_param.address_merkle_tree_root_index, + address_merkle_tree_account_index, + assigned_to_account: new_address_param.assigned_account_index.is_some(), + assigned_account_index: new_address_param.assigned_account_index.unwrap_or_default(), + }); + } + + vec +} + +pub fn pack_read_only_address_params( + new_address_params: &[ReadOnlyAddress], + remaining_accounts: &mut HashMap, +) -> Vec { + new_address_params + .iter() + .map(|x| PackedReadOnlyAddress { + address: x.address, + address_merkle_tree_root_index: x.address_merkle_tree_root_index, + address_merkle_tree_account_index: pack_account( + &x.address_merkle_tree_pubkey, + remaining_accounts, + ), + }) + .collect::>() +} + +pub fn pack_account(pubkey: &Pubkey, remaining_accounts: &mut HashMap) -> u8 { + match remaining_accounts.get(pubkey) { + Some(index) => *index as u8, + None => { + let next_index = remaining_accounts.len(); + remaining_accounts.insert(*pubkey, next_index); + next_index as u8 + } + } +} + +pub fn pack_read_only_accounts( + accounts: &[ReadOnlyCompressedAccount], + remaining_accounts: &mut HashMap, +) -> Vec { + accounts + .iter() + .map(|x| PackedReadOnlyCompressedAccount { + account_hash: x.account_hash, + merkle_context: pack_merkle_context(&[x.merkle_context], remaining_accounts)[0], + root_index: x.root_index, + }) + .collect::>() +} + +#[cfg(test)] +mod tests { + + use light_compressed_account::address::derive_address_legacy; + + use super::*; + + #[test] + fn test_derive_address_with_valid_input() { + let merkle_tree_pubkey = Pubkey::new_unique(); + let seeds = [1u8; 32]; + let result = derive_address_legacy(&merkle_tree_pubkey, &seeds); + let result_2 = derive_address_legacy(&merkle_tree_pubkey, &seeds); + assert_eq!(result, result_2); + } + + #[test] + fn test_derive_address_no_collision_same_seeds_diff_pubkey() { + let merkle_tree_pubkey = Pubkey::new_unique(); + let merkle_tree_pubkey_2 = Pubkey::new_unique(); + let seed = [2u8; 32]; + + let result = derive_address_legacy(&merkle_tree_pubkey, &seed); + let result_2 = derive_address_legacy(&merkle_tree_pubkey_2, &seed); + assert_ne!(result, result_2); + } + + #[test] + fn test_add_and_get_remaining_account_indices_empty() { + let pubkeys = vec![]; + let mut remaining_accounts = HashMap::new(); + let result = add_and_get_remaining_account_indices(&pubkeys, &mut remaining_accounts); + assert!(result.is_empty()); + } + + #[test] + fn test_add_and_get_remaining_account_indices_single() { + let pubkey = Pubkey::new_unique(); + let pubkeys = vec![pubkey]; + let mut remaining_accounts = HashMap::new(); + let result = add_and_get_remaining_account_indices(&pubkeys, &mut remaining_accounts); + assert_eq!(result, vec![0]); + assert_eq!(remaining_accounts.get(&pubkey), Some(&0)); + } + + #[test] + fn test_add_and_get_remaining_account_indices_multiple() { + let pubkey1 = Pubkey::new_unique(); + let pubkey2 = Pubkey::new_unique(); + let pubkeys = vec![pubkey1, pubkey2]; + let mut remaining_accounts = HashMap::new(); + let result = add_and_get_remaining_account_indices(&pubkeys, &mut remaining_accounts); + assert_eq!(result, vec![0, 1]); + assert_eq!(remaining_accounts.get(&pubkey1), Some(&0)); + assert_eq!(remaining_accounts.get(&pubkey2), Some(&1)); + } + + #[test] + fn test_add_and_get_remaining_account_indices_duplicates() { + let pubkey = Pubkey::new_unique(); + let pubkeys = vec![pubkey, pubkey]; + let mut remaining_accounts = HashMap::new(); + let result = add_and_get_remaining_account_indices(&pubkeys, &mut remaining_accounts); + assert_eq!(result, vec![0, 0]); + assert_eq!(remaining_accounts.get(&pubkey), Some(&0)); + assert_eq!(remaining_accounts.len(), 1); + } + + #[test] + fn test_add_and_get_remaining_account_indices_multiple_duplicates() { + let pubkey1 = Pubkey::new_unique(); + let pubkey2 = Pubkey::new_unique(); + let pubkey3 = Pubkey::new_unique(); + let pubkeys = vec![pubkey1, pubkey2, pubkey1, pubkey3, pubkey2, pubkey1]; + let mut remaining_accounts = HashMap::new(); + let result = add_and_get_remaining_account_indices(&pubkeys, &mut remaining_accounts); + assert_eq!(result, vec![0, 1, 0, 2, 1, 0]); + assert_eq!(remaining_accounts.get(&pubkey1), Some(&0)); + assert_eq!(remaining_accounts.get(&pubkey2), Some(&1)); + assert_eq!(remaining_accounts.get(&pubkey3), Some(&2)); + assert_eq!(remaining_accounts.len(), 3); + } +} diff --git a/program-tests/utils/src/create_address_test_program_sdk.rs b/program-tests/utils/src/create_address_test_program_sdk.rs index a7282f6b76..60b7f978c0 100644 --- a/program-tests/utils/src/create_address_test_program_sdk.rs +++ b/program-tests/utils/src/create_address_test_program_sdk.rs @@ -7,7 +7,7 @@ use light_client::{ rpc::{Rpc, RpcError}, }; use light_compressed_account::{ - address::{derive_address, pack_new_address_params}, + address::derive_address, instruction_data::{compressed_proof::CompressedProof, data::NewAddressParams}, }; use light_program_test::{accounts::test_accounts::TestAccounts, indexer::TestIndexerExtensions}; @@ -35,8 +35,10 @@ pub fn create_pda_instruction(input_params: CreateCompressedPdaInstructionInputs (*input_params.output_compressed_account_merkle_tree_pubkey).into(), 0, ); - let new_address_params = - pack_new_address_params(&[input_params.new_address_params], &mut remaining_accounts); + let new_address_params = crate::compressed_account_pack::pack_new_address_params( + &[input_params.new_address_params], + &mut remaining_accounts, + ); let instruction_data = create_address_test_program::instruction::CreateCompressedPda { data: input_params.data, diff --git a/program-tests/utils/src/lib.rs b/program-tests/utils/src/lib.rs index ed9b7b4f6a..1cc47620cc 100644 --- a/program-tests/utils/src/lib.rs +++ b/program-tests/utils/src/lib.rs @@ -33,6 +33,7 @@ pub mod assert_rollover; pub mod assert_token_tx; pub mod assert_transfer2; pub mod batched_address_tree; +pub mod compressed_account_pack; pub mod conversions; pub mod create_address_test_program_sdk; pub mod e2e_test_env; diff --git a/scripts/lint.sh b/scripts/lint.sh index c5612d641f..67467e89cd 100755 --- a/scripts/lint.sh +++ b/scripts/lint.sh @@ -7,6 +7,106 @@ npx nx run-many --target=lint --all cargo +nightly fmt --all -- --check cargo clippy --workspace --all-features --all-targets -- -D warnings -# Check no_std compatibility for light-zero-copy crate -echo "Checking no_std compatibility for light-zero-copy..." -cargo check -p light-zero-copy --no-default-features +echo "Testing feature combinations..." + +# Test no-default-features for all library crates +echo "Testing all library crates with --no-default-features..." +NO_DEFAULT_CRATES=( + "light-account-checks" + "light-batched-merkle-tree" + "light-bloom-filter" + "light-compressed-account" + "light-compressible" + "light-concurrent-merkle-tree" + "light-ctoken-types" + "light-hash-set" + "light-hasher" + "light-indexed-merkle-tree" + "light-macros" + "light-merkle-tree-metadata" + "light-verifier" + "light-zero-copy" + "light-heap" + "light-array-map" + "light-indexed-array" + "aligned-sized" + "light-sdk-types" + "light-sdk-pinocchio" + "light-sdk-macros" + "light-compressed-token-sdk" + "light-compressed-token-types" + "light-sdk" +) + +for crate in "${NO_DEFAULT_CRATES[@]}"; do + echo "Checking $crate with --no-default-features..." + cargo check -p "$crate" --no-default-features +done + +# Test pinocchio feature for all crates that have it +PINOCCHIO_CRATES=( + "light-hasher" + "light-indexed-merkle-tree" + "light-zero-copy" + "light-bloom-filter" + "light-compressed-account" + "light-merkle-tree-metadata" + "light-macros" + "light-batched-merkle-tree" + "light-concurrent-merkle-tree" + "light-verifier" + "light-account-checks" + "light-compressible" +) + +for crate in "${PINOCCHIO_CRATES[@]}"; do + echo "Checking $crate with pinocchio feature..." + cargo check -p "$crate" --features pinocchio +done + +# Test solana feature for all crates that have it +SOLANA_CRATES=( + "light-hasher" + "light-indexed-merkle-tree" + "light-zero-copy" + "light-bloom-filter" + "light-compressed-account" + "light-hash-set" + "light-merkle-tree-metadata" + "light-ctoken-types" + "light-macros" + "light-batched-merkle-tree" + "light-concurrent-merkle-tree" + "light-verifier" + "light-account-checks" + "light-compressible" +) + +for crate in "${SOLANA_CRATES[@]}"; do + echo "Checking $crate with solana feature..." + cargo check -p "$crate" --features solana +done + +# Test anchor feature for all crates that have it +ANCHOR_CRATES=( + "light-indexed-merkle-tree" + "light-compressed-account" + "light-merkle-tree-metadata" + "light-ctoken-types" + "light-verifier" + "light-compressible" + "light-sdk-types" + "light-sdk" + "light-compressed-token-sdk" + "light-compressed-token-types" +) + +for crate in "${ANCHOR_CRATES[@]}"; do + echo "Checking $crate with anchor feature..." + cargo check -p "$crate" --features anchor +done + +for crate in "${NO_DEFAULT_CRATES[@]}"; do + echo "Checking $crate with --no-default-features..." + cargo test -p "$crate" --no-run +done diff --git a/sdk-libs/client/Cargo.toml b/sdk-libs/client/Cargo.toml index 8fd84bdf39..6b1fed932e 100644 --- a/sdk-libs/client/Cargo.toml +++ b/sdk-libs/client/Cargo.toml @@ -43,6 +43,7 @@ light-indexed-merkle-tree = { workspace = true } light-sdk = { workspace = true } light-hasher = { workspace = true, features = ["poseidon"] } light-compressed-account = { workspace = true, features = ["solana", "poseidon"] } +light-event = { workspace = true } photon-api = { workspace = true } light-prover-client = { workspace = true } diff --git a/sdk-libs/client/src/rpc/client.rs b/sdk-libs/client/src/rpc/client.rs index e043e30a92..8944b03b5a 100644 --- a/sdk-libs/client/src/rpc/client.rs +++ b/sdk-libs/client/src/rpc/client.rs @@ -6,12 +6,10 @@ use std::{ use async_trait::async_trait; use borsh::BorshDeserialize; use bs58; -use light_compressed_account::{ - indexer_event::{ - event::{BatchPublicTransactionEvent, PublicTransactionEvent}, - parse::event_from_light_transaction, - }, - TreeType, +use light_compressed_account::TreeType; +use light_event::{ + event::{BatchPublicTransactionEvent, PublicTransactionEvent}, + parse::event_from_light_transaction, }; use solana_account::Account; use solana_clock::Slot; @@ -302,7 +300,7 @@ impl LightClient { } let parsed_event = event_from_light_transaction(program_ids.as_slice(), vec.as_slice(), vec_accounts) - .unwrap(); + .map_err(|e| RpcError::CustomError(format!("Failed to parse event: {e:?}")))?; let event = parsed_event.map(|e| (e, signature, slot)); Ok(event) } diff --git a/sdk-libs/client/src/rpc/errors.rs b/sdk-libs/client/src/rpc/errors.rs index e503d7c08b..fdff022a73 100644 --- a/sdk-libs/client/src/rpc/errors.rs +++ b/sdk-libs/client/src/rpc/errors.rs @@ -69,8 +69,8 @@ pub enum RpcError { LightSdkError(#[from] LightSdkError), } -impl From for RpcError { - fn from(e: light_compressed_account::indexer_event::error::ParseIndexerEventError) -> Self { +impl From for RpcError { + fn from(e: light_event::error::ParseIndexerEventError) -> Self { RpcError::CustomError(format!("ParseIndexerEventError: {}", e)) } } diff --git a/sdk-libs/client/src/rpc/rpc_trait.rs b/sdk-libs/client/src/rpc/rpc_trait.rs index 900ed08bab..e156c98dde 100644 --- a/sdk-libs/client/src/rpc/rpc_trait.rs +++ b/sdk-libs/client/src/rpc/rpc_trait.rs @@ -2,9 +2,7 @@ use std::fmt::Debug; use async_trait::async_trait; use borsh::BorshDeserialize; -use light_compressed_account::indexer_event::event::{ - BatchPublicTransactionEvent, PublicTransactionEvent, -}; +use light_event::event::{BatchPublicTransactionEvent, PublicTransactionEvent}; use solana_account::Account; use solana_clock::Slot; use solana_commitment_config::CommitmentConfig; diff --git a/sdk-libs/compressed-token-sdk/Cargo.toml b/sdk-libs/compressed-token-sdk/Cargo.toml index 41b04a859e..1cb9a01765 100644 --- a/sdk-libs/compressed-token-sdk/Cargo.toml +++ b/sdk-libs/compressed-token-sdk/Cargo.toml @@ -21,7 +21,7 @@ profile-heap = [ [dependencies] # Light Protocol dependencies light-compressed-token-types = { workspace = true } -light-compressed-account = { workspace = true } +light-compressed-account = { workspace = true, features = ["std"] } light-ctoken-types = { workspace = true } light-sdk = { workspace = true, features = ["v2"] } light-macros = { workspace = true } @@ -47,7 +47,7 @@ light-program-profiler = { workspace = true } anchor-lang = { workspace = true, optional = true } [dev-dependencies] -light-account-checks = { workspace = true, features = ["test-only", "pinocchio"] } +light-account-checks = { workspace = true, features = ["test-only", "pinocchio", "std"] } anchor-lang = { workspace = true } light-compressed-token = { workspace = true } pinocchio = { workspace = true } diff --git a/sdk-libs/compressed-token-types/Cargo.toml b/sdk-libs/compressed-token-types/Cargo.toml index 2a4617ff09..e81af4acb1 100644 --- a/sdk-libs/compressed-token-types/Cargo.toml +++ b/sdk-libs/compressed-token-types/Cargo.toml @@ -16,6 +16,6 @@ light-macros = { workspace = true } anchor-lang = { workspace = true, optional = true } light-sdk-types = { workspace = true } light-account-checks = { workspace = true } -light-compressed-account = { workspace = true } +light-compressed-account = { workspace = true, features = ["std"] } thiserror = { workspace = true } solana-msg = { workspace = true } diff --git a/sdk-libs/event/Cargo.toml b/sdk-libs/event/Cargo.toml new file mode 100644 index 0000000000..1ab3947f26 --- /dev/null +++ b/sdk-libs/event/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "light-event" +version = "0.1.0" +description = "Event types and utilities for Light Protocol" +repository = "https://github.com/Lightprotocol/light-protocol" +license = "Apache-2.0" +edition = "2021" + +[dependencies] +borsh = { workspace = true } +light-compressed-account = { workspace = true, features = ["std"] } +light-hasher = { workspace = true, features = ["poseidon"] } +light-zero-copy = { workspace = true } +thiserror = { workspace = true } + +[dev-dependencies] +rand = { workspace = true } diff --git a/program-libs/compressed-account/src/indexer_event/error.rs b/sdk-libs/event/src/error.rs similarity index 93% rename from program-libs/compressed-account/src/indexer_event/error.rs rename to sdk-libs/event/src/error.rs index 96ed6b3259..cfd0c8f133 100644 --- a/program-libs/compressed-account/src/indexer_event/error.rs +++ b/sdk-libs/event/src/error.rs @@ -1,9 +1,8 @@ use borsh::maybestd::io::Error as BorshError; +use light_compressed_account::CompressedAccountError; use light_zero_copy::errors::ZeroCopyError; use thiserror::Error; -use crate::CompressedAccountError; - #[derive(Debug, Error)] pub enum ParseIndexerEventError { #[error("Deserialize light system program instruction error")] diff --git a/program-libs/compressed-account/src/indexer_event/event.rs b/sdk-libs/event/src/event.rs similarity index 98% rename from program-libs/compressed-account/src/indexer_event/event.rs rename to sdk-libs/event/src/event.rs index 097cd829d2..6978cae3d5 100644 --- a/program-libs/compressed-account/src/indexer_event/event.rs +++ b/sdk-libs/event/src/event.rs @@ -1,6 +1,5 @@ use borsh::{BorshDeserialize, BorshSerialize}; - -use crate::{ +use light_compressed_account::{ instruction_data::{ data::OutputCompressedAccountWithPackedContext, insert_into_queues::MerkleTreeSequenceNumber as InstructionDataSequenceNumber, diff --git a/sdk-libs/event/src/lib.rs b/sdk-libs/event/src/lib.rs new file mode 100644 index 0000000000..5c137546a1 --- /dev/null +++ b/sdk-libs/event/src/lib.rs @@ -0,0 +1,5 @@ +// Light Protocol event types and utilities + +pub mod error; +pub mod event; +pub mod parse; diff --git a/program-libs/compressed-account/src/indexer_event/mod.rs b/sdk-libs/event/src/mod.rs similarity index 100% rename from program-libs/compressed-account/src/indexer_event/mod.rs rename to sdk-libs/event/src/mod.rs diff --git a/program-libs/compressed-account/src/indexer_event/parse.rs b/sdk-libs/event/src/parse.rs similarity index 99% rename from program-libs/compressed-account/src/indexer_event/parse.rs rename to sdk-libs/event/src/parse.rs index e73d0aa539..595edf5a46 100644 --- a/program-libs/compressed-account/src/indexer_event/parse.rs +++ b/sdk-libs/event/src/parse.rs @@ -1,14 +1,5 @@ use borsh::BorshDeserialize; -use light_zero_copy::traits::ZeroCopyAt; - -use super::{ - error::ParseIndexerEventError, - event::{ - BatchNullifyContext, BatchPublicTransactionEvent, MerkleTreeSequenceNumber, - MerkleTreeSequenceNumberV1, NewAddress, PublicTransactionEvent, - }, -}; -use crate::{ +use light_compressed_account::{ compressed_account::{ CompressedAccount, CompressedAccountData, PackedCompressedAccountWithMerkleContext, }, @@ -26,6 +17,15 @@ use crate::{ nullifier::create_nullifier, Pubkey, }; +use light_zero_copy::traits::ZeroCopyAt; + +use super::{ + error::ParseIndexerEventError, + event::{ + BatchNullifyContext, BatchPublicTransactionEvent, MerkleTreeSequenceNumber, + MerkleTreeSequenceNumberV1, NewAddress, PublicTransactionEvent, + }, +}; #[derive(Debug, Clone, PartialEq)] struct ExecutingSystemInstruction<'a> { @@ -311,7 +311,7 @@ fn deserialize_instruction<'a>( return Err(ParseIndexerEventError::DeserializeSystemInstructionError); } let accounts = accounts.split_at(11).1; - let data = crate::instruction_data::invoke_cpi::InstructionDataInvokeCpi::deserialize( + let data = light_compressed_account::instruction_data::invoke_cpi::InstructionDataInvokeCpi::deserialize( &mut &instruction[4..], )?; Ok(ExecutingSystemInstruction { diff --git a/sdk-libs/macros/Cargo.toml b/sdk-libs/macros/Cargo.toml index 5465202979..8f43ea4ffd 100644 --- a/sdk-libs/macros/Cargo.toml +++ b/sdk-libs/macros/Cargo.toml @@ -16,17 +16,17 @@ quote = { workspace = true } syn = { workspace = true } solana-pubkey = { workspace = true, features = ["curve25519", "sha2"] } -light-hasher = { workspace = true } +light-hasher = { workspace = true, features = ["sha256"] } [dev-dependencies] -light-compressed-account = { workspace = true } +light-compressed-account = { workspace = true, features = ["std"] } light-sdk-types = { workspace = true } prettyplease = "0.2.29" solana-pubkey = { workspace = true, features = ["borsh"] } borsh = { workspace = true } light-macros = { workspace = true } light-account-checks = { workspace = true } -light-hasher = { workspace = true , features = ["poseidon"]} +light-hasher = { workspace = true , features = ["poseidon", "keccak"]} light-poseidon = { workspace = true } [lib] diff --git a/sdk-libs/macros/src/lib.rs b/sdk-libs/macros/src/lib.rs index 61fe3c1fac..45bdf382d1 100644 --- a/sdk-libs/macros/src/lib.rs +++ b/sdk-libs/macros/src/lib.rs @@ -7,7 +7,6 @@ use traits::process_light_traits; mod account; mod accounts; -mod cpi_signer; mod discriminator; mod hasher; mod program; @@ -281,50 +280,3 @@ pub fn light_program(_: TokenStream, input: TokenStream) -> TokenStream { .unwrap_or_else(|err| err.to_compile_error()) .into() } - -/// Derives a Light Protocol CPI signer address at compile time -/// -/// This macro computes the CPI signer PDA using the "cpi_authority" seed -/// for the given program ID at compile time. -/// -/// ## Usage -/// -/// ``` -/// use light_sdk_macros::derive_light_cpi_signer_pda; -/// // Derive CPI signer for your program -/// const CPI_SIGNER_DATA: ([u8; 32], u8) = derive_light_cpi_signer_pda!("SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7"); -/// const CPI_SIGNER: [u8; 32] = CPI_SIGNER_DATA.0; -/// const CPI_SIGNER_BUMP: u8 = CPI_SIGNER_DATA.1; -/// ``` -/// -/// This macro computes the PDA during compile time and returns a tuple of ([u8; 32], bump). -#[proc_macro] -pub fn derive_light_cpi_signer_pda(input: TokenStream) -> TokenStream { - cpi_signer::derive_light_cpi_signer_pda(input) -} - -/// Derives a complete Light Protocol CPI configuration at compile time -/// -/// This macro computes the program ID, CPI signer PDA, and bump seed -/// for the given program ID at compile time. -/// -/// ## Usage -/// -/// ``` -/// use light_sdk_macros::derive_light_cpi_signer; -/// use light_sdk_types::CpiSigner; -/// // Derive complete CPI signer for your program -/// const LIGHT_CPI_SIGNER: CpiSigner = derive_light_cpi_signer!("SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7"); -/// -/// // Access individual fields: -/// const PROGRAM_ID: [u8; 32] = LIGHT_CPI_SIGNER.program_id; -/// const CPI_SIGNER: [u8; 32] = LIGHT_CPI_SIGNER.cpi_signer; -/// const BUMP: u8 = LIGHT_CPI_SIGNER.bump; -/// ``` -/// -/// This macro computes all values during compile time and returns a CpiSigner struct -/// containing the program ID, CPI signer address, and bump seed. -#[proc_macro] -pub fn derive_light_cpi_signer(input: TokenStream) -> TokenStream { - cpi_signer::derive_light_cpi_signer(input) -} diff --git a/sdk-libs/macros/src/program.rs b/sdk-libs/macros/src/program.rs index d1718ed591..5df0375b1a 100644 --- a/sdk-libs/macros/src/program.rs +++ b/sdk-libs/macros/src/program.rs @@ -12,7 +12,7 @@ use syn::{ // A single instruction parameter provided as an argument to the Anchor program // function. It consists of the name an the type, e.g.: `name: String`. -#[derive(Clone, Debug)] +#[derive(Clone)] struct InstructionParam { name: Ident, ty: Type, diff --git a/sdk-libs/macros/tests/pda.rs b/sdk-libs/macros/tests/pda.rs index 2982cba542..50fb33782e 100644 --- a/sdk-libs/macros/tests/pda.rs +++ b/sdk-libs/macros/tests/pda.rs @@ -1,6 +1,6 @@ use std::str::FromStr; -use light_sdk_macros::derive_light_cpi_signer; +use light_macros::derive_light_cpi_signer; use light_sdk_types::CpiSigner; use solana_pubkey::Pubkey; diff --git a/sdk-libs/program-test/Cargo.toml b/sdk-libs/program-test/Cargo.toml index de37e92123..0317748aca 100644 --- a/sdk-libs/program-test/Cargo.toml +++ b/sdk-libs/program-test/Cargo.toml @@ -17,13 +17,13 @@ light-indexed-array = { workspace = true } light-merkle-tree-reference = { workspace = true } light-merkle-tree-metadata = { workspace = true, features = ["anchor"] } light-concurrent-merkle-tree = { workspace = true, optional = true } -light-hasher = { workspace = true, features = ["poseidon"] } +light-hasher = { workspace = true, features = ["poseidon", "sha256", "keccak", "std"] } light-ctoken-types = { workspace = true } light-compressible = { workspace = true } light-compressed-token-sdk = { workspace = true } light-compressed-account = { workspace = true, features = ["anchor", "poseidon"] } light-batched-merkle-tree = { workspace = true, features = ["test-only"], optional = true } - +light-event = { workspace = true } # unreleased light-client = { workspace = true, features = ["program-test"] } light-prover-client = { workspace = true } diff --git a/sdk-libs/program-test/src/indexer/extensions.rs b/sdk-libs/program-test/src/indexer/extensions.rs index 1c5b2434eb..3e802ec66e 100644 --- a/sdk-libs/program-test/src/indexer/extensions.rs +++ b/sdk-libs/program-test/src/indexer/extensions.rs @@ -3,10 +3,8 @@ use async_trait::async_trait; use light_client::indexer::{ AddressMerkleTreeAccounts, MerkleProof, NewAddressProofWithContext, StateMerkleTreeAccounts, }; -use light_compressed_account::{ - compressed_account::CompressedAccountWithMerkleContext, - indexer_event::event::PublicTransactionEvent, -}; +use light_compressed_account::compressed_account::CompressedAccountWithMerkleContext; +use light_event::event::PublicTransactionEvent; use light_sdk::token::TokenDataWithMerkleContext; use solana_sdk::signature::Keypair; diff --git a/sdk-libs/program-test/src/indexer/test_indexer.rs b/sdk-libs/program-test/src/indexer/test_indexer.rs index 6e1104b6ad..ee098435f5 100644 --- a/sdk-libs/program-test/src/indexer/test_indexer.rs +++ b/sdk-libs/program-test/src/indexer/test_indexer.rs @@ -36,11 +36,11 @@ use light_client::{ use light_compressed_account::{ compressed_account::{CompressedAccountWithMerkleContext, MerkleContext}, hash_chain::create_hash_chain_from_slice, - indexer_event::event::PublicTransactionEvent, instruction_data::compressed_proof::CompressedProof, tx_hash::create_tx_hash, TreeType, }; +use light_event::event::PublicTransactionEvent; use light_hasher::{bigint::bigint_to_be_bytes_array, Poseidon}; use light_merkle_tree_metadata::QueueType; use light_merkle_tree_reference::MerkleTree; diff --git a/sdk-libs/program-test/src/program_test/extensions.rs b/sdk-libs/program-test/src/program_test/extensions.rs index 6e7b89c5f1..27c02e2094 100644 --- a/sdk-libs/program-test/src/program_test/extensions.rs +++ b/sdk-libs/program-test/src/program_test/extensions.rs @@ -3,10 +3,8 @@ use async_trait::async_trait; use light_client::indexer::{ AddressMerkleTreeAccounts, MerkleProof, NewAddressProofWithContext, StateMerkleTreeAccounts, }; -use light_compressed_account::{ - compressed_account::CompressedAccountWithMerkleContext, - indexer_event::event::PublicTransactionEvent, -}; +use light_compressed_account::compressed_account::CompressedAccountWithMerkleContext; +use light_event::event::PublicTransactionEvent; use light_sdk::token::TokenDataWithMerkleContext; use solana_sdk::signature::Keypair; diff --git a/sdk-libs/program-test/src/program_test/rpc.rs b/sdk-libs/program-test/src/program_test/rpc.rs index 6321fc96b1..b4c564e196 100644 --- a/sdk-libs/program-test/src/program_test/rpc.rs +++ b/sdk-libs/program-test/src/program_test/rpc.rs @@ -7,13 +7,11 @@ use light_client::{ indexer::{Indexer, TreeInfo}, rpc::{LightClientConfig, Rpc, RpcError}, }; -use light_compressed_account::{ - indexer_event::{ - error::ParseIndexerEventError, - event::{BatchPublicTransactionEvent, PublicTransactionEvent}, - parse::event_from_light_transaction, - }, - TreeType, +use light_compressed_account::TreeType; +use light_event::{ + error::ParseIndexerEventError, + event::{BatchPublicTransactionEvent, PublicTransactionEvent}, + parse::event_from_light_transaction, }; use solana_rpc_client_api::config::RpcSendTransactionConfig; use solana_sdk::{ diff --git a/sdk-libs/program-test/src/program_test/test_rpc.rs b/sdk-libs/program-test/src/program_test/test_rpc.rs index ac7fcca416..49ad31fb02 100644 --- a/sdk-libs/program-test/src/program_test/test_rpc.rs +++ b/sdk-libs/program-test/src/program_test/test_rpc.rs @@ -6,10 +6,8 @@ use solana_sdk::{clock::Slot, pubkey::Pubkey}; use { borsh::BorshDeserialize, light_client::fee::{assert_transaction_params, TransactionParams}, - light_compressed_account::indexer_event::event::{ - BatchPublicTransactionEvent, PublicTransactionEvent, - }, light_compressible::rent::SLOTS_PER_EPOCH, + light_event::event::{BatchPublicTransactionEvent, PublicTransactionEvent}, solana_sdk::{ clock::Clock, instruction::Instruction, diff --git a/sdk-libs/sdk-pinocchio/Cargo.toml b/sdk-libs/sdk-pinocchio/Cargo.toml index 2b0135848f..4d20e3e46e 100644 --- a/sdk-libs/sdk-pinocchio/Cargo.toml +++ b/sdk-libs/sdk-pinocchio/Cargo.toml @@ -7,19 +7,21 @@ license = "Apache-2.0" edition = "2021" [features] -default = [] +default = ["alloc"] +alloc = ["light-compressed-account/alloc"] +std = ["alloc"] v2 = ["light-sdk-types/v2"] +poseidon = ["light-hasher/poseidon", "light-sdk-types/poseidon", "light-compressed-account/poseidon"] +light-account = ["light-sdk"] [dependencies] pinocchio = { workspace = true } -light-hasher = { workspace = true, features = ["poseidon"] } -light-account-checks = { workspace = true, features = ["pinocchio"] } +light-hasher = { workspace = true, default-features = false } +light-account-checks = { workspace = true, default-features = false, features = ["pinocchio"] } light-macros = { workspace = true } light-sdk-macros = { workspace = true } -light-sdk-types = { workspace = true } -light-zero-copy = { workspace = true } +light-sdk-types = { workspace = true, default-features = false, features = ["alloc"] } borsh = { workspace = true } thiserror = { workspace = true } light-compressed-account = { workspace = true } -solana-pubkey = { workspace = true } -solana-msg = { workspace = true } +light-sdk = { workspace = true, optional = true } diff --git a/sdk-libs/sdk-pinocchio/src/account.rs b/sdk-libs/sdk-pinocchio/src/account.rs deleted file mode 100644 index 5d40bff7cb..0000000000 --- a/sdk-libs/sdk-pinocchio/src/account.rs +++ /dev/null @@ -1,196 +0,0 @@ -use std::ops::{Deref, DerefMut}; - -use light_compressed_account::{ - compressed_account::PackedMerkleContext, - instruction_data::with_account_info::{CompressedAccountInfo, InAccountInfo, OutAccountInfo}, -}; -use light_hasher::{DataHasher, Poseidon}; -use light_sdk_types::instruction::account_meta::CompressedAccountMetaTrait; -use pinocchio::pubkey::Pubkey; - -use crate::{error::LightSdkError, BorshDeserialize, BorshSerialize, LightDiscriminator}; - -#[derive(Debug, PartialEq)] -pub struct LightAccount< - 'a, - A: BorshSerialize + BorshDeserialize + LightDiscriminator + DataHasher + Default, -> { - owner: &'a Pubkey, - pub account: A, - account_info: CompressedAccountInfo, -} - -impl<'a, A: BorshSerialize + BorshDeserialize + LightDiscriminator + DataHasher + Default> - LightAccount<'a, A> -{ - pub fn new_init( - owner: &'a Pubkey, - address: Option<[u8; 32]>, - output_state_tree_index: u8, - ) -> Self { - let output_account_info = OutAccountInfo { - output_merkle_tree_index: output_state_tree_index, - discriminator: A::LIGHT_DISCRIMINATOR, - ..Default::default() - }; - Self { - owner, - account: A::default(), - account_info: CompressedAccountInfo { - address, - input: None, - output: Some(output_account_info), - }, - } - } - - pub fn new_mut( - owner: &'a Pubkey, - input_account_meta: &impl CompressedAccountMetaTrait, - input_account: A, - ) -> Result { - let input_account_info = { - let input_data_hash = input_account.hash::()?; - let tree_info = input_account_meta.get_tree_info(); - InAccountInfo { - data_hash: input_data_hash, - lamports: input_account_meta.get_lamports().unwrap_or_default(), - merkle_context: PackedMerkleContext { - merkle_tree_pubkey_index: tree_info.merkle_tree_pubkey_index, - queue_pubkey_index: tree_info.queue_pubkey_index, - leaf_index: tree_info.leaf_index, - prove_by_index: tree_info.prove_by_index, - }, - root_index: input_account_meta.get_root_index().unwrap_or_default(), - discriminator: A::LIGHT_DISCRIMINATOR, - } - }; - let output_account_info = { - let output_merkle_tree_index = input_account_meta - .get_output_state_tree_index() - .ok_or(LightSdkError::OutputStateTreeIndexIsNone)?; - OutAccountInfo { - lamports: input_account_meta.get_lamports().unwrap_or_default(), - output_merkle_tree_index, - discriminator: A::LIGHT_DISCRIMINATOR, - ..Default::default() - } - }; - - Ok(Self { - owner, - account: input_account, - account_info: CompressedAccountInfo { - address: input_account_meta.get_address(), - input: Some(input_account_info), - output: Some(output_account_info), - }, - }) - } - - pub fn new_close( - owner: &'a Pubkey, - input_account_meta: &impl CompressedAccountMetaTrait, - input_account: A, - ) -> Result { - let input_account_info = { - let input_data_hash = input_account.hash::()?; - let tree_info = input_account_meta.get_tree_info(); - InAccountInfo { - data_hash: input_data_hash, - lamports: input_account_meta.get_lamports().unwrap_or_default(), - merkle_context: PackedMerkleContext { - merkle_tree_pubkey_index: tree_info.merkle_tree_pubkey_index, - queue_pubkey_index: tree_info.queue_pubkey_index, - leaf_index: tree_info.leaf_index, - prove_by_index: tree_info.prove_by_index, - }, - root_index: input_account_meta.get_root_index().unwrap_or_default(), - discriminator: A::LIGHT_DISCRIMINATOR, - } - }; - Ok(Self { - owner, - account: input_account, - account_info: CompressedAccountInfo { - address: input_account_meta.get_address(), - input: Some(input_account_info), - output: None, - }, - }) - } - - pub fn discriminator(&self) -> &[u8; 8] { - &A::LIGHT_DISCRIMINATOR - } - - pub fn lamports(&self) -> u64 { - if let Some(output) = self.account_info.output.as_ref() { - output.lamports - } else if let Some(input) = self.account_info.input.as_ref() { - input.lamports - } else { - 0 - } - } - - pub fn lamports_mut(&mut self) -> &mut u64 { - if let Some(output) = self.account_info.output.as_mut() { - &mut output.lamports - } else if let Some(input) = self.account_info.input.as_mut() { - &mut input.lamports - } else { - panic!("No lamports field available in account_info") - } - } - - pub fn address(&self) -> &Option<[u8; 32]> { - &self.account_info.address - } - - pub fn owner(&self) -> &Pubkey { - self.owner - } - - pub fn in_account_info(&self) -> &Option { - &self.account_info.input - } - - pub fn out_account_info(&mut self) -> &Option { - &self.account_info.output - } - - /// 1. Serializes the account data and sets the output data hash. - /// 2. Returns CompressedAccountInfo. - /// - /// Note this is an expensive operation - /// that should only be called once per instruction. - pub fn to_account_info(mut self) -> Result { - if let Some(output) = self.account_info.output.as_mut() { - output.data_hash = self.account.hash::()?; - output.data = self - .account - .try_to_vec() - .map_err(|_| LightSdkError::Borsh)?; - } - Ok(self.account_info) - } -} - -impl Deref - for LightAccount<'_, A> -{ - type Target = A; - - fn deref(&self) -> &Self::Target { - &self.account - } -} - -impl DerefMut - for LightAccount<'_, A> -{ - fn deref_mut(&mut self) -> &mut ::Target { - &mut self.account - } -} diff --git a/sdk-libs/sdk-pinocchio/src/cpi/traits.rs b/sdk-libs/sdk-pinocchio/src/cpi/traits.rs index c054398cb2..880f87b4c4 100644 --- a/sdk-libs/sdk-pinocchio/src/cpi/traits.rs +++ b/sdk-libs/sdk-pinocchio/src/cpi/traits.rs @@ -1,14 +1,23 @@ +#[cfg(all(not(feature = "std"), feature = "alloc"))] +extern crate alloc; +#[cfg(all(not(feature = "std"), feature = "alloc"))] +use alloc::vec::Vec; +#[cfg(feature = "std")] +use std::vec::Vec; + use light_compressed_account::instruction_data::compressed_proof::ValidityProof; pub use light_compressed_account::LightInstructionData; use light_sdk_types::constants::{CPI_AUTHORITY_PDA_SEED, LIGHT_SYSTEM_PROGRAM_ID}; +#[cfg(any(feature = "std", feature = "alloc"))] +use pinocchio::pubkey::Pubkey; use pinocchio::{ cpi::slice_invoke_signed, instruction::{AccountMeta, Instruction, Seed, Signer}, program_error::ProgramError, - pubkey::Pubkey, }; -use crate::{error::LightSdkError, BorshDeserialize, BorshSerialize, LightDiscriminator}; +#[cfg(any(feature = "std", feature = "alloc"))] +use crate::error::LightSdkError; /// Trait for types that can provide account information for CPI calls pub trait CpiAccountsTrait { @@ -31,14 +40,15 @@ pub trait CpiAccountsTrait { pub trait LightCpiInstruction: Sized { fn new_cpi(cpi_signer: light_sdk_types::CpiSigner, proof: ValidityProof) -> Self; + #[cfg(feature = "light-account")] fn with_light_account( self, - account: crate::account::LightAccount<'_, A>, - ) -> Result + account: crate::LightAccount<'_, A>, + ) -> Result where - A: BorshSerialize - + BorshDeserialize - + LightDiscriminator + A: borsh::BorshSerialize + + borsh::BorshDeserialize + + crate::LightDiscriminator + light_hasher::DataHasher + Default; @@ -47,7 +57,13 @@ pub trait LightCpiInstruction: Sized { } pub trait InvokeLightSystemProgram { + #[cfg(any(feature = "std", feature = "alloc"))] fn invoke(self, accounts: impl CpiAccountsTrait) -> Result<(), ProgramError>; + + fn invoke_array( + self, + accounts: impl CpiAccountsTrait, + ) -> Result<(), ProgramError>; } // Blanket implementation for types that implement both LightInstructionData and LightCpiInstruction @@ -55,6 +71,7 @@ impl InvokeLightSystemProgram for T where T: LightInstructionData + LightCpiInstruction, { + #[cfg(any(feature = "std", feature = "alloc"))] fn invoke(self, accounts: impl CpiAccountsTrait) -> Result<(), ProgramError> { // Validate mode consistency if accounts.get_mode() != self.get_mode() { @@ -80,6 +97,37 @@ where invoke_light_system_program(&account_infos, instruction, self.get_bump()) } + + fn invoke_array( + self, + accounts: impl CpiAccountsTrait, + ) -> Result<(), ProgramError> { + use light_compressed_account::CompressedAccountError; + // Validate mode consistency + if accounts.get_mode() != self.get_mode() { + return Err(ProgramError::InvalidArgument); + } + + // Serialize instruction data with discriminator using data_array + let data = self.data_array::().map_err(|e| match e { + CompressedAccountError::InputTooLarge(_) => ProgramError::InvalidInstructionData, + _ => ProgramError::InvalidArgument, + })?; + + // Get account infos and metas + let account_infos = accounts + .to_account_infos_for_invoke() + .map_err(ProgramError::from)?; + let account_metas = accounts.to_account_metas().map_err(ProgramError::from)?; + + let instruction = Instruction { + program_id: &LIGHT_SYSTEM_PROGRAM_ID, + accounts: &account_metas, + data: data.as_slice(), + }; + + invoke_light_system_program(&account_infos, instruction, self.get_bump()) + } } #[inline(always)] diff --git a/sdk-libs/sdk-pinocchio/src/cpi/v1/invoke.rs b/sdk-libs/sdk-pinocchio/src/cpi/v1/invoke.rs index 3c4a4d437c..6c894bd48b 100644 --- a/sdk-libs/sdk-pinocchio/src/cpi/v1/invoke.rs +++ b/sdk-libs/sdk-pinocchio/src/cpi/v1/invoke.rs @@ -1,13 +1,12 @@ use light_compressed_account::instruction_data::{ compressed_proof::ValidityProof, invoke_cpi::InstructionDataInvokeCpi, }; +#[cfg(feature = "light-account")] use light_sdk_types::instruction::account_info::CompressedAccountInfoTrait; use crate::{ - account::LightAccount, cpi::traits::{LightCpiInstruction, LightInstructionData}, - error::LightSdkError, - BorshDeserialize, BorshSerialize, CpiSigner, LightDiscriminator, + BorshSerialize, CpiSigner, }; /// V1 wrapper struct for InstructionDataInvokeCpi with CpiSigner @@ -67,23 +66,30 @@ impl LightCpiInstruction for LightSystemProgramCpi { } } - fn with_light_account(mut self, account: LightAccount<'_, A>) -> Result + #[cfg(feature = "light-account")] + fn with_light_account( + mut self, + account: crate::LightAccount<'_, A>, + ) -> Result where - A: BorshSerialize - + BorshDeserialize - + LightDiscriminator + A: crate::BorshSerialize + + crate::BorshDeserialize + + crate::LightDiscriminator + light_hasher::DataHasher + Default, { use light_compressed_account::compressed_account::PackedCompressedAccountWithMerkleContext; - + use pinocchio::program_error::ProgramError; // Convert LightAccount to account info - let account_info = account.to_account_info()?; + let account_info = account + .to_account_info() + .map_err(|e| ProgramError::Custom(u64::from(e) as u32))?; // Handle input accounts - convert to PackedCompressedAccountWithMerkleContext if let Some(input_account) = account_info .input_compressed_account(self.cpi_signer.program_id.into()) - .map_err(LightSdkError::from)? + .map_err(crate::error::LightSdkError::from) + .map_err(|e| ProgramError::Custom(u32::from(e)))? { let packed_input = PackedCompressedAccountWithMerkleContext { compressed_account: input_account.compressed_account, @@ -99,7 +105,8 @@ impl LightCpiInstruction for LightSystemProgramCpi { // Handle output accounts if let Some(output_account) = account_info .output_compressed_account(self.cpi_signer.program_id.into()) - .map_err(LightSdkError::from)? + .map_err(crate::error::LightSdkError::from) + .map_err(|e| ProgramError::Custom(u32::from(e)))? { self.instruction_data .output_compressed_accounts @@ -120,7 +127,10 @@ impl LightCpiInstruction for LightSystemProgramCpi { // Manual BorshSerialize implementation that only serializes instruction_data impl BorshSerialize for LightSystemProgramCpi { - fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + fn serialize( + &self, + writer: &mut W, + ) -> borsh::maybestd::io::Result<()> { self.instruction_data.serialize(writer) } } @@ -132,7 +142,11 @@ impl light_compressed_account::InstructionDiscriminator for LightSystemProgramCp } impl LightInstructionData for LightSystemProgramCpi { - fn data(&self) -> Result, light_compressed_account::CompressedAccountError> { + #[cfg(feature = "alloc")] + fn data( + &self, + ) -> Result, light_compressed_account::CompressedAccountError> + { self.instruction_data.data() } } diff --git a/sdk-libs/sdk-pinocchio/src/cpi/v2/invoke.rs b/sdk-libs/sdk-pinocchio/src/cpi/v2/invoke.rs index acd8155934..2ebb9283f6 100644 --- a/sdk-libs/sdk-pinocchio/src/cpi/v2/invoke.rs +++ b/sdk-libs/sdk-pinocchio/src/cpi/v2/invoke.rs @@ -11,10 +11,7 @@ pub use light_compressed_account::instruction_data::{ }; use light_sdk_types::CpiSigner; -use crate::{ - account::LightAccount, cpi::LightCpiInstruction, error::LightSdkError, BorshDeserialize, - BorshSerialize, LightDiscriminator, -}; +use crate::cpi::LightCpiInstruction; impl LightCpiInstruction for InstructionDataInvokeCpiWithReadOnly { fn new_cpi(cpi_signer: CpiSigner, proof: ValidityProof) -> Self { @@ -27,16 +24,22 @@ impl LightCpiInstruction for InstructionDataInvokeCpiWithReadOnly { } } - fn with_light_account(mut self, account: LightAccount<'_, A>) -> Result + #[cfg(feature = "light-account")] + fn with_light_account( + mut self, + account: crate::LightAccount<'_, A>, + ) -> Result where - A: BorshSerialize - + BorshDeserialize - + LightDiscriminator + A: crate::BorshSerialize + + crate::BorshDeserialize + + crate::LightDiscriminator + light_hasher::DataHasher + Default, { // Convert LightAccount to instruction data format - let account_info = account.to_account_info()?; + let account_info = account + .to_account_info() + .map_err(|e| pinocchio::program_error::ProgramError::Custom(u64::from(e) as u32))?; // Handle input accounts if let Some(input) = account_info.input.as_ref() { @@ -92,16 +95,24 @@ impl LightCpiInstruction for InstructionDataInvokeCpiWithAccountInfo { } } - fn with_light_account(mut self, account: LightAccount<'_, A>) -> Result + #[cfg(feature = "light-account")] + fn with_light_account( + mut self, + account: crate::LightAccount<'_, A>, + ) -> Result where - A: BorshSerialize - + BorshDeserialize - + LightDiscriminator + A: crate::BorshSerialize + + borsh::BorshDeserialize + + crate::LightDiscriminator + light_hasher::DataHasher + Default, { // Convert LightAccount to instruction data format - let account_info = account.to_account_info()?; + + use pinocchio::program_error::ProgramError; + let account_info = account + .to_account_info() + .map_err(|e| ProgramError::Custom(u64::from(e) as u32))?; self.account_infos.push(account_info); Ok(self) } diff --git a/sdk-libs/sdk-pinocchio/src/error.rs b/sdk-libs/sdk-pinocchio/src/error.rs index 64009c231c..921b079374 100644 --- a/sdk-libs/sdk-pinocchio/src/error.rs +++ b/sdk-libs/sdk-pinocchio/src/error.rs @@ -1,7 +1,6 @@ use light_account_checks::error::AccountError; use light_hasher::HasherError; pub use light_sdk_types::error::LightSdkTypesError; -use light_zero_copy::errors::ZeroCopyError; use pinocchio::program_error::ProgramError; use thiserror::Error; @@ -79,8 +78,6 @@ pub enum LightSdkError { ModeMismatch, #[error(transparent)] Hasher(#[from] HasherError), - #[error(transparent)] - ZeroCopy(#[from] ZeroCopyError), #[error("Compressed account error: {0:?}")] CompressedAccount(light_compressed_account::CompressedAccountError), #[error("Program error: {0:?}")] @@ -176,7 +173,6 @@ impl From for u32 { LightSdkError::InvalidCpiAccountsOffset => 16034, LightSdkError::ModeMismatch => 16035, LightSdkError::Hasher(e) => e.into(), - LightSdkError::ZeroCopy(e) => e.into(), LightSdkError::CompressedAccount(_) => 16036, LightSdkError::ProgramError(e) => u64::from(e) as u32, LightSdkError::AccountError(e) => e.into(), diff --git a/sdk-libs/sdk-pinocchio/src/lib.rs b/sdk-libs/sdk-pinocchio/src/lib.rs index d22e1d47ec..6326ffd54d 100644 --- a/sdk-libs/sdk-pinocchio/src/lib.rs +++ b/sdk-libs/sdk-pinocchio/src/lib.rs @@ -1,11 +1,11 @@ -pub mod account; pub mod address; pub mod cpi; pub mod error; pub mod instruction; -pub use account::LightAccount; -pub use borsh::{BorshDeserialize, BorshSerialize}; +#[cfg(feature = "light-account")] +pub(crate) use borsh::BorshDeserialize; +pub(crate) use borsh::BorshSerialize; pub use cpi::{v1::CpiAccounts, CpiAccountsConfig}; pub use light_account_checks::discriminator::Discriminator as LightDiscriminator; pub use light_compressed_account::{ @@ -13,5 +13,9 @@ pub use light_compressed_account::{ instruction_data::{compressed_proof::ValidityProof, data::*}, }; pub use light_hasher; -pub use light_sdk_macros::{derive_light_cpi_signer, LightDiscriminator, LightHasher}; +pub use light_macros::derive_light_cpi_signer; +#[cfg(feature = "light-account")] +pub use light_sdk::LightAccount; +#[cfg(feature = "light-account")] +pub use light_sdk_macros::{LightDiscriminator, LightHasher}; pub use light_sdk_types::{self, constants, CpiSigner}; diff --git a/sdk-libs/sdk-types/Cargo.toml b/sdk-libs/sdk-types/Cargo.toml index 46d0bcd2fd..df90c0231e 100644 --- a/sdk-libs/sdk-types/Cargo.toml +++ b/sdk-libs/sdk-types/Cargo.toml @@ -7,6 +7,10 @@ repository = "https://github.com/lightprotocol/light-protocol" description = "Core types for Light Protocol SDK" [features] +default = ["std"] +std = ["alloc", "light-compressed-account/std", "solana-msg"] +alloc = ["light-compressed-account/alloc"] +keccak = ["light-hasher/keccak"] anchor = ["anchor-lang", "light-compressed-account/anchor"] v2 = [] cpi-context = [] @@ -19,8 +23,7 @@ light-account-checks = { workspace = true } light-hasher = { workspace = true } light-compressed-account = { workspace = true } light-macros = { workspace = true } -light-zero-copy = { workspace = true } -solana-msg = { workspace = true } +solana-msg = { workspace = true, optional = true } # External dependencies borsh = { workspace = true } diff --git a/sdk-libs/sdk-types/src/address.rs b/sdk-libs/sdk-types/src/address.rs index b49c343190..b0a97c6cc4 100644 --- a/sdk-libs/sdk-types/src/address.rs +++ b/sdk-libs/sdk-types/src/address.rs @@ -15,7 +15,9 @@ impl From for [u8; 32] { pub type CompressedAddress = [u8; 32]; pub mod v1 { - use light_hasher::{hash_to_field_size::hashv_to_bn254_field_size_be, Hasher, Keccak}; + use light_hasher::{ + hash_to_field_size::hashv_to_bn254_field_size_be_const_array, Hasher, Keccak, + }; use super::AddressSeed; @@ -33,18 +35,21 @@ pub mod v1 { /// ); /// ``` pub fn derive_address_seed(seeds: &[&[u8]], program_id: &[u8; 32]) -> AddressSeed { - let mut inputs = Vec::with_capacity(seeds.len() + 1); + let mut inputs: [&[u8]; 16] = [&[]; 16]; - inputs.push(program_id.as_slice()); + inputs[0] = program_id.as_slice(); - inputs.extend(seeds); + for (i, seed) in seeds.iter().enumerate() { + inputs[i + 1] = seed; + } let seed = hashv_to_bn254_field_size_be_legacy(inputs.as_slice()); AddressSeed(seed) } fn hashv_to_bn254_field_size_be_legacy(bytes: &[&[u8]]) -> [u8; 32] { - let mut hashed_value: [u8; 32] = Keccak::hashv(bytes).unwrap(); + let mut hashed_value: [u8; 32] = Keccak::hashv(bytes) + .expect("Keccak::hashv should be infallible when keccak feature is enabled"); // Truncates to 31 bytes so that value is less than bn254 Fr modulo // field size. hashed_value[0] = 0; @@ -58,7 +63,7 @@ pub mod v1 { address_tree_pubkey: &[u8; 32], ) -> [u8; 32] { let input = [address_tree_pubkey.as_slice(), address_seed.0.as_slice()]; - hashv_to_bn254_field_size_be(input.as_slice()) + hashv_to_bn254_field_size_be_const_array::<3>(input.as_slice()).unwrap() } /// Derives an address from provided seeds. Returns that address and a singular @@ -92,7 +97,7 @@ pub mod v1 { } pub mod v2 { - use light_hasher::hash_to_field_size::hashv_to_bn254_field_size_be; + use light_hasher::hash_to_field_size::hashv_to_bn254_field_size_be_const_array; use super::AddressSeed; @@ -109,7 +114,8 @@ pub mod v2 { /// ); /// ``` pub fn derive_address_seed(seeds: &[&[u8]]) -> AddressSeed { - AddressSeed(hashv_to_bn254_field_size_be(seeds)) + // Max 16 seeds + 1 for bump + AddressSeed(hashv_to_bn254_field_size_be_const_array::<17>(seeds).unwrap()) } /// Derives an address for a compressed account, based on the provided singular diff --git a/sdk-libs/sdk-types/src/cpi_accounts/config.rs b/sdk-libs/sdk-types/src/cpi_accounts/config.rs index ffd09631f0..e82e985470 100644 --- a/sdk-libs/sdk-types/src/cpi_accounts/config.rs +++ b/sdk-libs/sdk-types/src/cpi_accounts/config.rs @@ -1,11 +1,6 @@ -#[cfg(feature = "anchor")] -use anchor_lang::{AnchorDeserialize, AnchorSerialize}; -#[cfg(not(feature = "anchor"))] -use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize}; +use light_compressed_account::CpiSigner; -use crate::CpiSigner; - -#[derive(Debug, Copy, Clone, PartialEq, AnchorSerialize, AnchorDeserialize)] +#[derive(Debug, Copy, Clone, PartialEq)] pub struct CpiAccountsConfig { pub cpi_context: bool, pub sol_compression_recipient: bool, diff --git a/sdk-libs/sdk-types/src/cpi_accounts/v1.rs b/sdk-libs/sdk-types/src/cpi_accounts/v1.rs index bb6bfe7f12..27921ff713 100644 --- a/sdk-libs/sdk-types/src/cpi_accounts/v1.rs +++ b/sdk-libs/sdk-types/src/cpi_accounts/v1.rs @@ -1,9 +1,15 @@ +#[cfg(all(not(feature = "std"), feature = "alloc"))] +use alloc::vec::Vec; +#[cfg(feature = "std")] +use std::vec::Vec; + use light_account_checks::AccountInfoTrait; +use light_compressed_account::CpiSigner; use crate::{ cpi_accounts::{CpiAccountsConfig, TreeAccounts}, error::{LightSdkTypesError, Result}, - CpiSigner, CPI_CONTEXT_ACCOUNT_2_DISCRIMINATOR, LIGHT_SYSTEM_PROGRAM_ID, SOL_POOL_PDA, + CPI_CONTEXT_ACCOUNT_2_DISCRIMINATOR, LIGHT_SYSTEM_PROGRAM_ID, SOL_POOL_PDA, }; #[repr(usize)] @@ -88,6 +94,7 @@ impl<'a, T: AccountInfoTrait + Clone> CpiAccounts<'a, T> { let cpi_context = res.cpi_context()?; let discriminator_bytes = &cpi_context.try_borrow_data()?[..8]; if discriminator_bytes != CPI_CONTEXT_ACCOUNT_2_DISCRIMINATOR.as_slice() { + #[cfg(feature = "std")] solana_msg::msg!("Invalid CPI context account: {:?}", cpi_context.pubkey()); return Err(LightSdkTypesError::InvalidCpiContextAccount); } @@ -230,6 +237,7 @@ impl<'a, T: AccountInfoTrait + Clone> CpiAccounts<'a, T> { .ok_or(LightSdkTypesError::CpiAccountsIndexOutOfBounds(system_len)) } + #[cfg(feature = "alloc")] pub fn tree_pubkeys(&self) -> Result> { Ok(self .tree_accounts()? @@ -248,6 +256,7 @@ impl<'a, T: AccountInfoTrait + Clone> CpiAccounts<'a, T> { } /// Create a vector of account info references + #[cfg(feature = "alloc")] pub fn to_account_infos(&self) -> Vec { // Skip system light program let refs = &self.account_infos()[1..]; diff --git a/sdk-libs/sdk-types/src/cpi_accounts/v2.rs b/sdk-libs/sdk-types/src/cpi_accounts/v2.rs index 6f423b43dd..46af83683b 100644 --- a/sdk-libs/sdk-types/src/cpi_accounts/v2.rs +++ b/sdk-libs/sdk-types/src/cpi_accounts/v2.rs @@ -1,11 +1,16 @@ +#[cfg(all(not(feature = "std"), feature = "alloc"))] +use alloc::vec::Vec; +#[cfg(feature = "std")] +use std::vec::Vec; + use light_account_checks::AccountInfoTrait; +use light_compressed_account::CpiSigner; #[cfg(feature = "cpi-context")] use crate::cpi_context_write::CpiContextWriteAccounts; use crate::{ cpi_accounts::{CpiAccountsConfig, TreeAccounts}, error::{LightSdkTypesError, Result}, - CpiSigner, }; #[repr(usize)] @@ -38,7 +43,7 @@ impl<'a, T: AccountInfoTrait + Clone> TryFrom<&CpiAccounts<'a, T>> { type Error = LightSdkTypesError; - fn try_from(value: &CpiAccounts<'a, T>) -> std::result::Result { + fn try_from(value: &CpiAccounts<'a, T>) -> core::result::Result { Ok(Self { fee_payer: value.fee_payer, authority: value.authority()?, diff --git a/sdk-libs/sdk-types/src/error.rs b/sdk-libs/sdk-types/src/error.rs index 700ab838aa..d766449ce8 100644 --- a/sdk-libs/sdk-types/src/error.rs +++ b/sdk-libs/sdk-types/src/error.rs @@ -2,7 +2,7 @@ use light_account_checks::error::AccountError; use light_hasher::HasherError; use thiserror::Error; -pub type Result = std::result::Result; +pub type Result = core::result::Result; #[derive(Debug, Error, PartialEq)] pub enum LightSdkTypesError { diff --git a/sdk-libs/sdk-types/src/instruction/account_info.rs b/sdk-libs/sdk-types/src/instruction/account_info.rs index d420efb249..4e7997491a 100644 --- a/sdk-libs/sdk-types/src/instruction/account_info.rs +++ b/sdk-libs/sdk-types/src/instruction/account_info.rs @@ -1,12 +1,18 @@ +#[cfg(all(not(feature = "std"), feature = "alloc"))] +use alloc::vec::Vec; +#[cfg(feature = "std")] +use std::vec::Vec; + +#[cfg(feature = "alloc")] +use light_compressed_account::compressed_account::PackedCompressedAccountWithMerkleContext; +use light_compressed_account::{ + compressed_account::PackedMerkleContext, + instruction_data::with_account_info::{CompressedAccountInfo, InAccountInfo}, +}; +#[cfg(feature = "alloc")] use light_compressed_account::{ - compressed_account::{ - CompressedAccount, CompressedAccountData, PackedCompressedAccountWithMerkleContext, - PackedMerkleContext, - }, - instruction_data::{ - data::OutputCompressedAccountWithPackedContext, - with_account_info::{CompressedAccountInfo, InAccountInfo}, - }, + compressed_account::{CompressedAccount, CompressedAccountData}, + instruction_data::data::OutputCompressedAccountWithPackedContext, Pubkey, }; @@ -69,10 +75,12 @@ pub trait CompressedAccountInfoTrait { input_data_hash: [u8; 32], discriminator: [u8; 8], ) -> Result<(), LightSdkTypesError>; + #[cfg(feature = "alloc")] fn input_compressed_account( &self, owner: Pubkey, ) -> Result, LightSdkTypesError>; + #[cfg(feature = "alloc")] fn output_compressed_account( &self, owner: Pubkey, @@ -170,6 +178,7 @@ impl CompressedAccountInfoTrait for CompressedAccountInfo { Ok(()) } + #[cfg(feature = "alloc")] fn input_compressed_account( &self, owner: Pubkey, @@ -197,6 +206,7 @@ impl CompressedAccountInfoTrait for CompressedAccountInfo { } } + #[cfg(feature = "alloc")] fn output_compressed_account( &self, owner: Pubkey, diff --git a/sdk-libs/sdk-types/src/lib.rs b/sdk-libs/sdk-types/src/lib.rs index 2438c267ab..b31d17f4e2 100644 --- a/sdk-libs/sdk-types/src/lib.rs +++ b/sdk-libs/sdk-types/src/lib.rs @@ -1,3 +1,8 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(not(feature = "std"), feature = "alloc"))] +extern crate alloc; + pub mod address; pub mod constants; pub mod cpi_accounts; @@ -12,11 +17,4 @@ use anchor_lang::{AnchorDeserialize, AnchorSerialize}; #[cfg(not(feature = "anchor"))] use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize}; pub use constants::*; - -/// Configuration struct containing program ID, CPI signer, and bump for Light Protocol -#[derive(Debug, Clone, Copy, PartialEq, Eq, AnchorDeserialize, AnchorSerialize)] -pub struct CpiSigner { - pub program_id: [u8; 32], - pub cpi_signer: [u8; 32], - pub bump: u8, -} +pub use light_compressed_account::CpiSigner; diff --git a/sdk-libs/sdk/Cargo.toml b/sdk-libs/sdk/Cargo.toml index 660403910d..547c637dca 100644 --- a/sdk-libs/sdk/Cargo.toml +++ b/sdk-libs/sdk/Cargo.toml @@ -11,7 +11,7 @@ crate-type = ["cdylib", "lib"] name = "light_sdk" [features] -default = ["borsh"] +default = [] idl-build = ["anchor-lang/idl-build"] anchor = [ "anchor-lang", @@ -35,19 +35,19 @@ solana-instruction = { workspace = true } anchor-lang = { workspace = true, optional = true } num-bigint = { workspace = true } -# only needed with solana-program -borsh = { workspace = true, optional = true } +borsh = { workspace = true } thiserror = { workspace = true } light-sdk-macros = { workspace = true } -light-sdk-types = { workspace = true } +light-sdk-types = { workspace = true, features = ["std"] } light-macros = { workspace = true } -light-compressed-account = { workspace = true } -light-hasher = { workspace = true } +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 } [dev-dependencies] num-bigint = { workspace = true } light-compressed-account = { workspace = true, features = ["new-unique"] } +light-hasher = { workspace = true, features = ["keccak"] } anchor-lang = { workspace = true } diff --git a/sdk-libs/sdk/src/cpi/mod.rs b/sdk-libs/sdk/src/cpi/mod.rs index 7750592242..fc02227bc3 100644 --- a/sdk-libs/sdk/src/cpi/mod.rs +++ b/sdk-libs/sdk/src/cpi/mod.rs @@ -48,6 +48,6 @@ pub use instruction::*; pub use invoke::InvokeLightSystemProgram; pub use light_compressed_account::instruction_data::traits::LightInstructionData; /// Derives cpi signer and bump to invoke the light system program at compile time. -pub use light_sdk_macros::derive_light_cpi_signer; +pub use light_macros::derive_light_cpi_signer; /// Contains program id, derived cpi signer, and bump, pub use light_sdk_types::{cpi_accounts::CpiAccountsConfig, CpiSigner}; diff --git a/sdk-libs/sdk/src/lib.rs b/sdk-libs/sdk/src/lib.rs index 340b5842e0..954163984b 100644 --- a/sdk-libs/sdk/src/lib.rs +++ b/sdk-libs/sdk/src/lib.rs @@ -160,9 +160,9 @@ pub use light_account_checks::{self, discriminator::Discriminator as LightDiscri pub use light_hasher; #[cfg(feature = "poseidon")] use light_hasher::DataHasher; +pub use light_macros::{derive_light_cpi_signer, derive_light_cpi_signer_pda}; pub use light_sdk_macros::{ - derive_light_cpi_signer, light_system_accounts, LightDiscriminator, LightHasher, - LightHasherSha, LightTraits, + light_system_accounts, LightDiscriminator, LightHasher, LightHasherSha, LightTraits, }; pub use light_sdk_types::constants; use solana_account_info::AccountInfo; diff --git a/sdk-libs/token-client/Cargo.toml b/sdk-libs/token-client/Cargo.toml index f3490abecc..7790616f5b 100644 --- a/sdk-libs/token-client/Cargo.toml +++ b/sdk-libs/token-client/Cargo.toml @@ -8,7 +8,7 @@ edition = { workspace = true } [dependencies] # Light Protocol dependencies light-compressed-token-types = { workspace = true } -light-compressed-account = { workspace = true } +light-compressed-account = { workspace = true, features = ["std"] } light-ctoken-types = { workspace = true } light-sdk = { workspace = true } light-client = { workspace = true, features = ["v2"] } diff --git a/sdk-tests/client-test/Cargo.toml b/sdk-tests/client-test/Cargo.toml index 2cf913b10d..20ab2c3169 100644 --- a/sdk-tests/client-test/Cargo.toml +++ b/sdk-tests/client-test/Cargo.toml @@ -24,7 +24,7 @@ light-sdk-pinocchio = { workspace = true } light-sdk-types = { workspace = true } light-zero-copy = { workspace = true } light-hasher = { workspace = true, features = ["poseidon"] } -light-compressed-account = { workspace = true } +light-compressed-account = { workspace = true, features = ["std"] } light-compressed-token = { workspace = true } light-indexed-array = { workspace = true } light-merkle-tree-reference = { workspace = true } diff --git a/sdk-tests/client-test/tests/sdk_compat.rs b/sdk-tests/client-test/tests/sdk_compat.rs index 0c893b622c..e2a75e4a8e 100644 --- a/sdk-tests/client-test/tests/sdk_compat.rs +++ b/sdk-tests/client-test/tests/sdk_compat.rs @@ -1,7 +1,6 @@ use light_hasher::HasherError; use light_sdk::error::LightSdkError as SolanaLightSdkError; use light_sdk_pinocchio::error::LightSdkError as PinocchioLightSdkError; -use light_zero_copy::errors::ZeroCopyError; fn generate_all_solana_errors() -> Vec { vec![ @@ -36,7 +35,6 @@ fn generate_all_solana_errors() -> Vec { SolanaLightSdkError::MetaCloseInputIsNone, SolanaLightSdkError::CpiAccountsIndexOutOfBounds(1), SolanaLightSdkError::Hasher(HasherError::IntegerOverflow), - SolanaLightSdkError::ZeroCopy(ZeroCopyError::Full), ] } @@ -73,7 +71,6 @@ fn generate_all_pinocchio_errors() -> Vec { PinocchioLightSdkError::MetaCloseInputIsNone, PinocchioLightSdkError::CpiAccountsIndexOutOfBounds(1), PinocchioLightSdkError::Hasher(HasherError::IntegerOverflow), - PinocchioLightSdkError::ZeroCopy(ZeroCopyError::Full), ] } diff --git a/sdk-tests/sdk-anchor-test/programs/sdk-anchor-test/Cargo.toml b/sdk-tests/sdk-anchor-test/programs/sdk-anchor-test/Cargo.toml index f7709e8bcb..5676da6da1 100644 --- a/sdk-tests/sdk-anchor-test/programs/sdk-anchor-test/Cargo.toml +++ b/sdk-tests/sdk-anchor-test/programs/sdk-anchor-test/Cargo.toml @@ -22,11 +22,12 @@ idl-build = ["anchor-lang/idl-build", "light-sdk/idl-build"] [dependencies] # Needs to be imported for LightHasher -light-hasher = { workspace = true, features = ["solana", "poseidon"] } +light-hasher = { workspace = true, features = ["solana", "poseidon", "sha256", "std"] } anchor-lang = { workspace = true } light-sdk = { workspace = true, features = ["anchor", "v2", "poseidon"] } light-sdk-types = { workspace = true } serial_test = { workspace = true } +solana-pubkey = { workspace = true } [target.'cfg(not(target_os = "solana"))'.dependencies] solana-sdk = { workspace = true } diff --git a/sdk-tests/sdk-pinocchio-v1-test/Cargo.toml b/sdk-tests/sdk-pinocchio-v1-test/Cargo.toml index 793a4badb2..420a029148 100644 --- a/sdk-tests/sdk-pinocchio-v1-test/Cargo.toml +++ b/sdk-tests/sdk-pinocchio-v1-test/Cargo.toml @@ -20,9 +20,9 @@ test-sbf = [] default = [] [dependencies] -light-sdk-pinocchio = { workspace = true } +light-sdk-pinocchio = { workspace = true, features = ["light-account"] } light-sdk-types = { workspace = true } -light-hasher = { workspace = true, features = ["poseidon"] } +light-hasher = { workspace = true, features = ["poseidon", "sha256"] } pinocchio = { workspace = true } light-macros = { workspace = true } borsh = { workspace = true } diff --git a/sdk-tests/sdk-pinocchio-v1-test/src/create_pda.rs b/sdk-tests/sdk-pinocchio-v1-test/src/create_pda.rs index 6c2abc259c..f69085a9e1 100644 --- a/sdk-tests/sdk-pinocchio-v1-test/src/create_pda.rs +++ b/sdk-tests/sdk-pinocchio-v1-test/src/create_pda.rs @@ -1,13 +1,12 @@ use borsh::{BorshDeserialize, BorshSerialize}; use light_sdk_pinocchio::{ - account::LightAccount, cpi::{ v1::{CpiAccounts, CpiAccountsConfig}, InvokeLightSystemProgram, LightCpiInstruction, }, error::LightSdkError, instruction::PackedAddressTreeInfo, - LightDiscriminator, LightHasher, ValidityProof, + LightAccount, LightDiscriminator, LightHasher, ValidityProof, }; use pinocchio::account_info::AccountInfo; @@ -35,8 +34,9 @@ pub fn create_pda(accounts: &[AccountInfo], instruction_data: &[u8]) -> Result<( let new_address_params = address_tree_info.into_new_address_params_packed(address_seed); + let program_id = crate::LIGHT_CPI_SIGNER.program_id.into(); let mut my_compressed_account = LightAccount::<'_, MyCompressedAccount>::new_init( - &crate::ID, + &program_id, Some(address), instruction_data.output_merkle_tree_index, ); diff --git a/sdk-tests/sdk-pinocchio-v1-test/src/update_pda.rs b/sdk-tests/sdk-pinocchio-v1-test/src/update_pda.rs index e3c51ad9ce..77b360fb53 100644 --- a/sdk-tests/sdk-pinocchio-v1-test/src/update_pda.rs +++ b/sdk-tests/sdk-pinocchio-v1-test/src/update_pda.rs @@ -1,15 +1,16 @@ use borsh::{BorshDeserialize, BorshSerialize}; use light_sdk_pinocchio::{ - account::LightAccount, cpi::{ v1::{CpiAccounts, CpiAccountsConfig}, InvokeLightSystemProgram, LightCpiInstruction, }, error::LightSdkError, instruction::account_meta::CompressedAccountMeta, - ValidityProof, + LightAccount, ValidityProof, +}; +use pinocchio::{ + account_info::AccountInfo, log::sol_log_compute_units, program_error::ProgramError, }; -use pinocchio::{account_info::AccountInfo, log::sol_log_compute_units}; use crate::create_pda::MyCompressedAccount; @@ -24,13 +25,15 @@ pub fn update_pda(accounts: &[AccountInfo], instruction_data: &[u8]) -> Result<( .map_err(|_| LightSdkError::Borsh)?; sol_log_compute_units(); + let program_id = crate::LIGHT_CPI_SIGNER.program_id.into(); let mut my_compressed_account = LightAccount::<'_, MyCompressedAccount>::new_mut( - &crate::ID, + &program_id, &instruction_data.my_compressed_account.meta, MyCompressedAccount { data: instruction_data.my_compressed_account.data, }, - )?; + ) + .map_err(|e| LightSdkError::ProgramError(ProgramError::Custom(u64::from(e) as u32)))?; sol_log_compute_units(); my_compressed_account.data = instruction_data.new_data; diff --git a/sdk-tests/sdk-pinocchio-v2-test/Cargo.toml b/sdk-tests/sdk-pinocchio-v2-test/Cargo.toml index 315903417e..499a6a7920 100644 --- a/sdk-tests/sdk-pinocchio-v2-test/Cargo.toml +++ b/sdk-tests/sdk-pinocchio-v2-test/Cargo.toml @@ -20,9 +20,9 @@ test-sbf = [] default = [] [dependencies] -light-sdk-pinocchio = { workspace = true, features = ["v2"] } +light-sdk-pinocchio = { workspace = true, features = ["v2", "light-account"] } light-sdk-types = { workspace = true, features = ["v2"] } -light-hasher = { workspace = true, features = ["poseidon"] } +light-hasher = { workspace = true, features = ["poseidon", "sha256"] } pinocchio = { workspace = true } light-macros = { workspace = true } borsh = { workspace = true } diff --git a/sdk-tests/sdk-pinocchio-v2-test/src/create_pda.rs b/sdk-tests/sdk-pinocchio-v2-test/src/create_pda.rs index e158aa23d3..4170b8d616 100644 --- a/sdk-tests/sdk-pinocchio-v2-test/src/create_pda.rs +++ b/sdk-tests/sdk-pinocchio-v2-test/src/create_pda.rs @@ -1,6 +1,5 @@ use borsh::{BorshDeserialize, BorshSerialize}; use light_sdk_pinocchio::{ - account::LightAccount, cpi::{ v1::CpiAccountsConfig, v2::{CpiAccounts, LightSystemProgramCpi}, @@ -8,7 +7,7 @@ use light_sdk_pinocchio::{ }, error::LightSdkError, instruction::PackedAddressTreeInfo, - LightDiscriminator, LightHasher, ValidityProof, + LightAccount, LightDiscriminator, LightHasher, ValidityProof, }; use pinocchio::{account_info::AccountInfo, program_error::ProgramError}; @@ -48,8 +47,9 @@ pub fn create_pda(accounts: &[AccountInfo], instruction_data: &[u8]) -> Result<( let new_address_params = address_tree_info.into_new_address_params_assigned_packed(address_seed, Some(0)); + let program_id = crate::LIGHT_CPI_SIGNER.program_id.into(); let mut my_compressed_account = LightAccount::<'_, MyCompressedAccount>::new_init( - &crate::ID, + &program_id, Some(address), instruction_data.output_merkle_tree_index, ); diff --git a/sdk-tests/sdk-pinocchio-v2-test/src/update_pda.rs b/sdk-tests/sdk-pinocchio-v2-test/src/update_pda.rs index 561ab999f2..af0119e7a0 100644 --- a/sdk-tests/sdk-pinocchio-v2-test/src/update_pda.rs +++ b/sdk-tests/sdk-pinocchio-v2-test/src/update_pda.rs @@ -1,17 +1,16 @@ use borsh::{BorshDeserialize, BorshSerialize}; use light_sdk_pinocchio::{ - account::LightAccount, cpi::{ v1::CpiAccountsConfig, v2::{CpiAccounts, LightSystemProgramCpi}, InvokeLightSystemProgram, LightCpiInstruction, }, instruction::account_meta::CompressedAccountMeta, - ValidityProof, + LightAccount, ValidityProof, }; use pinocchio::{account_info::AccountInfo, program_error::ProgramError}; -use crate::create_pda::MyCompressedAccount; +use crate::{create_pda::MyCompressedAccount, LIGHT_CPI_SIGNER}; /// CU usage: /// - sdk pre system program 9,183k CU @@ -25,13 +24,15 @@ pub fn update_pda( let instruction_data = UpdatePdaInstructionData::deserialize(&mut instruction_data) .map_err(|_| ProgramError::BorshIoError)?; + let program_id = LIGHT_CPI_SIGNER.program_id.into(); let mut my_compressed_account = LightAccount::<'_, MyCompressedAccount>::new_mut( - &crate::ID, + &program_id, &instruction_data.my_compressed_account.meta, MyCompressedAccount { data: instruction_data.my_compressed_account.data, }, - )?; + ) + .map_err(|e| ProgramError::Custom(u64::from(e) as u32))?; my_compressed_account.data = instruction_data.new_data; diff --git a/sdk-tests/sdk-token-test/Cargo.toml b/sdk-tests/sdk-token-test/Cargo.toml index da527b4be6..7fae657fcd 100644 --- a/sdk-tests/sdk-token-test/Cargo.toml +++ b/sdk-tests/sdk-token-test/Cargo.toml @@ -36,7 +36,7 @@ anchor-lang = { workspace = true } light-hasher = { workspace = true } light-sdk = { workspace = true, features = ["v2", "cpi-context"] } light-sdk-types = { workspace = true, features = ["cpi-context"] } -light-compressed-account = { workspace = true } +light-compressed-account = { workspace = true, features = ["std"] } arrayvec = { workspace = true } light-batched-merkle-tree = { workspace = true } light-ctoken-types = { workspace = true, features = ["anchor"] } diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 107f55af94..6cd18f56f0 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -15,7 +15,7 @@ light-concurrent-merkle-tree = { workspace = true } light-hash-set = { workspace = true } light-hasher = { workspace = true, features = ["poseidon"] } light-indexed-merkle-tree = { workspace = true } -light-compressed-account = { workspace = true } +light-compressed-account = { workspace = true, features = ["std"] } light-merkle-tree-metadata = { workspace = true } num-bigint = { workspace = true } rand = { workspace = true }