diff --git a/Cargo.lock b/Cargo.lock index 3e0dfe1abe..725fe02283 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,7 +30,7 @@ dependencies = [ "light-merkle-tree-metadata", "light-merkle-tree-reference", "light-prover-client", - "light-utils", + "light-utils 1.1.0", "light-verifier", "num-bigint 0.4.6", "num-traits", @@ -64,7 +64,7 @@ dependencies = [ "light-prover-client", "light-system-program", "light-test-utils", - "light-utils", + "light-utils 1.1.0", "light-verifier", "memoffset 0.9.1", "num-bigint 0.4.6", @@ -1250,7 +1250,7 @@ dependencies = [ "light-prover-client", "light-system-program", "light-test-utils", - "light-utils", + "light-utils 1.1.0", "light-verifier", "num-bigint 0.4.6", "num-traits", @@ -1408,7 +1408,7 @@ dependencies = [ "anchor-lang", "light-hasher", "light-system-program", - "light-utils", + "light-utils 1.1.0", ] [[package]] @@ -1748,7 +1748,7 @@ dependencies = [ "light-registry", "light-system-program", "light-test-utils", - "light-utils", + "light-utils 1.1.0", "light-verifier", "num-bigint 0.4.6", "num-traits", @@ -2025,7 +2025,7 @@ dependencies = [ "light-registry", "light-system-program", "light-test-utils", - "light-utils", + "light-utils 1.1.0", "light-verifier", "photon-api", "prometheus", @@ -2065,7 +2065,7 @@ dependencies = [ "light-prover-client", "light-registry", "light-system-program", - "light-utils", + "light-utils 1.1.0", "log", "num-bigint 0.4.6", "num-traits", @@ -2921,8 +2921,9 @@ dependencies = [ "light-merkle-tree-metadata", "light-merkle-tree-reference", "light-prover-client", - "light-utils", + "light-utils 1.1.0", "light-verifier", + "light-zero-copy", "rand 0.8.5", "serial_test", "solana-program", @@ -2936,7 +2937,7 @@ version = "0.1.0" dependencies = [ "bitvec", "fastmurmur3", - "light-utils", + "light-utils 1.1.0", "num-bigint 0.4.6", "num-traits", "rand 0.8.5", @@ -2947,10 +2948,11 @@ dependencies = [ [[package]] name = "light-bounded-vec" version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47ced86d6f1b163a04d5d0be44f8bbeedb11d32f73af27812bbd144e0f1f1a42" dependencies = [ "bytemuck", "memoffset 0.9.1", - "rand 0.8.5", "solana-program", "thiserror", ] @@ -2972,7 +2974,7 @@ dependencies = [ "light-sdk", "light-system-program", "light-test-utils", - "light-utils", + "light-utils 1.1.0", "log", "num-bigint 0.4.6", "num-traits", @@ -3000,7 +3002,7 @@ dependencies = [ "light-hasher", "light-heap", "light-system-program", - "light-utils", + "light-utils 1.1.0", "rand 0.8.5", "solana-sdk", "solana-security-txt", @@ -3020,7 +3022,7 @@ dependencies = [ "light-hash-set", "light-hasher", "light-merkle-tree-reference", - "light-utils", + "light-utils 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "memoffset 0.9.1", "num-bigint 0.4.6", "num-traits", @@ -3039,7 +3041,7 @@ dependencies = [ "ark-bn254", "ark-ff", "light-heap", - "light-utils", + "light-utils 1.1.0", "num-bigint 0.4.6", "num-traits", "rand 0.8.5", @@ -3077,7 +3079,7 @@ dependencies = [ "light-hash-set", "light-hasher", "light-merkle-tree-reference", - "light-utils", + "light-utils 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "memoffset 0.9.1", "num-bigint 0.4.6", "num-traits", @@ -3094,7 +3096,7 @@ version = "1.1.0" dependencies = [ "bs58 0.5.1", "light-hasher", - "light-utils", + "light-utils 1.1.0", "proc-macro2", "quote", "syn 2.0.85", @@ -3154,7 +3156,7 @@ dependencies = [ "light-registry", "light-sdk", "light-system-program", - "light-utils", + "light-utils 1.1.0", "light-verifier", "log", "num-bigint 0.4.6", @@ -3181,7 +3183,7 @@ dependencies = [ "light-hasher", "light-indexed-merkle-tree", "light-merkle-tree-reference", - "light-utils", + "light-utils 1.1.0", "log", "num-bigint 0.4.6", "num-traits", @@ -3232,7 +3234,7 @@ dependencies = [ "light-prover-client", "light-sdk-macros", "light-system-program", - "light-utils", + "light-utils 1.1.0", "light-verifier", "num-bigint 0.4.6", "num-traits", @@ -3253,7 +3255,7 @@ version = "0.4.0" dependencies = [ "bs58 0.5.1", "light-hasher", - "light-utils", + "light-utils 1.1.0", "prettyplease", "proc-macro2", "quote", @@ -3274,7 +3276,7 @@ dependencies = [ "light-heap", "light-indexed-merkle-tree", "light-macros", - "light-utils", + "light-utils 1.1.0", "light-verifier", "rand 0.8.5", "solana-sdk", @@ -3307,7 +3309,7 @@ dependencies = [ "light-prover-client", "light-registry", "light-system-program", - "light-utils", + "light-utils 1.1.0", "light-verifier", "log", "memoffset 0.9.1", @@ -3346,6 +3348,22 @@ dependencies = [ "thiserror", ] +[[package]] +name = "light-utils" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e2b5a4959cb0456b483a20b4f3930920949137c00e76b5d0f9bf8d701a3c6a" +dependencies = [ + "anyhow", + "ark-bn254", + "ark-ff", + "light-bounded-vec", + "num-bigint 0.4.6", + "rand 0.8.5", + "solana-program", + "thiserror", +] + [[package]] name = "light-verifier" version = "1.1.0" @@ -3353,7 +3371,7 @@ dependencies = [ "borsh 0.10.3", "groth16-solana", "light-prover-client", - "light-utils", + "light-utils 1.1.0", "reqwest 0.11.27", "serial_test", "solana-program", @@ -3361,6 +3379,16 @@ dependencies = [ "tokio", ] +[[package]] +name = "light-zero-copy" +version = "0.1.0" +dependencies = [ + "num-traits", + "rand 0.8.5", + "solana-program", + "thiserror", +] + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -4628,7 +4656,7 @@ dependencies = [ "light-registry", "light-system-program", "light-test-utils", - "light-utils", + "light-utils 1.1.0", "light-verifier", "num-bigint 0.4.6", "num-traits", @@ -5006,7 +5034,7 @@ dependencies = [ "light-sdk", "light-sdk-macros", "light-test-utils", - "light-utils", + "light-utils 1.1.0", "light-verifier", "solana-program-test", "solana-sdk", @@ -7120,7 +7148,7 @@ dependencies = [ "light-registry", "light-system-program", "light-test-utils", - "light-utils", + "light-utils 1.1.0", "light-verifier", "num-bigint 0.4.6", "num-traits", @@ -7150,7 +7178,7 @@ dependencies = [ "light-registry", "light-system-program", "light-test-utils", - "light-utils", + "light-utils 1.1.0", "light-verifier", "num-bigint 0.4.6", "num-traits", @@ -8456,7 +8484,7 @@ dependencies = [ "light-hash-set", "light-hasher", "light-indexed-merkle-tree", - "light-utils", + "light-utils 1.1.0", "num-bigint 0.4.6", "quote", "rand 0.8.5", diff --git a/Cargo.toml b/Cargo.toml index e37afd1c05..7a58ce542a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ members = [ "program-libs/utils", "program-libs/verifier", "program-libs/merkle-tree-metadata", - "program-libs/bounded-vec", + "program-libs/zero-copy", "program-libs/concurrent-merkle-tree", "program-libs/hash-set", "program-libs/indexed-merkle-tree", @@ -95,6 +95,7 @@ light-sdk = { path = "sdk-libs/sdk", version = "0.11.0" } light-sdk-macros = { path = "sdk-libs/macros", version = "0.4.0" } light-utils = { path = "program-libs/utils", version = "1.1.0" } light-verifier = { path = "program-libs/verifier", version = "1.1.0" } +light-zero-copy = { path = "program-libs/zero-copy", version = "0.1.0" } photon-api = { path = "sdk-libs/photon-api", version = "0.45.0" } forester-utils = { path = "forester-utils", version = "1.2.0" } account-compression = { path = "programs/account-compression", version = "1.2.0", features = ["cpi"] } @@ -107,7 +108,7 @@ light-batched-merkle-tree = { path = "program-libs/batched-merkle-tree", version light-merkle-tree-metadata = { path = "program-libs/merkle-tree-metadata", version = "0.1.0" } aligned-sized = { path = "program-libs/aligned-sized", version = "1.1.0" } light-bloom-filter = { path = "program-libs/bloom-filter", version = "0.1.0" } -light-bounded-vec = { path = "program-libs/bounded-vec", version = "1.1.0" } +light-bounded-vec = { version = "1.1.0" } light-poseidon = { version = "0.2.0" } light-test-utils = { path = "program-tests/utils", version = "1.2.1" } create-address-program-test = { path = "program-tests/create-address-test-program", version = "1.0.0" } diff --git a/program-libs/batched-merkle-tree/Cargo.toml b/program-libs/batched-merkle-tree/Cargo.toml index 9efc4bd243..454e8de76e 100644 --- a/program-libs/batched-merkle-tree/Cargo.toml +++ b/program-libs/batched-merkle-tree/Cargo.toml @@ -14,7 +14,7 @@ solana = [] [dependencies] aligned-sized = { workspace=true} solana-program = { workspace = true } -light-bounded-vec = { workspace=true, features = ["solana"] } +light-zero-copy = { workspace=true, features = ["solana"] } light-hasher = { workspace=true, features = ["solana"] } light-utils = { workspace=true } light-bloom-filter = { workspace=true, features = ["solana"] } @@ -25,6 +25,7 @@ borsh = "0.10" light-macros = {workspace = true} anchor-lang = { workspace = true , optional = true } bytemuck = { workspace = true } +light-bounded-vec = { workspace = true } [dev-dependencies] rand = "0.8.5" diff --git a/program-libs/batched-merkle-tree/src/batch.rs b/program-libs/batched-merkle-tree/src/batch.rs index 2fdefffd14..ec475b1383 100644 --- a/program-libs/batched-merkle-tree/src/batch.rs +++ b/program-libs/batched-merkle-tree/src/batch.rs @@ -1,6 +1,6 @@ use light_bloom_filter::BloomFilter; -use light_bounded_vec::BoundedVec; use light_hasher::{Hasher, Poseidon}; +use light_zero_copy::vec::ZeroCopyVecUsize; use solana_program::msg; use crate::errors::BatchedMerkleTreeError; @@ -17,7 +17,7 @@ pub enum BatchState { } #[repr(C)] -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Batch { /// Number of inserted elements in the zkp batch. num_inserted: u64, @@ -140,7 +140,7 @@ impl Batch { pub fn store_value( &mut self, value: &[u8; 32], - value_store: &mut BoundedVec<[u8; 32]>, + value_store: &mut ZeroCopyVecUsize<[u8; 32]>, ) -> Result<(), BatchedMerkleTreeError> { if self.state != BatchState::CanBeFilled { return Err(BatchedMerkleTreeError::BatchNotReady); @@ -152,8 +152,8 @@ impl Batch { pub fn store_and_hash_value( &mut self, value: &[u8; 32], - value_store: &mut BoundedVec<[u8; 32]>, - hashchain_store: &mut BoundedVec<[u8; 32]>, + value_store: &mut ZeroCopyVecUsize<[u8; 32]>, + hashchain_store: &mut ZeroCopyVecUsize<[u8; 32]>, ) -> Result<(), BatchedMerkleTreeError> { self.store_value(value, value_store)?; self.add_to_hash_chain(value, hashchain_store) @@ -166,7 +166,7 @@ impl Batch { bloom_filter_value: &[u8; 32], hashchain_value: &[u8; 32], store: &mut [u8], - hashchain_store: &mut BoundedVec<[u8; 32]>, + hashchain_store: &mut ZeroCopyVecUsize<[u8; 32]>, ) -> Result<(), BatchedMerkleTreeError> { let mut bloom_filter = BloomFilter::new(self.num_iters as usize, self.bloom_filter_capacity, store)?; @@ -177,7 +177,7 @@ impl Batch { pub fn add_to_hash_chain( &mut self, value: &[u8; 32], - hashchain_store: &mut BoundedVec<[u8; 32]>, + hashchain_store: &mut ZeroCopyVecUsize<[u8; 32]>, ) -> Result<(), BatchedMerkleTreeError> { if self.num_inserted == self.zkp_batch_size || self.num_inserted == 0 { hashchain_store.push(*value)?; @@ -319,8 +319,24 @@ mod tests { fn test_store_value() { let mut batch = get_test_batch(); - let mut value_store = BoundedVec::with_capacity(batch.batch_size as usize); - let mut hashchain_store = BoundedVec::with_capacity(batch.get_hashchain_store_len()); + let mut value_store_bytes = + vec![ + 0u8; + ZeroCopyVecUsize::<[u8; 32]>::required_size_for_capacity(batch.batch_size as usize) + ]; + let mut value_store = + ZeroCopyVecUsize::new(batch.batch_size as usize, &mut value_store_bytes).unwrap(); + let mut hashchain_store_bytes = vec![ + 0u8; + ZeroCopyVecUsize::<[u8; 32]>::required_size_for_capacity( + batch.get_hashchain_store_len() + ) + ]; + let mut hashchain_store = ZeroCopyVecUsize::new( + batch.get_hashchain_store_len(), + hashchain_store_bytes.as_mut_slice(), + ) + .unwrap(); let mut ref_batch = get_test_batch(); for i in 0..batch.batch_size { @@ -361,9 +377,18 @@ mod tests { // Behavior Input queue let mut batch = get_test_batch(); let mut store = vec![0u8; 20_000]; - let hashchain_store_len = batch.get_hashchain_store_len(); - let mut hashchain_store: BoundedVec<[u8; 32]> = - BoundedVec::with_capacity(hashchain_store_len); + + let mut hashchain_store_bytes = vec![ + 0u8; + ZeroCopyVecUsize::<[u8; 32]>::required_size_for_capacity( + batch.get_hashchain_store_len() + ) + ]; + let mut hashchain_store = ZeroCopyVecUsize::<[u8; 32]>::new( + batch.get_hashchain_store_len(), + hashchain_store_bytes.as_mut_slice(), + ) + .unwrap(); let mut ref_batch = get_test_batch(); for i in 0..batch.batch_size { @@ -403,9 +428,17 @@ mod tests { #[test] fn test_add_to_hash_chain() { let mut batch = get_test_batch(); - let hashchain_store_len = batch.get_hashchain_store_len(); - let mut hashchain_store: BoundedVec<[u8; 32]> = - BoundedVec::with_capacity(hashchain_store_len); + let mut hashchain_store_bytes = vec![ + 0u8; + ZeroCopyVecUsize::<[u8; 32]>::required_size_for_capacity( + batch.get_hashchain_store_len() + ) + ]; + let mut hashchain_store = ZeroCopyVecUsize::<[u8; 32]>::new( + batch.get_hashchain_store_len(), + hashchain_store_bytes.as_mut_slice(), + ) + .unwrap(); let value = [1u8; 32]; assert!(batch @@ -433,9 +466,17 @@ mod tests { let value = [1u8; 32]; let mut store = vec![0u8; 20_000]; - let hashchain_store_len = batch.get_hashchain_store_len(); - let mut hashchain_store: BoundedVec<[u8; 32]> = - BoundedVec::with_capacity(hashchain_store_len); + let mut hashchain_store_bytes = vec![ + 0u8; + ZeroCopyVecUsize::<[u8; 32]>::required_size_for_capacity( + batch.get_hashchain_store_len() + ) + ]; + let mut hashchain_store = ZeroCopyVecUsize::<[u8; 32]>::new( + batch.get_hashchain_store_len(), + hashchain_store_bytes.as_mut_slice(), + ) + .unwrap(); assert!(batch.check_non_inclusion(&value, &mut store).is_ok()); let ref_batch = get_test_batch(); @@ -515,15 +556,32 @@ mod tests { batch.get_first_ready_zkp_batch(), Err(BatchedMerkleTreeError::BatchNotReady.into()) ); - let mut bounded_vec = BoundedVec::with_capacity(batch.batch_size as usize); - let mut value_store = BoundedVec::with_capacity(batch.batch_size as usize); + let mut value_store_bytes = + vec![ + 0u8; + ZeroCopyVecUsize::<[u8; 32]>::required_size_for_capacity(batch.batch_size as usize) + ]; + let mut value_store = + ZeroCopyVecUsize::<[u8; 32]>::new(batch.batch_size as usize, &mut value_store_bytes) + .unwrap(); + let mut hashchain_store_bytes = vec![ + 0u8; + ZeroCopyVecUsize::<[u8; 32]>::required_size_for_capacity( + batch.get_hashchain_store_len() + ) + ]; + let mut hashchain_store = ZeroCopyVecUsize::<[u8; 32]>::new( + batch.get_hashchain_store_len(), + hashchain_store_bytes.as_mut_slice(), + ) + .unwrap(); for i in 0..batch.batch_size + 10 { let mut value = [0u8; 32]; value[24..].copy_from_slice(&i.to_be_bytes()); if i < batch.batch_size { batch - .store_and_hash_value(&value, &mut value_store, &mut bounded_vec) + .store_and_hash_value(&value, &mut value_store, &mut hashchain_store) .unwrap(); } if (i + 1) % batch.zkp_batch_size == 0 && i != 0 { diff --git a/program-libs/batched-merkle-tree/src/errors.rs b/program-libs/batched-merkle-tree/src/errors.rs index 61e1913256..44e10eff33 100644 --- a/program-libs/batched-merkle-tree/src/errors.rs +++ b/program-libs/batched-merkle-tree/src/errors.rs @@ -1,14 +1,12 @@ use light_bloom_filter::BloomFilterError; -use light_bounded_vec::BoundedVecError; use light_hasher::HasherError; use light_merkle_tree_metadata::errors::MerkleTreeMetadataError; use light_utils::UtilsError; use light_verifier::VerifierError; +use light_zero_copy::errors::ZeroCopyError; use solana_program::program_error::ProgramError; use thiserror::Error; -use crate::zero_copy::ZeroCopyError; - #[derive(Debug, Error, PartialEq)] pub enum BatchedMerkleTreeError { #[error("Batch is not ready to be inserted")] @@ -29,8 +27,6 @@ pub enum BatchedMerkleTreeError { Hasher(#[from] HasherError), #[error("Utils error {0}")] Utils(#[from] UtilsError), - #[error("Bounded vector error: {0}")] - BoundedVec(#[from] BoundedVecError), #[error("Zero copy error {0}")] ZeroCopy(#[from] ZeroCopyError), #[error("Merkle tree metadata error {0}")] @@ -55,7 +51,6 @@ impl From for u32 { BatchedMerkleTreeError::BatchSizeNotDivisibleByZkpBatchSize => 14306, BatchedMerkleTreeError::InclusionProofByIndexFailed => 14307, BatchedMerkleTreeError::Hasher(e) => e.into(), - BatchedMerkleTreeError::BoundedVec(e) => e.into(), BatchedMerkleTreeError::ZeroCopy(e) => e.into(), BatchedMerkleTreeError::MerkleTreeMetadata(e) => e.into(), BatchedMerkleTreeError::BloomFilter(e) => e.into(), diff --git a/program-libs/batched-merkle-tree/src/initialize_address_tree.rs b/program-libs/batched-merkle-tree/src/initialize_address_tree.rs index bef21695fa..21feb28d70 100644 --- a/program-libs/batched-merkle-tree/src/initialize_address_tree.rs +++ b/program-libs/batched-merkle-tree/src/initialize_address_tree.rs @@ -87,7 +87,7 @@ impl Default for InitAddressTreeAccountsInstructionData { input_queue_num_batches: 2, height: 40, root_history_capacity: (DEFAULT_BATCH_SIZE / DEFAULT_ZKP_BATCH_SIZE * 2) as u32, - bloom_filter_capacity: (DEFAULT_BATCH_SIZE + 1) * 8, + bloom_filter_capacity: DEFAULT_BATCH_SIZE * 8, network_fee: Some(5000), rollover_threshold: Some(95), close_threshold: None, @@ -180,7 +180,7 @@ pub fn validate_batched_address_tree_params(params: InitAddressTreeAccountsInstr ); assert!(params.bloom_filter_num_iters > 0); - assert!(params.bloom_filter_capacity > params.input_queue_batch_size * 8); + assert!(params.bloom_filter_capacity >= params.input_queue_batch_size * 8); assert_eq!( params.bloom_filter_capacity % 8, 0, diff --git a/program-libs/batched-merkle-tree/src/initialize_state_tree.rs b/program-libs/batched-merkle-tree/src/initialize_state_tree.rs index 7c81d21626..9af970c2fb 100644 --- a/program-libs/batched-merkle-tree/src/initialize_state_tree.rs +++ b/program-libs/batched-merkle-tree/src/initialize_state_tree.rs @@ -103,7 +103,7 @@ impl Default for InitStateTreeAccountsInstructionData { output_queue_num_batches: 2, height: DEFAULT_BATCH_STATE_TREE_HEIGHT, root_history_capacity: (DEFAULT_BATCH_SIZE / DEFAULT_ZKP_BATCH_SIZE * 2) as u32, - bloom_filter_capacity: (DEFAULT_BATCH_SIZE + 1) * 8, + bloom_filter_capacity: DEFAULT_BATCH_SIZE * 8, network_fee: Some(5000), rollover_threshold: Some(95), close_threshold: None, @@ -268,7 +268,7 @@ pub fn validate_batched_tree_params(params: InitStateTreeAccountsInstructionData ); assert!(params.bloom_filter_num_iters > 0); - assert!(params.bloom_filter_capacity > params.input_queue_batch_size * 8); + assert!(params.bloom_filter_capacity >= params.input_queue_batch_size * 8); assert_eq!( params.bloom_filter_capacity % 8, 0, @@ -362,7 +362,7 @@ fn _assert_mt_zero_copy_inited( ); } assert_eq!( - account.hashchain_store[0].metadata().capacity(), + account.hashchain_store[0].capacity(), ref_account.queue_metadata.get_num_zkp_batches() as usize, "hashchain_store mismatch" ); diff --git a/program-libs/batched-merkle-tree/src/lib.rs b/program-libs/batched-merkle-tree/src/lib.rs index 951ab02ae5..1bbbf4393c 100644 --- a/program-libs/batched-merkle-tree/src/lib.rs +++ b/program-libs/batched-merkle-tree/src/lib.rs @@ -10,7 +10,6 @@ pub mod merkle_tree; pub mod queue; pub mod rollover_address_tree; pub mod rollover_state_tree; -pub mod zero_copy; #[cfg(feature = "anchor")] use anchor_lang::{AnchorDeserialize as BorshDeserialize, AnchorSerialize as BorshSerialize}; #[cfg(not(feature = "anchor"))] diff --git a/program-libs/batched-merkle-tree/src/merkle_tree.rs b/program-libs/batched-merkle-tree/src/merkle_tree.rs index eebaaa07ab..66f3f277f6 100644 --- a/program-libs/batched-merkle-tree/src/merkle_tree.rs +++ b/program-libs/batched-merkle-tree/src/merkle_tree.rs @@ -1,8 +1,7 @@ -use std::mem::{size_of, ManuallyDrop}; +use std::ops::{Deref, DerefMut}; use aligned_sized::aligned_sized; use bytemuck::{Pod, Zeroable}; -use light_bounded_vec::{BoundedVec, CyclicBoundedVec, CyclicBoundedVecMetadata}; use light_hasher::{Discriminator, Hasher, Poseidon}; use light_merkle_tree_metadata::{ access::AccessMetadata, @@ -20,6 +19,10 @@ use light_verifier::{ verify_batch_address_update, verify_batch_append_with_proofs, verify_batch_update, CompressedProof, }; +use light_zero_copy::{ + cyclic_vec::ZeroCopyCyclicVecUsize, errors::ZeroCopyError, slice_mut::ZeroCopySliceMutUsize, + vec::ZeroCopyVecUsize, wrapped_pointer_mut::WrappedPointerMut, +}; use solana_program::{account_info::AccountInfo, msg, pubkey::Pubkey}; use super::{ @@ -38,7 +41,6 @@ use crate::{ initialize_address_tree::InitAddressTreeAccountsInstructionData, initialize_state_tree::InitStateTreeAccountsInstructionData, queue::BatchedQueueAccount, - zero_copy::{bytes_to_struct_unchecked, ZeroCopyError}, BorshDeserialize, BorshSerialize, }; @@ -102,7 +104,7 @@ pub struct CreateTreeParams { impl CreateTreeParams { pub fn from_state_ix_params(data: InitStateTreeAccountsInstructionData, owner: Pubkey) -> Self { CreateTreeParams { - owner, // Assuming default owner, modify as needed + owner, program_owner: data.program_owner, forester: data.forester, rollover_threshold: data.rollover_threshold, @@ -122,7 +124,7 @@ impl CreateTreeParams { owner: Pubkey, ) -> Self { CreateTreeParams { - owner, // Assuming default owner, modify as needed + owner, program_owner: data.program_owner, forester: data.forester, rollover_threshold: data.rollover_threshold, @@ -141,8 +143,9 @@ impl CreateTreeParams { impl BatchedMerkleTreeMetadata { pub fn get_account_size(&self) -> Result { let account_size = Self::LEN; - let root_history_size = size_of::() - + (size_of::<[u8; 32]>() * self.root_history_capacity as usize); + let root_history_size = ZeroCopyCyclicVecUsize::<[u8; 32]>::required_size_for_capacity( + self.root_history_capacity as usize, + ); let size = account_size + root_history_size + queue_account_size(&self.queue_metadata, QueueType::Input as u64)?; @@ -223,12 +226,26 @@ impl BatchedMerkleTreeMetadata { #[repr(C)] #[derive(Debug, PartialEq)] pub struct BatchedMerkleTreeAccount { - metadata: *mut BatchedMerkleTreeMetadata, - pub root_history: ManuallyDrop>, - pub batches: ManuallyDrop>, - pub value_vecs: Vec>>, - pub bloom_filter_stores: Vec>>, - pub hashchain_store: Vec>>, + metadata: WrappedPointerMut, + pub root_history: ZeroCopyCyclicVecUsize<[u8; 32]>, + pub batches: ZeroCopySliceMutUsize, + pub value_vecs: Vec>, + pub bloom_filter_stores: Vec>, + pub hashchain_store: Vec>, +} + +impl Deref for BatchedMerkleTreeAccount { + type Target = BatchedMerkleTreeMetadata; + + fn deref(&self) -> &Self::Target { + self.metadata.get() + } +} + +impl DerefMut for BatchedMerkleTreeAccount { + fn deref_mut(&mut self) -> &mut Self::Target { + self.metadata.get_mut() + } } /// Get batch from account. @@ -266,11 +283,13 @@ pub struct AppendBatchProofInputsIx { } impl BatchedMerkleTreeAccount { + // TODO: remove pub fn get_metadata(&self) -> &BatchedMerkleTreeMetadata { - unsafe { self.metadata.as_ref() }.unwrap() + self.metadata.get() } + // TODO: remove pub fn get_metadata_mut(&mut self) -> &mut BatchedMerkleTreeMetadata { - unsafe { self.metadata.as_mut() }.unwrap() + self.metadata.get_mut() } // TODO: add unit test @@ -292,7 +311,7 @@ impl BatchedMerkleTreeAccount { } pub fn address_tree_from_account_info_mut( - account_info: &AccountInfo<'_>, + account_info: &AccountInfo, ) -> Result { Self::from_account_info_mut::( &ACCOUNT_COMPRESSION_PROGRAM_ID, @@ -302,7 +321,7 @@ impl BatchedMerkleTreeAccount { pub fn from_account_info_mut( program_id: &Pubkey, - account_info: &AccountInfo<'_>, + account_info: &AccountInfo, ) -> Result { check_account_info_mut::(program_id, account_info)?; let account_data = &mut account_info.try_borrow_mut_data()?; @@ -320,32 +339,33 @@ impl BatchedMerkleTreeAccount { fn from_bytes_mut( account_data: &mut [u8], ) -> Result { - unsafe { - let metadata = bytes_to_struct_unchecked::(account_data)?; - if (*metadata).tree_type != TREE_TYPE { - return Err(MerkleTreeMetadataError::InvalidTreeType.into()); - } - if account_data.len() != (*metadata).get_account_size()? { - return Err(ZeroCopyError::InvalidAccountSize.into()); - } - let mut start_offset = BatchedMerkleTreeMetadata::LEN; - let root_history = CyclicBoundedVec::deserialize(account_data, &mut start_offset)?; - let (batches, value_vecs, bloom_filter_stores, hashchain_store) = input_queue_bytes( - &(*metadata).queue_metadata, + let metadata = + WrappedPointerMut::::from_bytes_with_discriminator( account_data, - QueueType::Input as u64, - &mut start_offset, )?; - - Ok(BatchedMerkleTreeAccount { - metadata, - root_history, - batches, - value_vecs, - bloom_filter_stores, - hashchain_store, - }) + if metadata.tree_type != TREE_TYPE { + return Err(MerkleTreeMetadataError::InvalidTreeType.into()); + } + if account_data.len() != metadata.get_account_size()? { + return Err(ZeroCopyError::InvalidAccountSize.into()); } + let mut start_offset = BatchedMerkleTreeMetadata::LEN; + let root_history = ZeroCopyCyclicVecUsize::from_bytes_at(account_data, &mut start_offset)?; + let (batches, value_vecs, bloom_filter_stores, hashchain_store) = input_queue_bytes( + &metadata.queue_metadata, + account_data, + QueueType::Input as u64, + &mut start_offset, + )?; + + Ok(BatchedMerkleTreeAccount { + metadata, + root_history, + batches, + value_vecs, + bloom_filter_stores, + hashchain_store, + }) } #[allow(clippy::too_many_arguments)] @@ -362,61 +382,61 @@ impl BatchedMerkleTreeAccount { tree_type: TreeType, ) -> Result { set_discriminator::(&mut account_data[0..DISCRIMINATOR_LEN])?; - unsafe { - let account_metadata = - bytes_to_struct_unchecked::(account_data)?; - (*account_metadata).metadata = metadata; - (*account_metadata).root_history_capacity = root_history_capacity; - (*account_metadata).height = height; - (*account_metadata).tree_type = tree_type as u64; - (*account_metadata).queue_metadata.init( - num_batches_input_queue, - input_queue_batch_size, - input_queue_zkp_batch_size, - )?; - (*account_metadata).queue_metadata.bloom_filter_capacity = bloom_filter_capacity; - if account_data.len() != (*account_metadata).get_account_size()? { - msg!("merkle_tree_metadata: {:?}", (*account_metadata)); - msg!("account_data.len(): {}", account_data.len()); - msg!( - "account.get_account_size(): {}", - (*account_metadata).get_account_size()? - ); - return Err(ZeroCopyError::InvalidAccountSize.into()); - } - let mut start_offset = BatchedMerkleTreeMetadata::LEN; - let mut root_history = CyclicBoundedVec::init( - (*account_metadata).root_history_capacity as usize, - account_data, - &mut start_offset, - false, - )?; - if tree_type == TreeType::BatchedState { - root_history.push(light_hasher::Poseidon::zero_bytes()[height as usize]); - } else if tree_type == TreeType::BatchedAddress { - // Initialized indexed Merkle tree root - root_history.push(ADDRESS_TREE_INIT_ROOT_40); - (*account_metadata).next_index = 2; - } - let (batches, value_vecs, bloom_filter_stores, hashchain_store) = init_queue( - &(*account_metadata).queue_metadata, - QueueType::Input as u64, + let mut account_metadata = + WrappedPointerMut::::from_bytes_with_discriminator( account_data, - num_iters, - bloom_filter_capacity, - &mut start_offset, - (*account_metadata).next_index, )?; - Ok(BatchedMerkleTreeAccount { - metadata: account_metadata, - root_history, - batches, - value_vecs, - bloom_filter_stores, - hashchain_store, - }) + account_metadata.metadata = metadata; + account_metadata.root_history_capacity = root_history_capacity; + account_metadata.height = height; + account_metadata.tree_type = tree_type as u64; + account_metadata.queue_metadata.init( + num_batches_input_queue, + input_queue_batch_size, + input_queue_zkp_batch_size, + )?; + account_metadata.queue_metadata.bloom_filter_capacity = bloom_filter_capacity; + if account_data.len() != account_metadata.get_account_size()? { + msg!("merkle_tree_metadata: {:?}", account_metadata); + msg!("account_data.len(): {}", account_data.len()); + msg!( + "account.get_account_size(): {}", + account_metadata.get_account_size()? + ); + return Err(ZeroCopyError::InvalidAccountSize.into()); } + let mut start_offset = BatchedMerkleTreeMetadata::LEN; + + let mut root_history = ZeroCopyCyclicVecUsize::new_at( + account_metadata.root_history_capacity as usize, + account_data, + &mut start_offset, + )?; + if tree_type == TreeType::BatchedState { + root_history.push(light_hasher::Poseidon::zero_bytes()[height as usize]); + } else if tree_type == TreeType::BatchedAddress { + // Initialized indexed Merkle tree root + root_history.push(ADDRESS_TREE_INIT_ROOT_40); + account_metadata.next_index = 2; + } + let (batches, value_vecs, bloom_filter_stores, hashchain_store) = init_queue( + &account_metadata.queue_metadata, + QueueType::Input as u64, + account_data, + num_iters, + bloom_filter_capacity, + &mut start_offset, + account_metadata.next_index, + )?; + Ok(BatchedMerkleTreeAccount { + metadata: account_metadata, + root_history, + batches, + value_vecs, + bloom_filter_stores, + hashchain_store, + }) } pub fn update_output_queue_account_info( @@ -438,10 +458,7 @@ impl BatchedMerkleTreeAccount { instruction_data: InstructionDataBatchAppendInputs, id: [u8; 32], ) -> Result { - let batch_index = queue_account - .get_metadata() - .batch_metadata - .next_full_batch_index; + let batch_index = queue_account.batch_metadata.next_full_batch_index; let circuit_batch_size = queue_account.get_metadata().batch_metadata.zkp_batch_size; let batches = &mut queue_account.batches; let full_batch = batches.get_mut(batch_index as usize).unwrap(); @@ -613,7 +630,7 @@ impl BatchedMerkleTreeAccount { } else { return Err(MerkleTreeMetadataError::InvalidQueueType.into()); } - self.get_metadata_mut().sequence_number += 1; + self.metadata.sequence_number += 1; Ok(()) } @@ -651,64 +668,63 @@ impl BatchedMerkleTreeAccount { bloom_filter_value: &[u8; 32], leaves_hash_value: &[u8; 32], ) -> Result<(), BatchedMerkleTreeError> { - unsafe { - let (root_index, sequence_number) = insert_into_current_batch( - QueueType::Input as u64, - &mut (*self.metadata).queue_metadata, - &mut self.batches, - &mut self.value_vecs, - &mut self.bloom_filter_stores, - &mut self.hashchain_store, - bloom_filter_value, - Some(leaves_hash_value), - None, - )?; + let (root_index, sequence_number) = insert_into_current_batch( + QueueType::Input as u64, + &mut self.metadata.queue_metadata, + &mut self.batches, + &mut self.value_vecs, + &mut self.bloom_filter_stores, + &mut self.hashchain_store, + bloom_filter_value, + Some(leaves_hash_value), + None, + )?; - /* - * Note on security for root buffer: - * Account { - * bloom_filter: [B0, B1], - * roots: [R0, R1, R2, R3, R4, R5, R6, R7, R8, R9], - * } - * - * Timeslot 0: - * - insert into B0 until full - * - * Timeslot 1: - * - insert into B1 until full - * - update tree with B0 in 4 partial updates, don't clear B0 yet - * -> R0 -> B0.1 - * -> R1 -> B0.2 - * -> R2 -> B0.3 - * -> R3 -> B0.4 - final B0 root - * B0.sequence_number = 13 (3 + account.root.length) - * B0.root_index = 3 - * - execute some B1 root updates - * -> R4 -> B1.1 - * -> R5 -> B1.2 - * -> R6 -> B1.3 - * -> R7 -> B1.4 - final B1 (update batch 0) root - * B0.sequence_number = 17 (7 + account.root.length) - * B0.root_index = 7 - * current_sequence_number = 8 - * Timeslot 2: - * - clear B0 - * - current_sequence_number < 14 -> zero out all roots until root index is 3 - * - R8 -> 0 - * - R9 -> 0 - * - R0 -> 0 - * - R1 -> 0 - * - R2 -> 0 - * - now all roots containing values nullified in the final B0 root update are zeroed - * .-> B0 is safe to clear - */ - if let Some(sequence_number) = sequence_number { - // If the sequence number is greater than current sequence number - // there is still at least one root which can be used to prove - // inclusion of a value which was in the batch that was just wiped. - self.zero_out_roots(sequence_number, root_index); - } + /* + * Note on security for root buffer: + * Account { + * bloom_filter: [B0, B1], + * roots: [R0, R1, R2, R3, R4, R5, R6, R7, R8, R9], + * } + * + * Timeslot 0: + * - insert into B0 until full + * + * Timeslot 1: + * - insert into B1 until full + * - update tree with B0 in 4 partial updates, don't clear B0 yet + * -> R0 -> B0.1 + * -> R1 -> B0.2 + * -> R2 -> B0.3 + * -> R3 -> B0.4 - final B0 root + * B0.sequence_number = 13 (3 + account.root.length) + * B0.root_index = 3 + * - execute some B1 root updates + * -> R4 -> B1.1 + * -> R5 -> B1.2 + * -> R6 -> B1.3 + * -> R7 -> B1.4 - final B1 (update batch 0) root + * B0.sequence_number = 17 (7 + account.root.length) + * B0.root_index = 7 + * current_sequence_number = 8 + * Timeslot 2: + * - clear B0 + * - current_sequence_number < 14 -> zero out all roots until root index is 3 + * - R8 -> 0 + * - R9 -> 0 + * - R0 -> 0 + * - R1 -> 0 + * - R2 -> 0 + * - now all roots containing values nullified in the final B0 root update are zeroed + * .-> B0 is safe to clear + */ + if let Some(sequence_number) = sequence_number { + // If the sequence number is greater than current sequence number + // there is still at least one root which can be used to prove + // inclusion of a value which was in the batch that was just wiped. + self.zero_out_roots(sequence_number, root_index); } + Ok(()) } diff --git a/program-libs/batched-merkle-tree/src/queue.rs b/program-libs/batched-merkle-tree/src/queue.rs index 9d1bff42da..e027828f8b 100644 --- a/program-libs/batched-merkle-tree/src/queue.rs +++ b/program-libs/batched-merkle-tree/src/queue.rs @@ -1,14 +1,17 @@ -use std::mem::{size_of, ManuallyDrop}; +use std::ops::{Deref, DerefMut}; use aligned_sized::aligned_sized; use bytemuck::{Pod, Zeroable}; -use light_bounded_vec::{BoundedVec, BoundedVecMetadata}; use light_hasher::Discriminator; use light_merkle_tree_metadata::{ errors::MerkleTreeMetadataError, queue::{QueueMetadata, QueueType}, }; use light_utils::account::{check_account_info_mut, set_discriminator, DISCRIMINATOR_LEN}; +use light_zero_copy::{ + errors::ZeroCopyError, slice_mut::ZeroCopySliceMutUsize, vec::ZeroCopyVecUsize, + wrapped_pointer_mut::WrappedPointerMut, +}; use solana_program::{account_info::AccountInfo, msg, pubkey::Pubkey}; use super::batch::BatchState; @@ -18,7 +21,6 @@ use crate::{ constants::{ACCOUNT_COMPRESSION_PROGRAM_ID, OUTPUT_QUEUE_TYPE, TEST_DEFAULT_BATCH_SIZE}, errors::BatchedMerkleTreeError, initialize_state_tree::InitStateTreeAccountsInstructionData, - zero_copy::{bytes_to_struct_unchecked, ZeroCopyError}, BorshDeserialize, BorshSerialize, }; @@ -120,18 +122,19 @@ pub fn queue_account_size( } else { BatchedQueueMetadata::LEN }; - let bounded_vec_metadata_size = size_of::(); - let batches_size = - bounded_vec_metadata_size + (size_of::() * batch_metadata.num_batches as usize); - let value_vecs_size = - (bounded_vec_metadata_size + 32 * batch_metadata.batch_size as usize) * num_value_vec; + let batches_size = ZeroCopySliceMutUsize::::required_size_for_capacity( + batch_metadata.num_batches as usize, + ); + let value_vecs_size = ZeroCopyVecUsize::<[u8; 32]>::required_size_for_capacity( + batch_metadata.batch_size as usize, + ) * num_value_vec; // Bloomfilter capacity is in bits. - let bloom_filter_stores_size = (bounded_vec_metadata_size - + batch_metadata.bloom_filter_capacity as usize / 8) - * num_bloom_filter_stores; - let hashchain_store_size = (bounded_vec_metadata_size - + 32 * batch_metadata.get_num_zkp_batches() as usize) - * num_hashchain_store; + let bloom_filter_stores_size = ZeroCopySliceMutUsize::::required_size_for_capacity( + batch_metadata.bloom_filter_capacity as usize / 8, + ) * num_bloom_filter_stores; + let hashchain_store_size = ZeroCopyVecUsize::<[u8; 32]>::required_size_for_capacity( + batch_metadata.get_num_zkp_batches() as usize, + ) * num_hashchain_store; let size = account_size + batches_size + value_vecs_size @@ -141,23 +144,39 @@ pub fn queue_account_size( } /// Batched output queue #[repr(C)] -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct BatchedQueueAccount { - metadata: *mut BatchedQueueMetadata, - pub batches: ManuallyDrop>, - pub value_vecs: Vec>>, - pub bloom_filter_stores: Vec>>, + metadata: WrappedPointerMut, + pub batches: ZeroCopySliceMutUsize, + pub value_vecs: Vec>, + pub bloom_filter_stores: Vec>, /// hashchain_store_capacity = batch_capacity / zkp_batch_size - pub hashchain_store: Vec>>, + pub hashchain_store: Vec>, +} + +impl Deref for BatchedQueueAccount { + type Target = BatchedQueueMetadata; + + fn deref(&self) -> &Self::Target { + self.metadata.get() + } +} + +impl DerefMut for BatchedQueueAccount { + fn deref_mut(&mut self) -> &mut Self::Target { + self.metadata.get_mut() + } } impl BatchedQueueAccount { + // TODO: remove pub fn get_metadata(&self) -> &BatchedQueueMetadata { - unsafe { &*self.metadata } + self.metadata.get() } + // TODO: remove pub fn get_metadata_mut(&mut self) -> &mut BatchedQueueMetadata { - unsafe { &mut *self.metadata } + self.metadata.get_mut() } pub fn output_queue_from_account_info_mut( @@ -195,29 +214,28 @@ impl BatchedQueueAccount { fn internal_from_bytes_mut( account_data: &mut [u8], ) -> Result { - let metadata = bytes_to_struct_unchecked::(account_data)?; - unsafe { - if (*metadata).metadata.queue_type != QUEUE_TYPE { - return Err(MerkleTreeMetadataError::InvalidQueueType.into()); - } - let (num_value_stores, num_stores, num_hashchain_stores) = - (*metadata).get_size_parameters()?; - - let (batches, value_vecs, bloom_filter_stores, hashchain_store) = - output_queue_from_bytes( - num_value_stores, - num_stores, - num_hashchain_stores, - account_data, - )?; - Ok(BatchedQueueAccount { - metadata, - batches, - value_vecs, - bloom_filter_stores, - hashchain_store, - }) + let metadata = + WrappedPointerMut::::from_bytes_with_discriminator(account_data)?; + + if metadata.metadata.queue_type != QUEUE_TYPE { + return Err(MerkleTreeMetadataError::InvalidQueueType.into()); } + let (num_value_stores, num_stores, num_hashchain_stores) = + metadata.get_size_parameters()?; + + let (batches, value_vecs, bloom_filter_stores, hashchain_store) = output_queue_from_bytes( + num_value_stores, + num_stores, + num_hashchain_stores, + account_data, + )?; + Ok(BatchedQueueAccount { + metadata, + batches, + value_vecs, + bloom_filter_stores, + hashchain_store, + }) } pub fn init( @@ -230,54 +248,53 @@ impl BatchedQueueAccount { bloom_filter_capacity: u64, ) -> Result { set_discriminator::(&mut account_data[0..DISCRIMINATOR_LEN])?; - let account_metadata = bytes_to_struct_unchecked::(account_data)?; - unsafe { - (*account_metadata).init( - metadata, - num_batches_output_queue, - output_queue_batch_size, - output_queue_zkp_batch_size, - bloom_filter_capacity, - )?; - - let (batches, value_vecs, bloom_filter_stores, hashchain_store) = init_queue( - &(*account_metadata).batch_metadata, - (*account_metadata).metadata.queue_type, - account_data, - num_iters, - bloom_filter_capacity, - &mut 0, - 0, - )?; - Ok(BatchedQueueAccount { - metadata: account_metadata, - batches, - value_vecs, - bloom_filter_stores, - hashchain_store, - }) - } + let mut account_metadata = + WrappedPointerMut::::from_bytes_with_discriminator(account_data)?; + account_metadata.get_mut().init( + metadata, + num_batches_output_queue, + output_queue_batch_size, + output_queue_zkp_batch_size, + bloom_filter_capacity, + )?; + + let (batches, value_vecs, bloom_filter_stores, hashchain_store) = init_queue( + &account_metadata.batch_metadata, + account_metadata.metadata.queue_type, + account_data, + num_iters, + bloom_filter_capacity, + &mut 0, + 0, + )?; + Ok(BatchedQueueAccount { + metadata: account_metadata, + batches, + value_vecs, + bloom_filter_stores, + hashchain_store, + }) } pub fn insert_into_current_batch( &mut self, value: &[u8; 32], ) -> Result<(), BatchedMerkleTreeError> { - let current_index = self.get_metadata().next_index; - unsafe { - insert_into_current_batch( - (*self.metadata).metadata.queue_type, - &mut (*self.metadata).batch_metadata, - &mut self.batches, - &mut self.value_vecs, - &mut self.bloom_filter_stores, - &mut self.hashchain_store, - value, - None, - Some(current_index), - )?; - (*self.metadata).next_index += 1; - } + let current_index = self.next_index; + + insert_into_current_batch( + self.metadata.metadata.queue_type, + &mut self.metadata.batch_metadata, + &mut self.batches, + &mut self.value_vecs, + &mut self.bloom_filter_stores, + &mut self.hashchain_store, + value, + None, + Some(current_index), + )?; + self.metadata.next_index += 1; + Ok(()) } @@ -342,10 +359,7 @@ impl BatchedQueueAccount { } pub fn get_batch_num_inserted_in_current_batch(&self) -> u64 { - let next_full_batch = self - .get_metadata() - .batch_metadata - .currently_processing_batch_index; + let next_full_batch = self.batch_metadata.currently_processing_batch_index; let batch = self.batches.get(next_full_batch as usize).unwrap(); batch.get_num_inserted() + batch.get_current_zkp_batch_index() * batch.zkp_batch_size } @@ -356,10 +370,10 @@ impl BatchedQueueAccount { pub fn insert_into_current_batch( queue_type: u64, account: &mut BatchMetadata, - batches: &mut ManuallyDrop>, - value_vecs: &mut [ManuallyDrop>], - bloom_filter_stores: &mut [ManuallyDrop>], - hashchain_store: &mut [ManuallyDrop>], + batches: &mut ZeroCopySliceMutUsize, + value_vecs: &mut [ZeroCopyVecUsize<[u8; 32]>], + bloom_filter_stores: &mut [ZeroCopySliceMutUsize], + hashchain_store: &mut [ZeroCopyVecUsize<[u8; 32]>], value: &[u8; 32], leaves_hash_value: Option<&[u8; 32]>, current_index: Option, @@ -463,21 +477,27 @@ pub fn output_queue_from_bytes( account_data: &mut [u8], ) -> Result< ( - ManuallyDrop>, - Vec>>, - Vec>>, - Vec>>, + ZeroCopySliceMutUsize, + Vec>, + Vec>, + Vec>, ), BatchedMerkleTreeError, > { let mut start_offset = BatchedQueueMetadata::LEN; - let batches = BoundedVec::deserialize(account_data, &mut start_offset)?; - let value_vecs = - BoundedVec::deserialize_multiple(num_value_stores, account_data, &mut start_offset)?; + let batches = ZeroCopySliceMutUsize::from_bytes_at(account_data, &mut start_offset)?; + let value_vecs = ZeroCopyVecUsize::from_bytes_at_multiple( + num_value_stores, + account_data, + &mut start_offset, + )?; let bloom_filter_stores = - BoundedVec::deserialize_multiple(num_stores, account_data, &mut start_offset)?; - let hashchain_store = - BoundedVec::deserialize_multiple(num_hashchain_stores, account_data, &mut start_offset)?; + ZeroCopySliceMutUsize::from_bytes_at_multiple(num_stores, account_data, &mut start_offset)?; + let hashchain_store = ZeroCopyVecUsize::from_bytes_at_multiple( + num_hashchain_stores, + account_data, + &mut start_offset, + )?; Ok((batches, value_vecs, bloom_filter_stores, hashchain_store)) } @@ -489,10 +509,10 @@ pub fn input_queue_bytes( start_offset: &mut usize, ) -> Result< ( - ManuallyDrop>, - Vec>>, - Vec>>, - Vec>>, + ZeroCopySliceMutUsize, + Vec>, + Vec>, + Vec>, ), BatchedMerkleTreeError, > { @@ -501,13 +521,16 @@ pub fn input_queue_bytes( if queue_type == QueueType::Output as u64 { *start_offset += BatchedQueueMetadata::LEN; } - let batches = BoundedVec::deserialize(account_data, start_offset)?; + let batches = ZeroCopySliceMutUsize::from_bytes_at(account_data, start_offset)?; let value_vecs = - BoundedVec::deserialize_multiple(num_value_stores, account_data, start_offset)?; + ZeroCopyVecUsize::from_bytes_at_multiple(num_value_stores, account_data, start_offset)?; let bloom_filter_stores = - BoundedVec::deserialize_multiple(num_stores, account_data, start_offset)?; - let hashchain_store = - BoundedVec::deserialize_multiple(hashchain_store_capacity, account_data, start_offset)?; + ZeroCopySliceMutUsize::from_bytes_at_multiple(num_stores, account_data, start_offset)?; + let hashchain_store = ZeroCopyVecUsize::from_bytes_at_multiple( + hashchain_store_capacity, + account_data, + start_offset, + )?; Ok((batches, value_vecs, bloom_filter_stores, hashchain_store)) } @@ -523,10 +546,10 @@ pub fn init_queue( batch_start_index: u64, ) -> Result< ( - ManuallyDrop>, - Vec>>, - Vec>>, - Vec>>, + ZeroCopySliceMutUsize, + Vec>, + Vec>, + Vec>, ), BatchedMerkleTreeError, > { @@ -547,45 +570,37 @@ pub fn init_queue( *start_offset += BatchedQueueMetadata::LEN; } - let mut batches = BoundedVec::init( - account.num_batches as usize, - account_data, - start_offset, - false, - )?; + let mut batches = + ZeroCopySliceMutUsize::new_at(account.num_batches as usize, account_data, start_offset)?; for i in 0..account.num_batches { - batches.push(Batch::new( + batches[i as usize] = Batch::new( num_iters, bloom_filter_capacity, account.batch_size, account.zkp_batch_size, account.batch_size * i + batch_start_index, - ))?; + ); } - - let value_vecs = BoundedVec::init_multiple( + let value_vecs = ZeroCopyVecUsize::new_at_multiple( num_value_stores, account.batch_size as usize, account_data, start_offset, - false, )?; - let bloom_filter_stores = BoundedVec::init_multiple( + let bloom_filter_stores = ZeroCopySliceMutUsize::new_at_multiple( num_stores, account.bloom_filter_capacity as usize / 8, account_data, start_offset, - true, )?; - let hashchain_store = BoundedVec::init_multiple( + let hashchain_store = ZeroCopyVecUsize::new_at_multiple( num_hashchain_stores, account.get_num_zkp_batches() as usize, account_data, start_offset, - false, )?; Ok((batches, value_vecs, bloom_filter_stores, hashchain_store)) @@ -644,9 +659,9 @@ pub fn assert_queue_inited( batch_metadata: BatchMetadata, ref_batch_metadata: BatchMetadata, queue_type: u64, - value_vecs: &mut Vec>>, - bloom_filter_stores: &mut Vec>>, - batches: &mut ManuallyDrop>, + value_vecs: &mut Vec>, + bloom_filter_stores: &mut Vec>, + batches: &mut ZeroCopySliceMutUsize, num_batches: usize, num_iters: u64, start_index: u64, @@ -697,7 +712,7 @@ pub fn assert_queue_inited( for vec in bloom_filter_stores { assert_eq!( - vec.metadata().capacity() * 8, + vec.len() * 8, batch_metadata.bloom_filter_capacity as usize, "bloom_filter_capacity mismatch" ); @@ -705,7 +720,7 @@ pub fn assert_queue_inited( for vec in value_vecs.iter() { assert_eq!( - vec.metadata().capacity(), + vec.capacity(), batch_metadata.batch_size as usize, "batch_size mismatch" ); @@ -722,12 +737,11 @@ pub fn assert_queue_zero_copy_inited( let mut account = BatchedQueueAccount::output_queue_from_bytes_mut(account_data) .expect("from_bytes_unchecked_mut failed"); let num_batches = ref_account.batch_metadata.num_batches as usize; - let batch_metadata = account.get_metadata().batch_metadata; - let queue_type = account.get_metadata().metadata.queue_type; - let next_index = account.get_metadata().next_index; + let batch_metadata = account.batch_metadata; + let queue_type = account.metadata.metadata.queue_type; + let next_index = account.next_index; assert_eq!( - account.get_metadata().metadata, - ref_account.metadata, + account.metadata.metadata, ref_account.metadata, "metadata mismatch" ); assert_queue_inited( diff --git a/program-libs/batched-merkle-tree/src/rollover_state_tree.rs b/program-libs/batched-merkle-tree/src/rollover_state_tree.rs index 4f837efe22..2c41ce241a 100644 --- a/program-libs/batched-merkle-tree/src/rollover_state_tree.rs +++ b/program-libs/batched-merkle-tree/src/rollover_state_tree.rs @@ -131,6 +131,9 @@ pub fn batched_tree_is_ready_for_rollover( metadata: &BatchedMerkleTreeMetadata, network_fee: &Option, ) -> Result<(), BatchedMerkleTreeError> { + if metadata.metadata.rollover_metadata.rollover_threshold == u64::MAX { + return Err(MerkleTreeMetadataError::RolloverNotConfigured.into()); + } if metadata.next_index < ((1 << metadata.height) * metadata.metadata.rollover_metadata.rollover_threshold / 100) { diff --git a/program-libs/batched-merkle-tree/src/zero_copy.rs b/program-libs/batched-merkle-tree/src/zero_copy.rs deleted file mode 100644 index 1d6ca4e654..0000000000 --- a/program-libs/batched-merkle-tree/src/zero_copy.rs +++ /dev/null @@ -1,39 +0,0 @@ -use bytemuck::Pod; -use light_hasher::Discriminator; -use light_utils::account::DISCRIMINATOR_LEN; -use thiserror::Error; - -// TODO: move file to bounded vec crate and rename to light-zero-copy -#[derive(Debug, Error, PartialEq)] -pub enum ZeroCopyError { - #[error("Invalid Account size.")] - InvalidAccountSize, -} - -#[cfg(feature = "solana")] -impl From for u32 { - fn from(e: ZeroCopyError) -> u32 { - match e { - ZeroCopyError::InvalidAccountSize => 14401, - } - } -} - -#[cfg(feature = "solana")] -impl From for solana_program::program_error::ProgramError { - fn from(e: ZeroCopyError) -> Self { - solana_program::program_error::ProgramError::Custom(e.into()) - } -} - -pub fn bytes_to_struct_unchecked( - bytes: &mut [u8], -) -> Result<*mut T, ZeroCopyError> { - // Base address for alignment check of T. - let base_address = bytes.as_ptr() as usize + DISCRIMINATOR_LEN; - if bytes.len() < std::mem::size_of::() || base_address % std::mem::align_of::() != 0 { - return Err(ZeroCopyError::InvalidAccountSize); - } - - Ok(bytes[DISCRIMINATOR_LEN..].as_mut_ptr() as *mut T) -} diff --git a/program-libs/batched-merkle-tree/tests/initialize_address_tree.rs b/program-libs/batched-merkle-tree/tests/initialize_address_tree.rs index 640e1d33dd..83cd0f3581 100644 --- a/program-libs/batched-merkle-tree/tests/initialize_address_tree.rs +++ b/program-libs/batched-merkle-tree/tests/initialize_address_tree.rs @@ -9,7 +9,10 @@ use light_batched_merkle_tree::{ BatchedMerkleTreeMetadata, CreateTreeParams, }, }; -use light_bounded_vec::{BoundedVecMetadata, CyclicBoundedVecMetadata}; +use light_zero_copy::{ + SIZE_OF_ZERO_COPY_CYCLIC_VEC_METADATA, SIZE_OF_ZERO_COPY_SLICE_METADATA, + SIZE_OF_ZERO_COPY_VEC_METADATA, +}; use rand::{rngs::StdRng, Rng}; use solana_program::pubkey::Pubkey; @@ -89,14 +92,14 @@ fn test_rnd_account_init() { { let num_zkp_batches = params.input_queue_batch_size / params.input_queue_zkp_batch_size; let num_batches = params.input_queue_num_batches as usize; - let batch_size = size_of::() * num_batches + size_of::(); + let batch_size = size_of::() * num_batches + SIZE_OF_ZERO_COPY_SLICE_METADATA; let bloom_filter_size = (params.bloom_filter_capacity as usize / 8 - + size_of::()) + + SIZE_OF_ZERO_COPY_SLICE_METADATA) * num_batches; let hash_chain_store_size = - (num_zkp_batches as usize * 32 + size_of::()) * num_batches; + (num_zkp_batches as usize * 32 + SIZE_OF_ZERO_COPY_VEC_METADATA) * num_batches; let root_history_size = - params.root_history_capacity as usize * 32 + size_of::(); + params.root_history_capacity as usize * 32 + SIZE_OF_ZERO_COPY_CYCLIC_VEC_METADATA; // Output queue let ref_account_size = // metadata diff --git a/program-libs/batched-merkle-tree/tests/initialize_state_tree.rs b/program-libs/batched-merkle-tree/tests/initialize_state_tree.rs index 416c33f4a2..406700d56e 100644 --- a/program-libs/batched-merkle-tree/tests/initialize_state_tree.rs +++ b/program-libs/batched-merkle-tree/tests/initialize_state_tree.rs @@ -14,9 +14,76 @@ use light_batched_merkle_tree::{ get_output_queue_account_size_default, BatchedQueueMetadata, }, }; -use light_bounded_vec::{BoundedVecMetadata, CyclicBoundedVecMetadata}; +use light_zero_copy::{ + cyclic_vec::ZeroCopyCyclicVecUsize, slice_mut::ZeroCopySliceMutUsize, vec::ZeroCopyVecUsize, +}; use rand::{rngs::StdRng, Rng}; use solana_program::pubkey::Pubkey; +#[test] +fn test_different_parameters() { + let params = InitStateTreeAccountsInstructionData::test_default(); + let e2e_test_params = InitStateTreeAccountsInstructionData::e2e_test_default(); + let default_params = InitStateTreeAccountsInstructionData::default(); + for params in vec![params, e2e_test_params, default_params] { + println!("params: {:?}", params); + let owner = Pubkey::new_unique(); + let queue_account_size = get_output_queue_account_size( + params.output_queue_batch_size, + params.output_queue_zkp_batch_size, + params.output_queue_num_batches, + ); + + let mut output_queue_account_data = vec![0; queue_account_size]; + let output_queue_pubkey = Pubkey::new_unique(); + + let mt_account_size = get_merkle_tree_account_size( + params.input_queue_batch_size, + params.bloom_filter_capacity, + params.input_queue_zkp_batch_size, + params.root_history_capacity, + params.height, + params.input_queue_num_batches, + ); + let mut mt_account_data = vec![0; mt_account_size]; + let mt_pubkey = Pubkey::new_unique(); + + let merkle_tree_rent = 1_000_000_000; + let queue_rent = 1_000_000_000; + let additional_bytes_rent = 1000; + init_batched_state_merkle_tree_accounts( + owner, + params.clone(), + &mut output_queue_account_data, + output_queue_pubkey, + queue_rent, + &mut mt_account_data, + mt_pubkey, + merkle_tree_rent, + additional_bytes_rent, + ) + .unwrap(); + let queue_account_params = CreateOutputQueueParams::from( + params, + owner, + merkle_tree_rent + queue_rent + additional_bytes_rent, + mt_pubkey, + ); + let ref_output_queue_account = create_output_queue_account(queue_account_params); + assert_queue_zero_copy_inited( + output_queue_account_data.as_mut_slice(), + ref_output_queue_account, + 0, + ); + let mt_params = CreateTreeParams::from_state_ix_params(params, owner); + let ref_mt_account = + BatchedMerkleTreeMetadata::new_state_tree(mt_params, output_queue_pubkey); + assert_state_mt_zero_copy_inited( + &mut mt_account_data, + ref_mt_account, + params.bloom_filter_num_iters, + ); + } +} #[test] fn test_account_init() { @@ -116,17 +183,19 @@ fn test_rnd_account_init() { params.output_queue_num_batches, ); - use std::mem::size_of; { let num_batches = params.output_queue_num_batches as usize; let num_zkp_batches = params.output_queue_batch_size / params.output_queue_zkp_batch_size; - let batch_size = size_of::() * num_batches + size_of::(); - let value_vec_size = (params.output_queue_batch_size as usize * 32 - + size_of::()) - * num_batches; + let batch_size = ZeroCopySliceMutUsize::::required_size_for_capacity( + params.output_queue_num_batches as usize, + ); + let value_vec_size = ZeroCopyVecUsize::<[u8; 32]>::required_size_for_capacity( + params.output_queue_batch_size as usize, + ) * num_batches; let hash_chain_store_size = - (num_zkp_batches as usize * 32 + size_of::()) * num_batches; + ZeroCopyVecUsize::<[u8; 32]>::required_size_for_capacity(num_zkp_batches as usize) + * num_batches; // Output queue let ref_queue_account_size = // metadata @@ -154,14 +223,17 @@ fn test_rnd_account_init() { { let num_zkp_batches = params.input_queue_batch_size / params.input_queue_zkp_batch_size; let num_batches = params.input_queue_num_batches as usize; - let batch_size = size_of::() * num_batches + size_of::(); - let bloom_filter_size = (params.bloom_filter_capacity as usize / 8 - + size_of::()) - * num_batches; + let batch_size = + ZeroCopySliceMutUsize::::required_size_for_capacity(num_batches); + let bloom_filter_size = ZeroCopySliceMutUsize::::required_size_for_capacity( + params.bloom_filter_capacity as usize / 8, + ) * num_batches; let hash_chain_store_size = - (num_zkp_batches as usize * 32 + size_of::()) * num_batches; - let root_history_size = - params.root_history_capacity as usize * 32 + size_of::(); + ZeroCopyVecUsize::<[u8; 32]>::required_size_for_capacity(num_zkp_batches as usize) + * num_batches; + let root_history_size = ZeroCopyCyclicVecUsize::<[u8; 32]>::required_size_for_capacity( + params.root_history_capacity as usize, + ); // Output queue let ref_account_size = // metadata diff --git a/program-libs/batched-merkle-tree/tests/merkle_tree.rs b/program-libs/batched-merkle-tree/tests/merkle_tree.rs index d146d497f9..798d665135 100644 --- a/program-libs/batched-merkle-tree/tests/merkle_tree.rs +++ b/program-libs/batched-merkle-tree/tests/merkle_tree.rs @@ -1,5 +1,5 @@ #![allow(unused_assignments)] -use std::{cmp::min, mem::ManuallyDrop, ops::Deref}; +use std::cmp::min; use light_batched_merkle_tree::{ batch::{Batch, BatchState}, @@ -28,7 +28,6 @@ use light_batched_merkle_tree::{ }, }; use light_bloom_filter::BloomFilter; -use light_bounded_vec::BoundedVec; use light_hasher::{Hasher, Poseidon}; use light_merkle_tree_reference::MerkleTree; use light_prover_client::{ @@ -37,16 +36,17 @@ use light_prover_client::{ }; use light_utils::hashchain::create_hash_chain_from_slice; use light_verifier::CompressedProof; +use light_zero_copy::{slice_mut::ZeroCopySliceMutUsize, vec::ZeroCopyVecUsize}; use rand::{rngs::StdRng, Rng}; use serial_test::serial; use solana_program::pubkey::Pubkey; pub fn assert_nullifier_queue_insert( pre_account: BatchedMerkleTreeMetadata, - pre_batches: ManuallyDrop>, - pre_value_vecs: &mut Vec>>, + pre_batches: ZeroCopySliceMutUsize, + pre_value_vecs: Vec>, pre_roots: Vec<[u8; 32]>, - pre_hashchains: Vec>>, + pre_hashchains: Vec>, merkle_tree_account: BatchedMerkleTreeAccount, bloom_filter_insert_values: Vec<[u8; 32]>, leaf_indices: Vec, @@ -80,10 +80,10 @@ pub fn assert_nullifier_queue_insert( /// 3. pub fn assert_input_queue_insert( mut pre_account: BatchedMerkleTreeMetadata, - mut pre_batches: ManuallyDrop>, - pre_value_vecs: &mut Vec>>, + mut pre_batches: ZeroCopySliceMutUsize, + mut pre_value_vecs: Vec>, pre_roots: Vec<[u8; 32]>, - mut pre_hashchains: Vec>>, + mut pre_hashchains: Vec>, mut merkle_tree_account: BatchedMerkleTreeAccount, bloom_filter_insert_values: Vec<[u8; 32]>, leaf_hashchain_insert_values: Vec<[u8; 32]>, @@ -120,6 +120,8 @@ pub fn assert_input_queue_insert( .iter() .zip(pre_roots.iter()) .all(|(post, pre)| *post == *pre || *post == [0u8; 32]); + println!("pre_roots: {:?}", pre_roots); + println!("post_roots: {:?}", post_roots); if !only_zero_overwrites { panic!("Root buffer changed.") } @@ -248,9 +250,9 @@ pub fn assert_input_queue_insert( /// - if batch is full after insertion advance state to ReadyToUpdateTree pub fn assert_output_queue_insert( mut pre_account: BatchedQueueMetadata, - mut pre_batches: ManuallyDrop>, - mut pre_value_store: Vec>>, - mut pre_hashchains: Vec>>, + mut pre_batches: Vec, + mut pre_value_store: Vec>, + mut pre_hashchains: Vec>, mut output_account: BatchedQueueAccount, insert_values: Vec<[u8; 32]>, ) -> Result<(), BatchedMerkleTreeError> { @@ -263,7 +265,7 @@ pub fn assert_output_queue_insert( for insert_value in insert_values.iter() { // There are no bloom_filters for store in output_account.bloom_filter_stores.iter() { - assert_eq!(store.capacity(), 0); + assert_eq!(store.len(), 0); } // if the currently processing batch changed it should // increment by one and the old batch should be ready to @@ -321,7 +323,7 @@ pub fn assert_output_queue_insert( ); assert_eq!(pre_hashchains, output_account.hashchain_store); assert_eq!(pre_value_store, output_account.value_vecs); - assert_eq!(pre_batches, output_account.batches); + assert_eq!(pre_batches, output_account.batches.to_vec()); Ok(()) } @@ -540,15 +542,30 @@ async fn test_simulate_transactions() { BatchedQueueAccount::output_queue_from_bytes_mut(&mut output_queue_account_data) .unwrap(); let mut pre_mt_data = mt_account_data.clone(); - let pre_output_account = output_account.get_metadata().clone(); - let pre_output_batches = output_account.batches.clone(); - let mut pre_output_value_stores = output_account.value_vecs.clone(); - let pre_hashchains = output_account.hashchain_store.clone(); - - let pre_mt_account = merkle_tree_account.get_metadata().clone(); - let pre_batches = merkle_tree_account.batches.clone(); - let pre_roots = merkle_tree_account.root_history.iter().cloned().collect(); - let pre_mt_hashchains = merkle_tree_account.hashchain_store.clone(); + let mut pre_account_bytes = output_queue_account_data.clone(); + let pre_output_account = + BatchedQueueAccount::output_queue_from_bytes_mut(&mut pre_account_bytes).unwrap(); + let pre_output_metadata = pre_output_account.get_metadata().clone(); + let pre_output_batches = pre_output_account.batches; + let pre_output_value_stores = pre_output_account.value_vecs; + let pre_output_hashchains = pre_output_account.hashchain_store; + let pre_output_value_stores_2 = + BatchedQueueAccount::output_queue_from_bytes_mut(&mut pre_account_bytes) + .unwrap() + .value_vecs; + let mut pre_mt_account_bytes = mt_account_data.clone(); + let pre_merkle_tree_account = + BatchedMerkleTreeAccount::state_tree_from_bytes_mut(&mut pre_mt_account_bytes) + .unwrap(); + let pre_mt_account = pre_merkle_tree_account.get_metadata().clone(); + let pre_batches = pre_merkle_tree_account.batches; + // let pre_value_store = pre_merkle_tree_account.value_vecs; + let pre_roots = pre_merkle_tree_account + .root_history + .iter() + .cloned() + .collect(); + let pre_mt_hashchains = pre_merkle_tree_account.hashchain_store; if !outputs.is_empty() || !inputs.is_empty() { println!("Simulating tx with inputs: {:?}", instruction_data); @@ -569,7 +586,7 @@ async fn test_simulate_transactions() { assert_nullifier_queue_insert( pre_mt_account, pre_batches, - &mut pre_output_value_stores, + pre_output_value_stores, pre_roots, pre_mt_hashchains, merkle_tree_account, @@ -584,11 +601,14 @@ async fn test_simulate_transactions() { if !outputs.is_empty() { assert_output_queue_insert( - pre_output_account, - pre_output_batches, - pre_output_value_stores, - pre_hashchains, - output_account.clone(), + pre_output_metadata, + pre_output_batches.to_vec(), + pre_output_value_stores_2, + pre_output_hashchains, + BatchedQueueAccount::output_queue_from_bytes_mut( + &mut output_queue_account_data.clone(), // clone so that data cannot be modified + ) + .unwrap(), outputs.clone(), ) .unwrap(); @@ -897,21 +917,27 @@ async fn test_e2e() { println!("Output insert -----------------------------"); println!("num_output_values: {}", num_output_values); let rnd_bytes = get_rnd_bytes(&mut rng); - - let pre_account = output_account.get_metadata().clone(); - let pre_batches = output_account.batches.clone(); - let pre_value_store = output_account.value_vecs.clone(); - let pre_hashchains = output_account.hashchain_store.clone(); + let mut pre_account_bytes = output_queue_account_data.clone(); + let pre_output_account = + BatchedQueueAccount::output_queue_from_bytes_mut(&mut pre_account_bytes) + .unwrap(); + let pre_account = pre_output_account.get_metadata().clone(); + let pre_batches = pre_output_account.batches; + let pre_value_store = pre_output_account.value_vecs; + let pre_hashchains = pre_output_account.hashchain_store; output_account .insert_into_current_batch(&rnd_bytes) .unwrap(); assert_output_queue_insert( pre_account, - pre_batches, + pre_batches.to_vec(), pre_value_store, pre_hashchains, - output_account.clone(), + BatchedQueueAccount::output_queue_from_bytes_mut( + &mut output_queue_account_data.clone(), // clone so that data cannot be modified + ) + .unwrap(), vec![rnd_bytes], ) .unwrap(); @@ -933,11 +959,14 @@ async fn test_e2e() { println!("Input insert -----------------------------"); let (_, leaf) = get_random_leaf(&mut rng, &mut mock_indexer.active_leaves); - let pre_batches: ManuallyDrop> = - merkle_tree_account.batches.clone(); - let pre_account = merkle_tree_account.get_metadata().clone(); - let pre_roots = merkle_tree_account.root_history.iter().cloned().collect(); - let pre_hashchains = merkle_tree_account.hashchain_store.clone(); + let mut pre_account_bytes = mt_account_data.clone(); + let pre_mt_account = + BatchedMerkleTreeAccount::state_tree_from_bytes_mut(&mut pre_account_bytes) + .unwrap(); + let pre_account = pre_mt_account.get_metadata().clone(); + let pre_batches = pre_mt_account.batches; + let pre_hashchains = pre_mt_account.hashchain_store; + let pre_roots = pre_mt_account.root_history.iter().cloned().collect(); let tx_hash = create_hash_chain_from_slice(vec![leaf].as_slice()).unwrap(); let leaf_index = mock_indexer.merkle_tree.get_leaf_index(&leaf).unwrap(); mock_indexer.input_queue_leaves.push((leaf, leaf_index)); @@ -962,7 +991,7 @@ async fn test_e2e() { assert_nullifier_queue_insert( pre_account, pre_batches, - &mut vec![], + vec![], pre_roots, pre_hashchains, merkle_tree_account, @@ -1025,8 +1054,6 @@ async fn test_e2e() { .value_vecs .get(next_full_batch as usize) .unwrap() - .deref() - .clone() .to_vec(); println!("leaves {:?}", leaves.len()); let leaves_hashchain = output_account @@ -1305,8 +1332,8 @@ fn assert_merkle_tree_update( expected_account.next_index += queue_account.batches.get(0).unwrap().zkp_batch_size; let next_full_batch_index = expected_queue_account.batch_metadata.next_full_batch_index; ( - queue_account.batches.clone(), - old_queue_account.as_ref().unwrap().batches.clone(), + queue_account.batches.to_vec(), + old_queue_account.as_ref().unwrap().batches.to_vec(), previous_processing, Some(expected_queue_account), next_full_batch_index, @@ -1323,8 +1350,8 @@ fn assert_merkle_tree_update( 0 }; ( - account.batches.clone(), - old_account.batches.clone(), + account.batches.to_vec(), + old_account.batches.to_vec(), previous_processing, None, 0, @@ -1470,11 +1497,15 @@ async fn test_fill_queues_completely() { .unwrap(); let rnd_bytes = get_rnd_bytes(&mut rng); - - let pre_account = output_account.get_metadata().clone(); - let pre_batches = output_account.batches.clone(); - let pre_value_store = output_account.value_vecs.clone(); - let pre_hashchains = output_account.hashchain_store.clone(); + let mut pre_output_queue_account_data = output_queue_account_data.clone(); + let pre_output_account = BatchedQueueAccount::output_queue_from_bytes_mut( + &mut pre_output_queue_account_data, + ) + .unwrap(); + let pre_account = pre_output_account.get_metadata().clone(); + let pre_batches = pre_output_account.batches.to_vec(); + let pre_value_store = pre_output_account.value_vecs; + let pre_hashchains = pre_output_account.hashchain_store; output_account .insert_into_current_batch(&rnd_bytes) @@ -1484,7 +1515,10 @@ async fn test_fill_queues_completely() { pre_batches, pre_value_store, pre_hashchains, - output_account.clone(), + BatchedQueueAccount::output_queue_from_bytes_mut( + &mut output_queue_account_data.clone(), // clone so that data cannot be modified + ) + .unwrap(), vec![rnd_bytes], ) .unwrap(); @@ -1589,10 +1623,18 @@ async fn test_fill_queues_completely() { let (_, leaf) = get_random_leaf(&mut rng, &mut mock_indexer.active_leaves); let leaf_index = mock_indexer.merkle_tree.get_leaf_index(&leaf).unwrap(); - let pre_batches: ManuallyDrop> = merkle_tree_account.batches.clone(); - let pre_account = merkle_tree_account.get_metadata().clone(); - let pre_roots = merkle_tree_account.root_history.iter().cloned().collect(); - let pre_hashchains = merkle_tree_account.hashchain_store.clone(); + let mut pre_mt_account_data = mt_account_data.clone(); + let pre_merkle_tree_account = + BatchedMerkleTreeAccount::state_tree_from_bytes_mut(&mut pre_mt_account_data) + .unwrap(); + let pre_account = pre_merkle_tree_account.get_metadata().clone(); + let pre_batches = pre_merkle_tree_account.batches; + let pre_roots = pre_merkle_tree_account + .root_history + .iter() + .cloned() + .collect(); + let pre_hashchains = pre_merkle_tree_account.hashchain_store; let tx_hash = create_hash_chain_from_slice(&[leaf]).unwrap(); // Index input queue insert event mock_indexer.input_queue_leaves.push((leaf, leaf_index)); @@ -1613,7 +1655,7 @@ async fn test_fill_queues_completely() { assert_nullifier_queue_insert( pre_account, pre_batches, - &mut vec![], + vec![], pre_roots, pre_hashchains, merkle_tree_account, @@ -1837,10 +1879,18 @@ async fn test_fill_address_tree_completely() { let mut rnd_address = get_rnd_bytes(&mut rng); rnd_address[0] = 0; - let pre_batches: ManuallyDrop> = merkle_tree_account.batches.clone(); - let pre_account = merkle_tree_account.get_metadata().clone(); - let pre_roots = merkle_tree_account.root_history.iter().cloned().collect(); - let pre_hashchains = merkle_tree_account.hashchain_store.clone(); + let mut pre_account_data = mt_account_data.clone(); + let pre_merkle_tree_account = + BatchedMerkleTreeAccount::address_tree_from_bytes_mut(&mut pre_account_data) + .unwrap(); + let pre_account = pre_merkle_tree_account.get_metadata().clone(); + let pre_batches = pre_merkle_tree_account.batches; + let pre_roots = pre_merkle_tree_account + .root_history + .iter() + .cloned() + .collect(); + let pre_hashchains = pre_merkle_tree_account.hashchain_store; merkle_tree_account .insert_address_into_current_batch(&rnd_address) @@ -1848,7 +1898,7 @@ async fn test_fill_address_tree_completely() { assert_input_queue_insert( pre_account, pre_batches, - &mut vec![], + vec![], pre_roots, pre_hashchains, merkle_tree_account, diff --git a/program-libs/batched-merkle-tree/tests/rollover_address_tree.rs b/program-libs/batched-merkle-tree/tests/rollover_address_tree.rs index 5ee705900d..bd86f1475e 100644 --- a/program-libs/batched-merkle-tree/tests/rollover_address_tree.rs +++ b/program-libs/batched-merkle-tree/tests/rollover_address_tree.rs @@ -10,8 +10,11 @@ use light_batched_merkle_tree::{ }, rollover_address_tree::{assert_address_mt_roll_over, rollover_batched_address_tree}, }; -use light_bounded_vec::{BoundedVecMetadata, CyclicBoundedVecMetadata}; use light_merkle_tree_metadata::errors::MerkleTreeMetadataError; +use light_zero_copy::{ + SIZE_OF_ZERO_COPY_CYCLIC_VEC_METADATA, SIZE_OF_ZERO_COPY_SLICE_METADATA, + SIZE_OF_ZERO_COPY_VEC_METADATA, +}; use rand::thread_rng; use solana_program::pubkey::Pubkey; @@ -191,14 +194,14 @@ fn test_rnd_rollover() { { let num_zkp_batches = params.input_queue_batch_size / params.input_queue_zkp_batch_size; let num_batches = params.input_queue_num_batches as usize; - let batch_size = size_of::() * num_batches + size_of::(); + let batch_size = size_of::() * num_batches + SIZE_OF_ZERO_COPY_SLICE_METADATA; let bloom_filter_size = (params.bloom_filter_capacity as usize / 8 - + size_of::()) + + SIZE_OF_ZERO_COPY_SLICE_METADATA) * num_batches; let hash_chain_store_size = - (num_zkp_batches as usize * 32 + size_of::()) * num_batches; + (num_zkp_batches as usize * 32 + SIZE_OF_ZERO_COPY_VEC_METADATA) * num_batches; let root_history_size = - params.root_history_capacity as usize * 32 + size_of::(); + params.root_history_capacity as usize * 32 + SIZE_OF_ZERO_COPY_CYCLIC_VEC_METADATA; // Output queue let ref_account_size = // metadata diff --git a/program-libs/batched-merkle-tree/tests/rollover_state_tree.rs b/program-libs/batched-merkle-tree/tests/rollover_state_tree.rs index 05bd88048e..fbfa37c0f8 100644 --- a/program-libs/batched-merkle-tree/tests/rollover_state_tree.rs +++ b/program-libs/batched-merkle-tree/tests/rollover_state_tree.rs @@ -17,9 +17,9 @@ use light_batched_merkle_tree::{ assert_state_mt_roll_over, rollover_batched_state_tree, RolloverBatchStateTreeParams, StateMtRollOverAssertParams, }, - zero_copy::ZeroCopyError, }; use light_merkle_tree_metadata::errors::MerkleTreeMetadataError; +use light_zero_copy::errors::ZeroCopyError; use rand::{rngs::StdRng, Rng}; use solana_program::pubkey::Pubkey; diff --git a/program-libs/batched-merkle-tree/tests/zero_copy.rs b/program-libs/batched-merkle-tree/tests/zero_copy.rs index 8054948015..a2c1d7a21c 100644 --- a/program-libs/batched-merkle-tree/tests/zero_copy.rs +++ b/program-libs/batched-merkle-tree/tests/zero_copy.rs @@ -11,10 +11,10 @@ use light_batched_merkle_tree::{ }, merkle_tree::{get_merkle_tree_account_size_default, BatchedMerkleTreeAccount}, queue::{get_output_queue_account_size_default, BatchedQueueAccount}, - zero_copy::bytes_to_struct_unchecked, }; use light_hasher::Discriminator; use light_utils::account::set_discriminator; +use light_zero_copy::wrapped_pointer_mut::WrappedPointerMut; use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; /// Tests: @@ -36,18 +36,20 @@ fn test_bytes_to_struct() { // Test 1 functional init. set_discriminator::(&mut bytes).unwrap(); - let inited_struct = bytes_to_struct_unchecked::(&mut bytes).unwrap(); - unsafe { - (*inited_struct).data = 1; - } + let inited_struct = + &mut WrappedPointerMut::::from_bytes_with_discriminator(&mut bytes).unwrap(); + + (*inited_struct).data = 1; + assert_eq!(bytes[0..8], MyStruct::DISCRIMINATOR); assert_eq!(bytes[8..].to_vec(), vec![1, 0, 0, 0, 0, 0, 0, 0]); // Test 2 functional deserialize. - let inited_struct = unsafe { *bytes_to_struct_unchecked::(&mut bytes).unwrap() }; + let inited_struct = + *WrappedPointerMut::::from_bytes_with_discriminator(&mut bytes).unwrap(); assert_eq!(inited_struct, MyStruct { data: 1 }); // Test 3 failing deserialize invalid data. let inited_struct = - unsafe { *bytes_to_struct_unchecked::(&mut empty_bytes).unwrap() }; + *WrappedPointerMut::::from_bytes_with_discriminator(&mut empty_bytes).unwrap(); assert_ne!(inited_struct, MyStruct { data: 1 }); } diff --git a/program-libs/bounded-vec/src/lib.rs b/program-libs/bounded-vec/src/lib.rs deleted file mode 100644 index 63e0b3f076..0000000000 --- a/program-libs/bounded-vec/src/lib.rs +++ /dev/null @@ -1,2086 +0,0 @@ -pub mod offset; - -use std::{ - alloc::{self, handle_alloc_error, Layout}, - fmt, - mem::{self, size_of, ManuallyDrop}, - ops::{Index, IndexMut, Sub}, - ptr::{self, NonNull}, - slice::{self, Iter, IterMut, SliceIndex}, -}; - -use memoffset::span_of; -use offset::zero_copy::{read_array_like_ptr_at_mut, read_ptr_at, write_at}; -use thiserror::Error; - -#[derive(Debug, Error, PartialEq)] -pub enum BoundedVecError { - #[error("The vector is full, cannot push any new elements")] - Full, - #[error("Requested array of size {0}, but the vector has {1} elements")] - ArraySize(usize, usize), - #[error("The requested start index is out of bounds.")] - IterFromOutOfBounds, - #[error("Memory allocated {0}, Memory required {0}")] - InsufficientMemoryAllocated(usize, usize), -} - -#[cfg(feature = "solana")] -impl From for u32 { - fn from(e: BoundedVecError) -> u32 { - match e { - BoundedVecError::Full => 8001, - BoundedVecError::ArraySize(_, _) => 8002, - BoundedVecError::IterFromOutOfBounds => 8003, - BoundedVecError::InsufficientMemoryAllocated(_, _) => 8004, - } - } -} - -#[cfg(feature = "solana")] -impl From for solana_program::program_error::ProgramError { - fn from(e: BoundedVecError) -> Self { - solana_program::program_error::ProgramError::Custom(e.into()) - } -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct BoundedVecMetadata { - capacity: usize, - length: usize, -} - -impl BoundedVecMetadata { - pub fn new(capacity: usize) -> Self { - Self { - capacity, - length: 0, - } - } - - pub fn new_with_length(capacity: usize, length: usize) -> Self { - Self { capacity, length } - } - - pub fn from_le_bytes(bytes: [u8; mem::size_of::()]) -> Self { - Self { - capacity: usize::from_le_bytes(bytes[span_of!(Self, capacity)].try_into().unwrap()), - length: usize::from_le_bytes(bytes[span_of!(Self, length)].try_into().unwrap()), - } - } - - pub fn to_le_bytes(&self) -> [u8; mem::size_of::()] { - let mut bytes = [0u8; mem::size_of::()]; - bytes[span_of!(Self, capacity)].copy_from_slice(&self.capacity.to_le_bytes()); - bytes[span_of!(Self, length)].copy_from_slice(&self.length.to_le_bytes()); - - bytes - } - - pub fn capacity(&self) -> usize { - self.capacity - } - - pub fn length(&self) -> usize { - self.length - } -} - -/// `BoundedVec` is a custom vector implementation which forbids -/// post-initialization reallocations. The size is not known during compile -/// time (that makes it different from arrays), but can be defined only once -/// (that makes it different from [`Vec`](std::vec::Vec)). -pub struct BoundedVec -where - T: Clone, -{ - metadata: *mut BoundedVecMetadata, - data: NonNull, -} - -impl BoundedVec -where - T: Clone, -{ - #[inline] - fn metadata_with_capacity(capacity: usize) -> *mut BoundedVecMetadata { - let layout = Layout::new::(); - let metadata = unsafe { alloc::alloc(layout) as *mut BoundedVecMetadata }; - if metadata.is_null() { - handle_alloc_error(layout); - } - unsafe { - *metadata = BoundedVecMetadata { - capacity, - length: 0, - }; - } - - metadata - } - - #[inline] - fn metadata_from(src_metadata: &BoundedVecMetadata) -> *mut BoundedVecMetadata { - let layout = Layout::new::(); - let metadata = unsafe { alloc::alloc(layout) as *mut BoundedVecMetadata }; - if metadata.is_null() { - handle_alloc_error(layout); - } - unsafe { (*metadata).clone_from(src_metadata) }; - - metadata - } - - #[inline] - fn data_with_capacity(capacity: usize) -> NonNull { - let layout = Layout::array::(capacity).unwrap(); - let data_ptr = unsafe { alloc::alloc(layout) as *mut T }; - if data_ptr.is_null() { - handle_alloc_error(layout); - } - // PANICS: We ensured that the pointer is not NULL. - NonNull::new(data_ptr).unwrap() - } - - #[inline] - pub fn with_capacity(capacity: usize) -> Self { - let metadata = Self::metadata_with_capacity(capacity); - let data = Self::data_with_capacity(capacity); - - Self { metadata, data } - } - - #[inline] - pub fn clear(&mut self) { - self.metadata_mut().length = 0; - } - - /// Creates a `BoundedVec` with the given `metadata`. - /// - /// # Safety - /// - /// This method is unsafe, as it does not guarantee the correctness of - /// provided parameters (other than `capacity`). The full responisibility - /// is on the caller. - #[inline] - pub unsafe fn with_metadata(metadata: &BoundedVecMetadata) -> Self { - let capacity = metadata.capacity(); - let metadata = Self::metadata_from(metadata); - let data = Self::data_with_capacity(capacity); - - Self { metadata, data } - } - - pub fn metadata(&self) -> &BoundedVecMetadata { - unsafe { &*self.metadata } - } - - fn metadata_mut(&mut self) -> &mut BoundedVecMetadata { - unsafe { &mut *self.metadata } - } - - pub fn from_array(array: &[T; N]) -> Self { - let mut vec = Self::with_capacity(N); - for element in array { - // SAFETY: We are sure that the array and the vector have equal - // sizes, there is no chance for the error to occur. - vec.push(element.clone()).unwrap(); - } - vec - } - - pub fn from_slice(slice: &[T]) -> Self { - let mut vec = Self::with_capacity(slice.len()); - for element in slice { - // SAFETY: We are sure that the array and the vector have equal - // sizes, there is no chance for the error to occur. - vec.push(element.clone()).unwrap(); - } - vec - } - - /// Creates `BoundedVec` directly from a pointer, a capacity, and a length. - /// - /// # Safety - /// - /// This is highly unsafe, due to the number of invariants that aren't - /// checked: - /// - /// * `ptr` must have been allocated using the global allocator, such as via - /// the [`alloc::alloc`] function. - /// * `T` needs to have the same alignment as what `ptr` was allocated with. - /// (`T` having a less strict alignment is not sufficient, the alignment really - /// needs to be equal to satisfy the [`dealloc`] requirement that memory must be - /// allocated and deallocated with the same layout.) - /// * The size of `T` times the `capacity` (ie. the allocated size in bytes) needs - /// to be the same size as the pointer was allocated with. (Because similar to - /// alignment, [`dealloc`] must be called with the same layout `size`.) - /// * `length` needs to be less than or equal to `capacity`. - /// * The first `length` values must be properly initialized values of type `T`. - /// * `capacity` needs to be the capacity that the pointer was allocated with. - /// * The allocated size in bytes must be no larger than `isize::MAX`. - /// See the safety documentation of [`pointer::offset`]. - #[inline] - pub unsafe fn from_raw_parts(metadata: *mut BoundedVecMetadata, ptr: *mut T) -> Self { - let data = NonNull::new(ptr).unwrap(); - Self { metadata, data } - } - - /// Returns the total number of elements the vector can hold without - /// reallocating. - /// - /// # Examples - /// - /// ``` - /// let mut vec: Vec = Vec::with_capacity(10); - /// vec.push(42); - /// assert!(vec.capacity() >= 10); - /// ``` - #[inline] - pub fn capacity(&self) -> usize { - self.metadata().capacity - } - - #[inline] - pub fn as_slice(&self) -> &[T] { - unsafe { slice::from_raw_parts(self.data.as_ptr(), self.len()) } - } - - #[inline] - pub fn as_mut_slice(&mut self) -> &mut [T] { - unsafe { slice::from_raw_parts_mut(self.data.as_ptr(), self.len()) } - } - - /// Appends an element to the back of a collection. - /// - /// # Panics - /// - /// Panics if the new capacity exceeds `isize::MAX` bytes. - /// - /// # Examples - /// - /// ``` - /// let mut vec = vec![1, 2]; - /// vec.push(3); - /// assert_eq!(vec, [1, 2, 3]); - /// ``` - #[inline] - pub fn push(&mut self, value: T) -> Result<(), BoundedVecError> { - if self.len() == self.capacity() { - return Err(BoundedVecError::Full); - } - - unsafe { ptr::write(self.data.as_ptr().add(self.len()), value) }; - self.inc_len(); - - Ok(()) - } - - #[inline] - pub fn len(&self) -> usize { - self.metadata().length - } - - #[inline] - fn inc_len(&mut self) { - self.metadata_mut().length += 1; - } - - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - #[inline] - pub fn get(&self, index: usize) -> Option<&T> { - if index >= self.len() { - return None; - } - let cell = unsafe { &*self.data.as_ptr().add(index) }; - Some(cell) - } - - #[inline] - pub fn get_mut(&mut self, index: usize) -> Option<&mut T> { - if index >= self.len() { - return None; - } - let cell = unsafe { &mut *self.data.as_ptr().add(index) }; - Some(cell) - } - - /// Returns a mutable pointer to `BoundedVec`'s buffer. - #[inline(always)] - pub fn as_mut_ptr(&mut self) -> *mut T { - self.data.as_ptr() - } - - #[inline] - pub fn iter(&self) -> Iter<'_, T> { - self.as_slice().iter() - } - - #[inline] - pub fn iter_mut(&mut self) -> IterMut<'_, T> { - self.as_mut_slice().iter_mut() - } - - #[inline] - pub fn last(&self) -> Option<&T> { - if self.is_empty() { - return None; - } - self.get(self.len() - 1) - } - - #[inline] - pub fn last_mut(&mut self) -> Option<&mut T> { - if self.is_empty() { - return None; - } - self.get_mut(self.len() - 1) - } - - pub fn to_array(&self) -> Result<[T; N], BoundedVecError> { - if self.len() != N { - return Err(BoundedVecError::ArraySize(N, self.len())); - } - Ok(std::array::from_fn(|i| self.get(i).unwrap().clone())) - } - - pub fn to_vec(self) -> Vec { - self.as_slice().to_vec() - } - - pub fn extend>(&mut self, iter: U) -> Result<(), BoundedVecError> { - for item in iter { - self.push(item)?; - } - Ok(()) - } - - pub fn deserialize( - account_data: &mut [u8], - start_offset: &mut usize, - ) -> Result>, BoundedVecError> { - unsafe { - let meta_data_size = size_of::(); - if account_data.len().saturating_sub(*start_offset) < meta_data_size { - return Err(BoundedVecError::InsufficientMemoryAllocated( - account_data.len().saturating_sub(*start_offset), - meta_data_size, - )); - } - let metadata: *mut BoundedVecMetadata = read_ptr_at(account_data, start_offset); - let full_vector_size = (*metadata).capacity() * size_of::(); - if account_data.len().saturating_sub(*start_offset) < full_vector_size { - return Err(BoundedVecError::InsufficientMemoryAllocated( - account_data.len().saturating_sub(*start_offset), - full_vector_size + meta_data_size, - )); - } - Ok(ManuallyDrop::new(BoundedVec::from_raw_parts( - metadata, - read_array_like_ptr_at_mut(account_data, start_offset, (*metadata).capacity()), - ))) - } - } - - pub fn deserialize_multiple( - num: usize, - account_data: &mut [u8], - start_offset: &mut usize, - ) -> Result>>, BoundedVecError> { - let mut value_vecs = Vec::with_capacity(num); - for _ in 0..num { - let vec = Self::deserialize(account_data, start_offset)?; - value_vecs.push(vec); - } - Ok(value_vecs) - } - - pub fn init( - capacity: usize, - account_data: &mut [u8], - start_offset: &mut usize, - with_len: bool, - ) -> Result>, BoundedVecError> { - let vector_size = capacity * size_of::(); - let full_vector_size = vector_size + size_of::(); - if full_vector_size > account_data.len().saturating_sub(*start_offset) { - return Err(BoundedVecError::InsufficientMemoryAllocated( - account_data.len().saturating_sub(*start_offset), - full_vector_size, - )); - } - let meta: BoundedVecMetadata = if with_len { - BoundedVecMetadata::new_with_length(capacity, capacity) - } else { - BoundedVecMetadata::new(capacity) - }; - write_at::(account_data, meta.to_le_bytes().as_slice(), start_offset); - let meta: *mut BoundedVecMetadata = unsafe { - read_ptr_at( - &*account_data, - &mut start_offset.sub(size_of::()), - ) - }; - - Ok(unsafe { - ManuallyDrop::new(BoundedVec::from_raw_parts( - meta, - read_array_like_ptr_at_mut(account_data, start_offset, capacity), - )) - }) - } - - pub fn init_multiple( - num: usize, - capacity: usize, - account_data: &mut [u8], - start_offset: &mut usize, - with_len: bool, - ) -> Result>>, BoundedVecError> { - let mut value_vecs = Vec::with_capacity(num); - for _ in 0..num { - let vec = Self::init(capacity, account_data, start_offset, with_len)?; - value_vecs.push(vec); - } - Ok(value_vecs) - } -} - -impl Clone for BoundedVec -where - T: Clone, -{ - fn clone(&self) -> Self { - // Create a new buffer with the same capacity as the original - - let layout = Layout::new::(); - let metadata = unsafe { alloc::alloc(layout) as *mut BoundedVecMetadata }; - if metadata.is_null() { - handle_alloc_error(layout); - } - unsafe { *metadata = self.metadata().clone() }; - - let layout = Layout::array::(self.capacity()).unwrap(); - let data_ptr = unsafe { alloc::alloc(layout) as *mut T }; - if data_ptr.is_null() { - handle_alloc_error(layout); - } - let data = NonNull::new(data_ptr).unwrap(); - - // Copy elements from the original data slice to the new slice - let new_vec = Self { metadata, data }; - - // Clone each element into the new vector - for i in 0..self.len() { - unsafe { ptr::write(data_ptr.add(i), (*self.get(i).unwrap()).clone()) }; - } - - new_vec - } -} - -impl fmt::Debug for BoundedVec -where - T: Clone + fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self.as_slice()) - } -} - -impl Drop for BoundedVec -where - T: Clone, -{ - fn drop(&mut self) { - let layout = Layout::array::(self.capacity()).unwrap(); - unsafe { alloc::dealloc(self.data.as_ptr() as *mut u8, layout) }; - - let layout = Layout::new::(); - unsafe { alloc::dealloc(self.metadata as *mut u8, layout) }; - } -} - -impl> Index for BoundedVec -where - T: Clone, - I: SliceIndex<[T]>, -{ - type Output = I::Output; - - #[inline] - fn index(&self, index: I) -> &Self::Output { - self.as_slice().index(index) - } -} - -impl IndexMut for BoundedVec -where - T: Clone, - I: SliceIndex<[T]>, -{ - fn index_mut(&mut self, index: I) -> &mut Self::Output { - self.as_mut_slice().index_mut(index) - } -} - -impl IntoIterator for BoundedVec -where - T: Clone, -{ - type Item = T; - type IntoIter = BoundedVecIntoIterator; - - fn into_iter(self) -> Self::IntoIter { - BoundedVecIntoIterator { - vec: self, - current: 0, - } - } -} - -impl PartialEq for BoundedVec -where - T: Clone + PartialEq, -{ - fn eq(&self, other: &Self) -> bool { - self.iter().eq(other.iter()) - } -} - -impl Eq for BoundedVec where T: Clone + Eq {} - -pub struct BoundedVecIntoIterator -where - T: Clone, -{ - vec: BoundedVec, - current: usize, -} - -impl Iterator for BoundedVecIntoIterator -where - T: Clone, -{ - type Item = T; - - fn next(&mut self) -> Option { - let element = self.vec.get(self.current).map(|element| element.to_owned()); - self.current += 1; - element - } -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct CyclicBoundedVecMetadata { - capacity: usize, - length: usize, - first_index: usize, - last_index: usize, -} - -impl CyclicBoundedVecMetadata { - pub fn new(capacity: usize) -> Self { - Self { - capacity, - length: 0, - first_index: 0, - last_index: 0, - } - } - - pub fn new_with_length(capacity: usize, length: usize) -> Self { - Self { - capacity, - length, - first_index: 0, - last_index: 0, - } - } - - pub fn new_with_indices( - capacity: usize, - length: usize, - first_index: usize, - last_index: usize, - ) -> Self { - Self { - capacity, - length, - first_index, - last_index, - } - } - - pub fn from_le_bytes(bytes: [u8; mem::size_of::()]) -> Self { - Self { - capacity: usize::from_le_bytes(bytes[span_of!(Self, capacity)].try_into().unwrap()), - length: usize::from_le_bytes(bytes[span_of!(Self, length)].try_into().unwrap()), - first_index: usize::from_le_bytes( - bytes[span_of!(Self, first_index)].try_into().unwrap(), - ), - last_index: usize::from_le_bytes(bytes[span_of!(Self, last_index)].try_into().unwrap()), - } - } - - pub fn to_le_bytes(&self) -> [u8; mem::size_of::()] { - let mut bytes = [0u8; mem::size_of::()]; - bytes[span_of!(Self, capacity)].copy_from_slice(&self.capacity.to_le_bytes()); - bytes[span_of!(Self, length)].copy_from_slice(&self.length.to_le_bytes()); - bytes[span_of!(Self, first_index)].copy_from_slice(&self.first_index.to_le_bytes()); - bytes[span_of!(Self, last_index)].copy_from_slice(&self.last_index.to_le_bytes()); - - bytes - } - - pub fn capacity(&self) -> usize { - self.capacity - } - - pub fn length(&self) -> usize { - self.length - } - - pub fn get_first_index(&self) -> usize { - self.first_index - } -} - -/// `CyclicBoundedVec` is a wrapper around [`Vec`](std::vec::Vec) which: -/// -/// * Forbids post-initialization reallocations. -/// * Starts overwriting elements from the beginning once it reaches its -/// capacity. -pub struct CyclicBoundedVec -where - T: Clone, -{ - metadata: *mut CyclicBoundedVecMetadata, - data: NonNull, -} - -impl CyclicBoundedVec -where - T: Clone, -{ - #[inline] - fn metadata_with_capacity(capacity: usize) -> *mut CyclicBoundedVecMetadata { - let layout = Layout::new::(); - let metadata = unsafe { alloc::alloc(layout) as *mut CyclicBoundedVecMetadata }; - if metadata.is_null() { - handle_alloc_error(layout); - } - unsafe { - *metadata = CyclicBoundedVecMetadata { - capacity, - length: 0, - first_index: 0, - last_index: 0, - }; - } - - metadata - } - - #[inline] - fn metadata_from(src_metadata: &CyclicBoundedVecMetadata) -> *mut CyclicBoundedVecMetadata { - let layout = Layout::new::(); - let metadata = unsafe { alloc::alloc(layout) as *mut CyclicBoundedVecMetadata }; - if metadata.is_null() { - handle_alloc_error(layout); - } - unsafe { (*metadata).clone_from(src_metadata) }; - - metadata - } - - #[inline] - fn data_with_capacity(capacity: usize) -> NonNull { - let layout = Layout::array::(capacity).unwrap(); - let data_ptr = unsafe { alloc::alloc(layout) as *mut T }; - if data_ptr.is_null() { - handle_alloc_error(layout); - } - // PANICS: We ensured that the pointer is not NULL. - NonNull::new(data_ptr).unwrap() - } - - #[inline] - pub fn with_capacity(capacity: usize) -> Self { - let metadata = Self::metadata_with_capacity(capacity); - let data = Self::data_with_capacity(capacity); - - Self { metadata, data } - } - - /// Creates a `CyclicBoundedVec` with the given `metadata`. - /// - /// # Safety - /// - /// This method is unsafe, as it does not guarantee the correctness of - /// provided parameters (other than `capacity`). The full responisibility - /// is on the caller. - #[inline] - pub unsafe fn with_metadata(metadata: &CyclicBoundedVecMetadata) -> Self { - let capacity = metadata.capacity(); - let metadata = Self::metadata_from(metadata); - let data = Self::data_with_capacity(capacity); - - Self { metadata, data } - } - - pub fn metadata(&self) -> &CyclicBoundedVecMetadata { - unsafe { &*self.metadata } - } - - fn metadata_mut(&mut self) -> &mut CyclicBoundedVecMetadata { - unsafe { &mut *self.metadata } - } - - /// Creates a `CyclicBoundedVec` directly from a pointer, a capacity, and a length. - /// - /// # Safety - /// - /// This is highly unsafe, due to the number of invariants that aren't - /// checked: - /// - /// * `ptr` must have been allocated using the global allocator, such as via - /// the [`alloc::alloc`] function. - /// * `T` needs to have the same alignment as what `ptr` was allocated with. - /// (`T` having a less strict alignment is not sufficient, the alignment really - /// needs to be equal to satisfy the [`dealloc`] requirement that memory must be - /// allocated and deallocated with the same layout.) - /// * The size of `T` times the `capacity` (ie. the allocated size in bytes) needs - /// to be the same size as the pointer was allocated with. (Because similar to - /// alignment, [`dealloc`] must be called with the same layout `size`.) - /// * `length` needs to be less than or equal to `capacity`. - /// * The first `length` values must be properly initialized values of type `T`. - /// * `capacity` needs to be the capacity that the pointer was allocated with. - /// * The allocated size in bytes must be no larger than `isize::MAX`. - /// See the safety documentation of [`pointer::offset`]. - #[inline] - pub unsafe fn from_raw_parts(metadata: *mut CyclicBoundedVecMetadata, ptr: *mut T) -> Self { - let data = NonNull::new(ptr).unwrap(); - Self { metadata, data } - } - - /// Returns the total number of elements the vector can hold without - /// reallocating. - /// - /// # Examples - /// - /// ``` - /// let mut vec: Vec = Vec::with_capacity(10); - /// vec.push(42); - /// assert!(vec.capacity() >= 10); - /// ``` - #[inline] - pub fn capacity(&self) -> usize { - self.metadata().capacity - } - - #[inline] - pub fn as_slice(&self) -> &[T] { - unsafe { slice::from_raw_parts(self.data.as_ptr(), self.len()) } - } - - /// Appends an element to the back of a collection. - /// - /// # Examples - /// - /// ``` - /// let mut vec = vec![1, 2]; - /// vec.push(3); - /// assert_eq!(vec, [1, 2, 3]); - /// ``` - #[inline] - pub fn push(&mut self, value: T) { - if self.is_empty() { - self.inc_len(); - } else if self.len() < self.capacity() { - self.inc_len(); - self.inc_last_index(); - } else { - self.inc_last_index(); - self.inc_first_index(); - } - // SAFETY: We made sure that `last_index` doesn't exceed the capacity. - unsafe { - std::ptr::write(self.data.as_ptr().add(self.last_index()), value); - } - } - - #[inline] - pub fn len(&self) -> usize { - self.metadata().length - } - - #[inline] - fn inc_len(&mut self) { - self.metadata_mut().length += 1; - } - - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - #[inline] - pub fn get(&self, index: usize) -> Option<&T> { - if index >= self.len() { - return None; - } - let cell = unsafe { &*self.data.as_ptr().add(index) }; - Some(cell) - } - - #[inline] - pub fn get_mut(&mut self, index: usize) -> Option<&mut T> { - if index >= self.len() { - return None; - } - let cell = unsafe { &mut *self.data.as_ptr().add(index) }; - Some(cell) - } - - /// Returns a mutable pointer to `BoundedVec`'s buffer. - #[inline(always)] - pub fn as_mut_ptr(&mut self) -> *mut T { - self.data.as_ptr() - } - - #[inline] - pub fn iter(&self) -> CyclicBoundedVecIterator<'_, T> { - CyclicBoundedVecIterator { - vec: self, - current: self.first_index(), - is_finished: false, - } - } - - #[inline] - pub fn iter_from( - &self, - start: usize, - ) -> Result, BoundedVecError> { - if start >= self.len() { - return Err(BoundedVecError::IterFromOutOfBounds); - } - Ok(CyclicBoundedVecIterator { - vec: self, - current: start, - is_finished: false, - }) - } - - #[inline] - pub fn first_index(&self) -> usize { - self.metadata().first_index - } - - #[inline] - fn inc_first_index(&mut self) { - self.metadata_mut().first_index = (self.metadata().first_index + 1) % self.capacity(); - } - - #[inline] - pub fn first(&self) -> Option<&T> { - self.get(self.first_index()) - } - - #[inline] - pub fn first_mut(&mut self) -> Option<&mut T> { - self.get_mut(self.first_index()) - } - - #[inline] - pub fn last_index(&self) -> usize { - self.metadata().last_index - } - - #[inline] - fn inc_last_index(&mut self) { - self.metadata_mut().last_index = (self.metadata().last_index + 1) % self.capacity(); - } - - #[inline] - pub fn last(&self) -> Option<&T> { - self.get(self.last_index()) - } - - #[inline] - pub fn last_mut(&mut self) -> Option<&mut T> { - self.get_mut(self.last_index()) - } - - pub fn init( - capacity: usize, - account_data: &mut [u8], - start_offset: &mut usize, - with_len: bool, - ) -> Result, BoundedVecError> { - let vector_size = capacity * size_of::(); - - let full_vector_size = vector_size + size_of::(); - if full_vector_size > account_data.len().saturating_sub(*start_offset) { - return Err(BoundedVecError::InsufficientMemoryAllocated( - account_data.len().saturating_sub(*start_offset), - full_vector_size, - )); - } - let meta: CyclicBoundedVecMetadata = if with_len { - CyclicBoundedVecMetadata::new_with_length(capacity, capacity) - } else { - CyclicBoundedVecMetadata::new(capacity) - }; - write_at::( - account_data, - meta.to_le_bytes().as_slice(), - start_offset, - ); - let meta: *mut CyclicBoundedVecMetadata = unsafe { - read_ptr_at( - &*account_data, - &mut start_offset.sub(size_of::()), - ) - }; - Ok(unsafe { - ManuallyDrop::new(CyclicBoundedVec::from_raw_parts( - meta, - read_array_like_ptr_at_mut(account_data, start_offset, capacity), - )) - }) - } - - // TODO: pull ManuallyDrop into CyclicBoundedVec - pub fn deserialize( - account_data: &mut [u8], - start_offset: &mut usize, - ) -> Result>, BoundedVecError> { - unsafe { - if account_data.len().saturating_sub(*start_offset) - < size_of::() - { - return Err(BoundedVecError::InsufficientMemoryAllocated( - account_data.len().saturating_sub(*start_offset), - size_of::(), - )); - } - - let metadata: *mut CyclicBoundedVecMetadata = read_ptr_at(account_data, start_offset); - - let full_vector_size = (*metadata).capacity() * size_of::(); - if account_data.len().saturating_sub(*start_offset) < full_vector_size { - return Err(BoundedVecError::InsufficientMemoryAllocated( - account_data.len().saturating_sub(*start_offset), - full_vector_size, - )); - } - - Ok(ManuallyDrop::new(CyclicBoundedVec::from_raw_parts( - metadata, - read_array_like_ptr_at_mut(account_data, start_offset, (*metadata).capacity()), - ))) - } - } -} - -impl fmt::Debug for CyclicBoundedVec -where - T: Clone + fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self.as_slice()) - } -} - -impl Drop for CyclicBoundedVec -where - T: Clone, -{ - fn drop(&mut self) { - let layout = Layout::array::(self.capacity()).unwrap(); - unsafe { alloc::dealloc(self.data.as_ptr() as *mut u8, layout) }; - - let layout = Layout::new::(); - unsafe { alloc::dealloc(self.metadata as *mut u8, layout) }; - } -} - -impl Index for CyclicBoundedVec -where - T: Clone, -{ - type Output = T; - - #[inline] - fn index(&self, index: usize) -> &Self::Output { - self.get(index).unwrap() - } -} - -impl IndexMut for CyclicBoundedVec -where - T: Clone, -{ - #[inline] - fn index_mut(&mut self, index: usize) -> &mut Self::Output { - self.get_mut(index).unwrap() - } -} - -impl PartialEq for CyclicBoundedVec -where - T: Clone + PartialEq, -{ - fn eq(&self, other: &Self) -> bool { - self.iter().eq(other.iter()) - } -} - -impl Eq for CyclicBoundedVec where T: Clone + Eq {} - -pub struct CyclicBoundedVecIterator<'a, T> -where - T: Clone, -{ - vec: &'a CyclicBoundedVec, - current: usize, - is_finished: bool, -} - -impl<'a, T> Iterator for CyclicBoundedVecIterator<'a, T> -where - T: Clone, -{ - type Item = &'a T; - - fn next(&mut self) -> Option { - if self.vec.capacity() == 0 || self.is_finished { - None - } else { - if self.current == self.vec.last_index() { - self.is_finished = true; - } - let new_current = (self.current + 1) % self.vec.capacity(); - let element = self.vec.get(self.current); - self.current = new_current; - element - } - } -} - -#[cfg(test)] -mod test { - use std::array; - - use rand::{ - distributions::{ - uniform::{SampleRange, SampleUniform}, - Distribution, Standard, - }, - thread_rng, Rng, - }; - - use super::*; - - /// Generates a random value in the given range, excluding the values provided - /// in `exclude`. - fn gen_range_exclude(rng: &mut N, range: R, exclude: &[T]) -> T - where - N: Rng, - R: Clone + SampleRange, - T: PartialEq + SampleUniform, - { - loop { - // This utility is supposed to be used only in unit tests. This `clone` - // is harmless and necessary (can't pass a reference to range, it has - // to be moved). - let sample = rng.gen_range(range.clone()); - if !exclude.contains(&sample) { - return sample; - } - } - } - - #[test] - fn test_gen_range_exclude() { - let mut rng = thread_rng(); - - for n_excluded in 1..100 { - let excluded: Vec = (0..n_excluded).map(|_| rng.gen_range(0..100)).collect(); - - for _ in 0..10_000 { - let sample = gen_range_exclude(&mut rng, 0..100, excluded.as_slice()); - for excluded in excluded.iter() { - assert_ne!(&sample, excluded); - } - } - } - } - - fn rand_bounded_vec() -> BoundedVec - where - T: Clone, - Standard: Distribution, - { - let mut rng = rand::thread_rng(); - - let capacity = rng.gen_range(1..1000); - let length = rng.gen_range(0..capacity); - - let mut bounded_vec = BoundedVec::::with_capacity(capacity); - for _ in 0..length { - let element = rng.gen(); - bounded_vec.push(element).unwrap(); - } - - bounded_vec - } - - #[test] - fn test_bounded_vec_metadata_serialization() { - let mut rng = thread_rng(); - - for _ in 0..1000 { - let capacity = rng.gen(); - let metadata = BoundedVecMetadata::new(capacity); - - assert_eq!(metadata.capacity(), capacity); - assert_eq!(metadata.length(), 0); - - let bytes = metadata.to_le_bytes(); - let metadata_2 = BoundedVecMetadata::from_le_bytes(bytes); - - assert_eq!(metadata, metadata_2); - } - } - - #[test] - fn test_bounded_vec_with_capacity() { - for capacity in 0..1024 { - let bounded_vec = BoundedVec::::with_capacity(capacity); - - assert_eq!(bounded_vec.capacity(), capacity); - assert_eq!(bounded_vec.len(), 0); - } - } - - fn bounded_vec_from_array() { - let mut rng = thread_rng(); - - let arr: [u64; N] = array::from_fn(|_| rng.gen()); - let vec = BoundedVec::from_array(&arr); - - assert_eq!(&arr, vec.as_slice()); - } - - #[test] - fn test_bounded_vec_from_array_256() { - bounded_vec_from_array::<256>() - } - - #[test] - fn test_bounded_vec_from_array_512() { - bounded_vec_from_array::<512>() - } - - #[test] - fn test_bounded_vec_from_array_1024() { - bounded_vec_from_array::<1024>() - } - - #[test] - fn test_bounded_vec_from_slice() { - let mut rng = thread_rng(); - - for capacity in 0..10_000 { - let vec: Vec = (0..capacity).map(|_| rng.gen()).collect(); - let bounded_vec = BoundedVec::from_slice(&vec); - - assert_eq!(vec.as_slice(), bounded_vec.as_slice()); - } - } - - #[test] - fn test_bounded_vec_is_empty() { - let mut rng = thread_rng(); - let mut vec = BoundedVec::with_capacity(1000); - - assert!(vec.is_empty()); - - for _ in 0..1000 { - let element: u64 = rng.gen(); - vec.push(element).unwrap(); - - assert!(!vec.is_empty()); - } - } - - #[test] - fn test_bounded_vec_get() { - let mut vec = BoundedVec::with_capacity(1000); - - for i in 0..1000 { - assert!(vec.get(i).is_none()); - vec.push(i).unwrap(); - } - - for i in 0..1000 { - assert_eq!(vec.get(i), Some(&i)); - } - for i in 1000..10_000 { - assert!(vec.get(i).is_none()); - } - } - - #[test] - fn test_bounded_vec_get_mut() { - let mut vec = BoundedVec::with_capacity(1000); - - for i in 0..1000 { - assert!(vec.get_mut(i).is_none()); - vec.push(i).unwrap(); - } - - for i in 0..1000 { - let element = vec.get_mut(i).unwrap(); - assert_eq!(element, &i); - *element = i * 2; - } - for i in 0..1000 { - assert_eq!(vec.get_mut(i), Some(&mut (i * 2))); - } - for i in 1000..10_000 { - assert!(vec.get_mut(i).is_none()); - } - } - - #[test] - fn test_bounded_vec_iter_mut() { - let mut vec = BoundedVec::with_capacity(1000); - - for i in 0..1000 { - vec.push(i).unwrap(); - } - - for (i, element) in vec.iter().enumerate() { - assert_eq!(*element, i); - } - - for element in vec.iter_mut() { - *element = *element * 2; - } - - for (i, element) in vec.iter().enumerate() { - assert_eq!(*element, i * 2); - } - } - - #[test] - fn test_bounded_vec_last() { - let mut rng = thread_rng(); - let mut vec = BoundedVec::with_capacity(1000); - - assert!(vec.last().is_none()); - - for _ in 0..1000 { - let element: u64 = rng.gen(); - vec.push(element).unwrap(); - - assert_eq!(vec.last(), Some(&element)); - } - } - - #[test] - fn test_bounded_vec_last_mut() { - let mut rng = thread_rng(); - let mut vec = BoundedVec::with_capacity(1000); - - assert!(vec.last_mut().is_none()); - - for _ in 0..1000 { - let element_old: u64 = rng.gen(); - vec.push(element_old).unwrap(); - - let element_ref = vec.last_mut().unwrap(); - assert_eq!(*element_ref, element_old); - - // Assign a new value. - let element_new: u64 = rng.gen(); - *element_ref = element_new; - - // Assert that it took the effect. - let element_ref = vec.last_mut().unwrap(); - assert_eq!(*element_ref, element_new); - } - } - - #[test] - fn test_bounded_vec_to_array() { - let vec = BoundedVec::from_array(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]); - let arr: [u32; 16] = vec.to_array().unwrap(); - assert_eq!(arr, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]); - - assert!(matches!( - vec.to_array::<15>(), - Err(BoundedVecError::ArraySize(_, _)) - )); - assert!(matches!( - vec.to_array::<17>(), - Err(BoundedVecError::ArraySize(_, _)) - )); - } - - #[test] - fn test_bounded_vec_to_vec() { - let mut rng = thread_rng(); - - for capacity in (0..10_000).step_by(100) { - let vec_1: Vec = (0..capacity).map(|_| rng.gen()).collect(); - let bounded_vec = BoundedVec::from_slice(&vec_1); - let vec_2 = bounded_vec.to_vec(); - - assert_eq!(vec_1.as_slice(), vec_2.as_slice()); - } - } - - #[test] - fn test_bounded_vec_extend() { - let mut rng = thread_rng(); - - for capacity in (1..10_000).step_by(100) { - let length = rng.gen_range(0..capacity); - - let mut vec = BoundedVec::with_capacity(capacity); - vec.extend(0..length).unwrap(); - - assert_eq!(vec.capacity(), capacity); - assert_eq!(vec.len(), length); - for (element_1, element_2) in vec.iter().zip(0..length) { - assert_eq!(*element_1, element_2); - } - } - } - - #[test] - fn test_bounded_vec_clone() { - for _ in 0..1000 { - let bounded_vec = rand_bounded_vec::(); - let cloned_bounded_vec = bounded_vec.clone(); - - assert_eq!(bounded_vec.capacity(), cloned_bounded_vec.capacity()); - assert_eq!(bounded_vec.len(), cloned_bounded_vec.len()); - assert_eq!(bounded_vec, cloned_bounded_vec); - } - } - - #[test] - fn test_bounded_vec_index() { - let mut vec = BoundedVec::with_capacity(1000); - for i in 0..1000 { - vec.push(i).unwrap(); - } - - for i in 0..1000 { - assert_eq!(vec[i], i); - } - - for i in 0..1000 { - vec[i] = i * 2; - } - - for i in 0..1000 { - assert_eq!(vec[i], i * 2); - } - } - - #[test] - fn test_bounded_vec_into_iter() { - let mut vec = BoundedVec::with_capacity(1000); - for i in 0..1000 { - vec.push(i).unwrap(); - } - - for (i, element) in vec.into_iter().enumerate() { - assert_eq!(element, i); - } - } - - #[test] - fn test_cyclic_bounded_vec_metadata_serialization() { - let mut rng = thread_rng(); - - for _ in 0..1000 { - let capacity = rng.gen(); - let metadata = CyclicBoundedVecMetadata::new(capacity); - - assert_eq!(metadata.capacity(), capacity); - assert_eq!(metadata.length(), 0); - - let bytes = metadata.to_le_bytes(); - let metadata_2 = CyclicBoundedVecMetadata::from_le_bytes(bytes); - - assert_eq!(metadata, metadata_2); - } - } - - #[test] - fn test_cyclic_bounded_vec_with_capacity() { - for capacity in 0..1024 { - let cyclic_bounded_vec = CyclicBoundedVec::::with_capacity(capacity); - - assert_eq!(cyclic_bounded_vec.capacity(), capacity); - assert_eq!(cyclic_bounded_vec.len(), 0); - assert_eq!(cyclic_bounded_vec.first_index(), 0); - assert_eq!(cyclic_bounded_vec.last_index(), 0); - } - } - - #[test] - fn test_cyclic_bounded_vec_is_empty() { - let mut rng = thread_rng(); - let mut vec = CyclicBoundedVec::with_capacity(1000); - - assert!(vec.is_empty()); - - for _ in 0..1000 { - let element: u64 = rng.gen(); - vec.push(element); - - assert!(!vec.is_empty()); - } - } - - #[test] - fn test_cyclic_bounded_vec_get() { - let mut vec = CyclicBoundedVec::with_capacity(1000); - - for i in 0..1000 { - vec.push(i); - } - - for i in 0..1000 { - assert_eq!(vec.get(i), Some(&i)); - } - for i in 1000..10_000 { - assert!(vec.get(i).is_none()); - } - } - - #[test] - fn test_cyclic_bounded_vec_get_mut() { - let mut vec = CyclicBoundedVec::with_capacity(1000); - - for i in 0..2000 { - vec.push(i); - } - - for i in 0..1000 { - let element = vec.get_mut(i).unwrap(); - assert_eq!(*element, 1000 + i); - *element = i * 2; - } - for i in 0..1000 { - assert_eq!(vec.get_mut(i), Some(&mut (i * 2))); - } - for i in 1000..10_000 { - assert!(vec.get_mut(i).is_none()); - } - } - - #[test] - fn test_cyclic_bounded_vec_first() { - let mut vec = CyclicBoundedVec::with_capacity(500); - - assert!(vec.first().is_none()); - - for i in 0..1000 { - vec.push(i); - assert_eq!(vec.first(), Some(&((i as u64).saturating_sub(499)))); - } - } - - #[test] - fn test_cyclic_bounded_vec_last() { - let mut rng = thread_rng(); - let mut vec = CyclicBoundedVec::with_capacity(500); - - assert!(vec.last().is_none()); - - for _ in 0..1000 { - let element: u64 = rng.gen(); - vec.push(element); - - assert_eq!(vec.last(), Some(&element)); - } - } - - #[test] - fn test_cyclic_bounded_vec_last_mut() { - let mut rng = thread_rng(); - let mut vec = CyclicBoundedVec::with_capacity(500); - - assert!(vec.last_mut().is_none()); - - for _ in 0..1000 { - let element_old: u64 = rng.gen(); - vec.push(element_old); - - let element_ref = vec.last_mut().unwrap(); - assert_eq!(*element_ref, element_old); - - // Assign a new value. - let element_new: u64 = rng.gen(); - *element_ref = element_new; - - // Assert that it took the effect. - let element_ref = vec.last_mut().unwrap(); - assert_eq!(*element_ref, element_new); - } - } - - #[test] - fn test_cyclic_bounded_vec_manual() { - let mut cyclic_bounded_vec = CyclicBoundedVec::with_capacity(8); - - // Fill up the cyclic vector. - // - // ``` - // ^ $ - // index [0, 1, 2, 3, 4, 5, 6, 7] - // value [0, 1, 2, 3, 4, 5, 6, 7] - // ``` - // - // * `^` - first element - // * `$` - last element - for i in 0..8 { - cyclic_bounded_vec.push(i); - } - assert_eq!(cyclic_bounded_vec.first_index(), 0); - assert_eq!(cyclic_bounded_vec.last_index(), 7); - assert_eq!( - cyclic_bounded_vec.iter().collect::>().as_slice(), - &[&0, &1, &2, &3, &4, &5, &6, &7] - ); - - // Overwrite half of values. - // - // ``` - // $ ^ - // index [0, 1, 2, 3, 4, 5, 6, 7] - // value [8, 9, 10, 11, 4, 5, 6, 7] - // ``` - // - // * `^` - first element - // * `$` - last element - for i in 0..4 { - cyclic_bounded_vec.push(i + 8); - } - assert_eq!(cyclic_bounded_vec.first_index(), 4); - assert_eq!(cyclic_bounded_vec.last_index(), 3); - assert_eq!( - cyclic_bounded_vec.iter().collect::>().as_slice(), - &[&4, &5, &6, &7, &8, &9, &10, &11] - ); - - // Overwrite even more. - // - // ``` - // $ ^ - // index [0, 1, 2, 3, 4, 5, 6, 7] - // value [8, 9, 10, 11, 12, 13, 6, 7] - // ``` - // - // * `^` - first element - // * `$` - last element - for i in 0..2 { - cyclic_bounded_vec.push(i + 12); - } - assert_eq!(cyclic_bounded_vec.first_index(), 6); - assert_eq!(cyclic_bounded_vec.last_index(), 5); - assert_eq!( - cyclic_bounded_vec.iter().collect::>().as_slice(), - &[&6, &7, &8, &9, &10, &11, &12, &13] - ); - - // Overwrite all values from the first loop. - // - // ``` - // ^ $ - // index [0, 1, 2, 3, 4, 5, 6, 7] - // value [8, 9, 10, 11, 12, 13, 14, 15] - // ``` - // - // * `^` - first element - // * `$` - last element - for i in 0..2 { - cyclic_bounded_vec.push(i + 14); - } - assert_eq!(cyclic_bounded_vec.first_index(), 0); - assert_eq!(cyclic_bounded_vec.last_index(), 7); - assert_eq!( - cyclic_bounded_vec.iter().collect::>().as_slice(), - &[&8, &9, &10, &11, &12, &13, &14, &15] - ); - } - - /// Iteration on a vector with one element. - /// - /// ``` - /// ^$ - /// index [0] - /// value [0] - /// ``` - /// - /// * `^` - first element - /// * `$` - last element - /// * `#` - visited elements - /// - /// Length: 1 - /// Capacity: 8 - /// First index: 0 - /// Last index: 0 - /// - /// Start iteration from: 0 - /// - /// Should iterate over one element. - #[test] - fn test_cyclic_bounded_vec_iter_one_element() { - let mut cyclic_bounded_vec = CyclicBoundedVec::with_capacity(8); - cyclic_bounded_vec.push(0); - - assert_eq!(cyclic_bounded_vec.len(), 1); - assert_eq!(cyclic_bounded_vec.capacity(), 8); - assert_eq!(cyclic_bounded_vec.first_index(), 0); - assert_eq!(cyclic_bounded_vec.last_index(), 0); - - let elements = cyclic_bounded_vec.iter().collect::>(); - assert_eq!(elements.len(), 1); - assert_eq!(elements.as_slice(), &[&0]); - - let elements = cyclic_bounded_vec.iter_from(0).unwrap().collect::>(); - assert_eq!(elements.len(), 1); - assert_eq!(elements.as_slice(), &[&0]); - } - - /// Iteration without reset in a vector which is not full. - /// - /// ``` - /// # # # # - /// ^ $ - /// index [0, 1, 2, 3, 4, 5] - /// value [0, 1, 2, 3, 4, 5] - /// ``` - /// - /// * `^` - first element - /// * `$` - last element - /// * `#` - visited elements - /// - /// Length: 6 - /// Capacity: 8 - /// First index: 0 - /// Last index: 5 - /// - /// Start iteration from: 2 - /// - /// Should iterate over elements from 2 to 5, with 4 iterations. - #[test] - fn test_cyclic_bounded_vec_iter_from_without_reset_not_full_6_8_4() { - let mut cyclic_bounded_vec = CyclicBoundedVec::with_capacity(8); - - for i in 0..6 { - cyclic_bounded_vec.push(i); - } - - assert_eq!(cyclic_bounded_vec.len(), 6); - assert_eq!(cyclic_bounded_vec.capacity(), 8); - assert_eq!(cyclic_bounded_vec.first_index(), 0); - assert_eq!(cyclic_bounded_vec.last_index(), 5); - - let elements = cyclic_bounded_vec.iter_from(2).unwrap().collect::>(); - assert_eq!(elements.len(), 4); - assert_eq!(elements.as_slice(), &[&2, &3, &4, &5]); - } - /// Iteration without reset in a vector which is full. - /// - /// ``` - /// # # # - /// ^ $ - /// index [0, 1, 2, 3, 4] - /// value [0, 1, 2, 3, 4] - /// ``` - /// - /// * `^` - first element - /// * `$` - last element - /// * `#` - visited elements - /// - /// Length: 5 - /// Capacity: 5 - /// First index: 0 - /// Last index: 4 - /// - /// Start iteration from: 2 - /// - /// Should iterate over elements 2..4 - 3 iterations. - #[test] - fn test_cyclic_bounded_vec_iter_from_without_reset_not_full_5_5_4() { - let mut cyclic_bounded_vec = CyclicBoundedVec::with_capacity(5); - - for i in 0..5 { - cyclic_bounded_vec.push(i); - } - - assert_eq!(cyclic_bounded_vec.len(), 5); - assert_eq!(cyclic_bounded_vec.capacity(), 5); - assert_eq!(cyclic_bounded_vec.first_index(), 0); - assert_eq!(cyclic_bounded_vec.last_index(), 4); - - let elements = cyclic_bounded_vec.iter_from(2).unwrap().collect::>(); - assert_eq!(elements.len(), 3); - assert_eq!(elements.as_slice(), &[&2, &3, &4]); - } - - /// Iteration without reset in a vector which is full. - /// - /// ``` - /// # # # # # # - /// ^ $ - /// index [0, 1, 2, 3, 4, 5, 6, 7] - /// value [0, 1, 2, 3, 4, 5, 6, 7] - /// ``` - /// - /// * `^` - first element - /// * `$` - last element - /// * `#` - visited elements - /// - /// Length: 8 - /// Capacity: 8 - /// First index: 0 - /// Last index: 7 - /// - /// Start iteration from: 2 - /// - /// Should iterate over elements 2..7 - 6 iterations. - #[test] - fn test_cyclic_bounded_vec_iter_from_without_reset_full_8_8_6() { - let mut cyclic_bounded_vec = CyclicBoundedVec::with_capacity(8); - - for i in 0..8 { - cyclic_bounded_vec.push(i); - } - - assert_eq!(cyclic_bounded_vec.len(), 8); - assert_eq!(cyclic_bounded_vec.capacity(), 8); - assert_eq!(cyclic_bounded_vec.first_index(), 0); - assert_eq!(cyclic_bounded_vec.last_index(), 7); - - let elements = cyclic_bounded_vec.iter_from(2).unwrap().collect::>(); - assert_eq!(elements.len(), 6); - assert_eq!(elements.as_slice(), &[&2, &3, &4, &5, &6, &7]); - } - - /// Iteration with reset. - /// - /// Insert elements over capacity, so the vector resets and starts - /// overwriting elements from the start - 12 elements into a vector with - /// capacity 8. - /// - /// The resulting data structure looks like: - /// - /// ``` - /// # # # # # # - /// $ ^ - /// index [0, 1, 2, 3, 4, 5, 6, 7] - /// value [8, 9, 10, 11, 4, 5, 6, 7] - /// ``` - /// - /// * `^` - first element - /// * `$` - last element - /// * `#` - visited elements - /// - /// Length: 8 - /// Capacity: 8 - /// First: 4 - /// Last: 3 - /// - /// Start iteration from: 6 - /// - /// Should iterate over elements 6..7 and 8..11 - 6 iterations. - #[test] - fn test_cyclic_bounded_vec_iter_from_reset() { - let mut cyclic_bounded_vec = CyclicBoundedVec::with_capacity(8); - - for i in 0..12 { - cyclic_bounded_vec.push(i); - } - - assert_eq!(cyclic_bounded_vec.len(), 8); - assert_eq!(cyclic_bounded_vec.capacity(), 8); - assert_eq!(cyclic_bounded_vec.first_index(), 4); - assert_eq!(cyclic_bounded_vec.last_index(), 3); - - let elements = cyclic_bounded_vec.iter_from(6).unwrap().collect::>(); - assert_eq!(elements.len(), 6); - assert_eq!(elements.as_slice(), &[&6, &7, &8, &9, &10, &11]); - } - - #[test] - fn test_cyclic_bounded_vec_iter_from_out_of_bounds_not_full() { - let mut cyclic_bounded_vec = CyclicBoundedVec::with_capacity(8); - - for i in 0..4 { - cyclic_bounded_vec.push(i); - } - - // Try `start` values in bounds. - for i in 0..4 { - let elements = cyclic_bounded_vec.iter_from(i).unwrap().collect::>(); - assert_eq!(elements.len(), 4 - i); - let expected = (i..4).collect::>(); - // Just to coerce it to have references... - let expected = expected.iter().collect::>(); - assert_eq!(elements.as_slice(), expected.as_slice()); - } - - // Try `start` values out of bounds. - for i in 4..1000 { - let elements = cyclic_bounded_vec.iter_from(i); - assert!(matches!( - elements, - Err(BoundedVecError::IterFromOutOfBounds) - )); - } - } - - #[test] - fn test_cyclic_bounded_vec_iter_from_out_of_bounds_full() { - let mut cyclic_bounded_vec = CyclicBoundedVec::with_capacity(8); - - for i in 0..12 { - cyclic_bounded_vec.push(i); - } - - // Try different `start` values which are out of bounds. - for start in 8..1000 { - let elements = cyclic_bounded_vec.iter_from(start); - assert!(matches!( - elements, - Err(BoundedVecError::IterFromOutOfBounds) - )); - } - } - - #[test] - fn test_cyclic_bounded_vec_iter_from_out_of_bounds_iter_from() { - let mut cyclic_bounded_vec = CyclicBoundedVec::with_capacity(8); - - for i in 0..8 { - assert!(matches!( - cyclic_bounded_vec.iter_from(i), - Err(BoundedVecError::IterFromOutOfBounds) - )); - cyclic_bounded_vec.push(i); - } - } - - #[test] - fn test_cyclic_bounded_vec_overwrite() { - let mut cyclic_bounded_vec = CyclicBoundedVec::with_capacity(64); - - for i in 0..256 { - cyclic_bounded_vec.push(i); - } - - assert_eq!(cyclic_bounded_vec.len(), 64); - assert_eq!(cyclic_bounded_vec.capacity(), 64); - assert_eq!( - cyclic_bounded_vec.iter().collect::>().as_slice(), - &[ - &192, &193, &194, &195, &196, &197, &198, &199, &200, &201, &202, &203, &204, &205, - &206, &207, &208, &209, &210, &211, &212, &213, &214, &215, &216, &217, &218, &219, - &220, &221, &222, &223, &224, &225, &226, &227, &228, &229, &230, &231, &232, &233, - &234, &235, &236, &237, &238, &239, &240, &241, &242, &243, &244, &245, &246, &247, - &248, &249, &250, &251, &252, &253, &254, &255 - ] - ); - } - - #[test] - fn test_clear_pass() { - let mut vec = BoundedVec::with_capacity(5); - vec.push(1).unwrap(); - vec.push(2).unwrap(); - vec.clear(); - assert_eq!(vec.len(), 0); - assert!(vec.get(0).is_none()); - assert!(vec.get(1).is_none()); - } - - #[test] - fn test_clear_fail() { - let mut vec = BoundedVec::with_capacity(5); - vec.push(1).unwrap(); - assert_eq!(vec.get(0).unwrap(), &1); - vec.clear(); - assert_eq!(vec.get(0), None); - } - - #[test] - fn test_deserialize_pass() { - let mut account_data = vec![0u8; 64]; - let mut start_offset = 0; - - // Initialize data with valid BoundedVec metadata and elements - BoundedVec::::init(4, &mut account_data, &mut start_offset, false).unwrap(); - start_offset = 0; - - // Deserialize the BoundedVec - let deserialized_vec = BoundedVec::::deserialize(&mut account_data, &mut start_offset) - .expect("Failed to deserialize BoundedVec"); - - assert_eq!(deserialized_vec.metadata().capacity(), 4); - assert_eq!(deserialized_vec.metadata().length(), 0); - } - - #[test] - fn test_deserialize_multiple_pass() { - let mut account_data = vec![0u8; 128]; - let mut start_offset = 0; - - // Initialize data for multiple BoundedVecs - BoundedVec::::init(4, &mut account_data, &mut start_offset, false).unwrap(); - BoundedVec::::init(4, &mut account_data, &mut start_offset, false).unwrap(); - start_offset = 0; - - // Deserialize multiple BoundedVecs - let deserialized_vecs = - BoundedVec::::deserialize_multiple(2, &mut account_data, &mut start_offset) - .expect("Failed to deserialize multiple BoundedVecs"); - - assert_eq!(deserialized_vecs.len(), 2); - } - - #[test] - fn test_init_pass() { - let mut account_data = vec![0u8; 64]; - let mut start_offset = 0; - - // Initialize a BoundedVec with capacity 4 - let mut vec = BoundedVec::::init(4, &mut account_data, &mut start_offset, false) - .expect("Failed to initialize BoundedVec"); - - assert_eq!(vec.metadata().capacity(), 4); - assert_eq!(vec.metadata().length(), 0); - for i in 0..4 { - assert!(vec.get(i).is_none()); - vec.push(i as u64).unwrap(); - assert_eq!(*vec.get(i).unwrap(), i as u64); - assert!(vec.metadata().length() == i + 1); - } - } - - #[test] - fn test_init_multiple_pass() { - let mut account_data = vec![0u8; 128]; - let mut start_offset = 0; - let mut initialized_vecs = - BoundedVec::::init_multiple(2, 4, &mut account_data, &mut start_offset, false) - .expect("Failed to initialize multiple BoundedVecs"); - - assert_eq!(initialized_vecs.len(), 2); - assert_eq!(initialized_vecs[0].metadata().capacity(), 4); - assert_eq!(initialized_vecs[1].metadata().capacity(), 4); - assert_eq!(initialized_vecs[0].metadata().length(), 0); - assert_eq!(initialized_vecs[1].metadata().length(), 0); - for i in 0..4 { - for vec in initialized_vecs.iter_mut() { - assert!(vec.get(i).is_none()); - vec.push(i as u64).unwrap(); - assert_eq!(*vec.get(i).unwrap(), i as u64); - assert!(vec.metadata().length() == i + 1); - } - } - } - - #[test] - fn test_insufficient_memory_deserialize_metadata() { - let required_memory = mem::size_of::(); - let mut account_data = vec![0u8; required_memory - 1]; - let mut start_offset = 0; - - let result = BoundedVec::::deserialize(&mut account_data, &mut start_offset); - assert!(matches!( - result, - Err(BoundedVecError::InsufficientMemoryAllocated(_, expected_memory - )) if expected_memory == required_memory - )); - } - - #[test] - fn test_insufficient_memory_deserialize_full_vector() { - let required_memory = mem::size_of::() + 4 * mem::size_of::(); - let mut account_data = vec![0u8; required_memory]; - BoundedVec::::init(4, &mut account_data, &mut 0, false).unwrap(); - let mut account_data = account_data[0..required_memory - 1].to_vec(); - let mut start_offset = 0; - - let result = BoundedVec::::deserialize(&mut account_data, &mut start_offset); - assert!(matches!( - result, - Err(BoundedVecError::InsufficientMemoryAllocated(_, expected_memory - )) if expected_memory == required_memory - )); - } - - #[test] - fn test_insufficient_memory_init_single() { - let required_memory = mem::size_of::() + 4 * mem::size_of::(); - let mut account_data = vec![0u8; required_memory - 1]; - let mut start_offset = 0; - let result = BoundedVec::::init(4, &mut account_data, &mut start_offset, false); - assert!(matches!( - result, - Err(BoundedVecError::InsufficientMemoryAllocated(_, expected_memory - )) if expected_memory == required_memory - )); - } - - #[test] - fn test_insufficient_memory_deserialize_multiple() { - let required_memory = - 2 * (mem::size_of::() + 3 * mem::size_of::()); - let mut account_data = vec![0u8; required_memory]; - BoundedVec::::init_multiple(2, 3, &mut account_data, &mut 0, false).unwrap(); - let mut account_data = account_data[0..required_memory - 1].to_vec(); - let mut start_offset = 0; - - let result = - BoundedVec::::deserialize_multiple(2, &mut account_data, &mut start_offset); - let required_memory_per_vec = required_memory / 2; - assert!(matches!( - result, - Err(BoundedVecError::InsufficientMemoryAllocated(_, expected_memory - )) if expected_memory == required_memory_per_vec - )); - } - - #[test] - fn test_insufficient_memory_init_multiple() { - let required_memory = - 2 * (mem::size_of::() + 3 * mem::size_of::()); - let mut account_data = vec![0u8; required_memory - 1]; - - let result = BoundedVec::::init_multiple(2, 3, &mut account_data, &mut 0, false); - let required_memory_per_vec = required_memory / 2; - assert!(matches!( - result, - Err(BoundedVecError::InsufficientMemoryAllocated( - _, - expected_memory - )) if expected_memory == required_memory_per_vec - )); - } -} diff --git a/program-libs/bounded-vec/src/offset/copy.rs b/program-libs/bounded-vec/src/offset/copy.rs deleted file mode 100644 index b08dc8300a..0000000000 --- a/program-libs/bounded-vec/src/offset/copy.rs +++ /dev/null @@ -1,379 +0,0 @@ -use std::{mem, ptr}; - -use crate::{BoundedVec, BoundedVecMetadata, CyclicBoundedVec, CyclicBoundedVecMetadata}; - -/// Creates a copy of value of type `T` based on the provided `bytes` buffer. -/// -/// # Safety -/// -/// This is higly unsafe. This function doesn't ensure alignment and -/// correctness of provided buffer. The responsibility of such checks is on -/// the caller. -pub unsafe fn read_value_at(bytes: &[u8], offset: &mut usize) -> T -where - T: Clone, -{ - let size = mem::size_of::(); - let ptr = bytes[*offset..*offset + size].as_ptr() as *const T; - *offset += size; - ptr::read(ptr) -} - -/// Creates a `BoundedVec` from the sequence of values provided in `bytes` buffer. -/// -/// # Safety -/// -/// This is higly unsafe. This function doesn't ensure alignment and -/// correctness of provided buffer. The responsibility of such checks is on -/// the caller. -/// -/// The `T` type needs to be either a primitive or struct consisting of -/// primitives. It cannot contain any nested heap-backed stucture (like vectors, -/// slices etc.). -pub unsafe fn read_bounded_vec_at( - bytes: &[u8], - offset: &mut usize, - metadata: &BoundedVecMetadata, -) -> BoundedVec -where - T: Clone, -{ - let size = mem::size_of::() * metadata.capacity(); - let ptr = bytes[*offset..*offset + size].as_ptr() as *const T; - - let mut vec = BoundedVec::with_metadata(metadata); - let dst_ptr: *mut T = vec.as_mut_ptr(); - - for i in 0..metadata.length() { - let val = ptr::read(ptr.add(i)); - // SAFETY: We ensured the bounds. - unsafe { ptr::write(dst_ptr.add(i), val) }; - } - - *offset += size; - - vec -} - -/// Creates a `CyclicBoundedVec` from the sequence of values provided in -/// `bytes` buffer. -/// -/// # Safety -/// -/// This is higly unsafe. This function doesn't ensure alignment and -/// correctness of provided buffer. The responsibility of such checks is on -/// the caller. -pub unsafe fn read_cyclic_bounded_vec_at( - bytes: &[u8], - offset: &mut usize, - metadata: &CyclicBoundedVecMetadata, -) -> CyclicBoundedVec -where - T: Clone, -{ - let size = mem::size_of::() * metadata.capacity(); - let src_ptr = bytes[*offset..*offset + size].as_ptr() as *const T; - - let mut vec = CyclicBoundedVec::with_metadata(metadata); - let dst_ptr: *mut T = vec.as_mut_ptr(); - - for i in 0..metadata.length() { - let val = ptr::read(src_ptr.add(i)); - // SAFETY: We ensured the bounds. - unsafe { ptr::write(dst_ptr.add(i), val) }; - } - - *offset += size; - - vec -} - -#[cfg(test)] -mod test { - use std::slice; - - use bytemuck::{Pod, Zeroable}; - use memoffset::offset_of; - - use super::*; - - #[test] - fn test_value_at() { - #[derive(Clone, Copy, Pod, Zeroable)] - #[repr(C)] - struct TestStruct { - a: isize, - b: usize, - c: i64, - d: u64, - e: i32, - f: u32, - g: i16, - _padding_1: [u8; 2], - h: u16, - _padding_2: [u8; 2], - i: i8, - _padding_3: [i8; 3], - j: u8, - _padding_4: [i8; 3], - } - - let mut buf = vec![0_u8; mem::size_of::()]; - let s = buf.as_mut_ptr() as *mut TestStruct; - - unsafe { - (*s).a = isize::MIN; - (*s).b = usize::MAX; - (*s).c = i64::MIN; - (*s).d = u64::MAX; - (*s).e = i32::MIN; - (*s).f = u32::MAX; - (*s).g = i16::MIN; - (*s).h = u16::MAX; - (*s).i = i8::MIN; - (*s).j = u8::MAX; - - let mut offset = offset_of!(TestStruct, a); - assert_eq!(offset, 0); - assert_eq!(read_value_at::(&buf, &mut offset), isize::MIN); - assert_eq!(offset, 8); - - let mut offset = offset_of!(TestStruct, b); - assert_eq!(offset, 8); - assert_eq!(read_value_at::(&buf, &mut offset), usize::MAX); - assert_eq!(offset, 16); - - let mut offset = offset_of!(TestStruct, c); - assert_eq!(offset, 16); - assert_eq!(read_value_at::(&buf, &mut offset), i64::MIN); - assert_eq!(offset, 24); - - let mut offset = offset_of!(TestStruct, d); - assert_eq!(offset, 24); - assert_eq!(read_value_at::(&buf, &mut offset), u64::MAX); - assert_eq!(offset, 32); - - let mut offset = offset_of!(TestStruct, e); - assert_eq!(offset, 32); - assert_eq!(read_value_at::(&buf, &mut offset), i32::MIN); - assert_eq!(offset, 36); - - let mut offset = offset_of!(TestStruct, f); - assert_eq!(offset, 36); - assert_eq!(read_value_at::(&buf, &mut offset), u32::MAX); - assert_eq!(offset, 40); - - let mut offset = offset_of!(TestStruct, g); - assert_eq!(offset, 40); - assert_eq!(read_value_at::(&buf, &mut offset), i16::MIN); - assert_eq!(offset, 42); - - let mut offset = offset_of!(TestStruct, h); - assert_eq!(offset, 44); - assert_eq!(read_value_at::(&buf, &mut offset), u16::MAX); - assert_eq!(offset, 46); - - let mut offset = offset_of!(TestStruct, i); - assert_eq!(offset, 48); - assert_eq!(read_value_at::(&buf, &mut offset), i8::MIN); - assert_eq!(offset, 49); - - let mut offset = offset_of!(TestStruct, j); - assert_eq!(offset, 52); - assert_eq!(read_value_at::(&buf, &mut offset), u8::MAX); - assert_eq!(offset, 53); - } - } - - #[test] - fn test_read_bounded_vec_at() { - #[derive(Clone, Copy, Pod, Zeroable)] - #[repr(C)] - struct TestStruct { - a: [i64; 32], - b: [u64; 32], - c: [[u8; 32]; 32], - } - - let mut buf = vec![0_u8; mem::size_of::()]; - let s = buf.as_mut_ptr() as *mut TestStruct; - - unsafe { - for (i, element) in (*s).a.iter_mut().enumerate() { - *element = -(i as i64); - } - for (i, element) in (*s).b.iter_mut().enumerate() { - *element = i as u64; - } - for (i, element) in (*s).c.iter_mut().enumerate() { - *element = [i as u8; 32]; - } - - let metadata = BoundedVecMetadata::new_with_length(32, 32); - let mut offset = offset_of!(TestStruct, a); - assert_eq!(offset, 0); - let vec: BoundedVec = read_bounded_vec_at(&buf, &mut offset, &metadata); - for (i, element) in vec.iter().enumerate() { - assert_eq!(i as i64, -(*element as i64)); - } - assert_eq!(offset, 256); - - let metadata = BoundedVecMetadata::new_with_length(32, 32); - let mut offset = offset_of!(TestStruct, b); - assert_eq!(offset, 256); - let vec: BoundedVec = read_bounded_vec_at(&buf, &mut offset, &metadata); - for (i, element) in vec.iter().enumerate() { - assert_eq!(i as u64, *element as u64); - } - assert_eq!(offset, 512); - - let metadata = BoundedVecMetadata::new_with_length(32, 32); - let mut offset = offset_of!(TestStruct, c); - assert_eq!(offset, 512); - let vec: BoundedVec<[u8; 32]> = read_bounded_vec_at(&buf, &mut offset, &metadata); - for (i, element) in vec.iter().enumerate() { - assert_eq!(&[i as u8; 32], element); - } - assert_eq!(offset, 1536); - } - } - - #[test] - fn test_read_cyclic_bounded_vec_at() { - #[derive(Clone, Copy, Pod, Zeroable)] - #[repr(C)] - struct TestStruct { - a: [i64; 32], - b: [u64; 32], - c: [[u8; 32]; 32], - } - - let mut buf = vec![0_u8; mem::size_of::()]; - let s = buf.as_mut_ptr() as *mut TestStruct; - - unsafe { - for (i, element) in (*s).a.iter_mut().enumerate() { - *element = -(i as i64); - } - for (i, element) in (*s).b.iter_mut().enumerate() { - *element = i as u64; - } - for (i, element) in (*s).c.iter_mut().enumerate() { - *element = [i as u8; 32]; - } - - // Start the cyclic vecs from the middle. - - let metadata = CyclicBoundedVecMetadata::new_with_indices(32, 32, 14, 13); - let mut offset = offset_of!(TestStruct, a); - assert_eq!(offset, 0); - let vec: CyclicBoundedVec = - read_cyclic_bounded_vec_at(&buf, &mut offset, &metadata); - assert_eq!(vec.capacity(), 32); - assert_eq!(vec.len(), 32); - assert_eq!(vec.first_index(), 14); - assert_eq!(vec.last_index(), 13); - assert_eq!( - vec.iter().collect::>().as_slice(), - &[ - &-14, &-15, &-16, &-17, &-18, &-19, &-20, &-21, &-22, &-23, &-24, &-25, &-26, - &-27, &-28, &-29, &-30, &-31, &-0, &-1, &-2, &-3, &-4, &-5, &-6, &-7, &-8, &-9, - &-10, &-11, &-12, &-13 - ] - ); - assert_eq!(offset, 256); - - let metadata = CyclicBoundedVecMetadata::new_with_indices(32, 32, 14, 13); - let mut offset = offset_of!(TestStruct, b); - assert_eq!(offset, 256); - let vec: CyclicBoundedVec = - read_cyclic_bounded_vec_at(&buf, &mut offset, &metadata); - assert_eq!(vec.capacity(), 32); - assert_eq!(vec.len(), 32); - assert_eq!(vec.first_index(), 14); - assert_eq!(vec.last_index(), 13); - assert_eq!( - vec.iter().collect::>().as_slice(), - &[ - &14, &15, &16, &17, &18, &19, &20, &21, &22, &23, &24, &25, &26, &27, &28, &29, - &30, &31, &0, &1, &2, &3, &4, &5, &6, &7, &8, &9, &10, &11, &12, &13 - ] - ); - assert_eq!(offset, 512); - - let metadata = CyclicBoundedVecMetadata::new_with_indices(32, 32, 14, 13); - let mut offset = offset_of!(TestStruct, c); - assert_eq!(offset, 512); - let vec: CyclicBoundedVec<[u8; 32]> = - read_cyclic_bounded_vec_at(&buf, &mut offset, &metadata); - assert_eq!(vec.capacity(), 32); - assert_eq!(vec.len(), 32); - assert_eq!(vec.first_index(), 14); - assert_eq!(vec.last_index(), 13); - assert_eq!( - vec.iter().collect::>().as_slice(), - &[ - &[14_u8; 32], - &[15_u8; 32], - &[16_u8; 32], - &[17_u8; 32], - &[18_u8; 32], - &[19_u8; 32], - &[20_u8; 32], - &[21_u8; 32], - &[22_u8; 32], - &[23_u8; 32], - &[24_u8; 32], - &[25_u8; 32], - &[26_u8; 32], - &[27_u8; 32], - &[28_u8; 32], - &[29_u8; 32], - &[30_u8; 32], - &[31_u8; 32], - &[0_u8; 32], - &[1_u8; 32], - &[2_u8; 32], - &[3_u8; 32], - &[4_u8; 32], - &[5_u8; 32], - &[6_u8; 32], - &[7_u8; 32], - &[8_u8; 32], - &[9_u8; 32], - &[10_u8; 32], - &[11_u8; 32], - &[12_u8; 32], - &[13_u8; 32], - ] - ); - assert_eq!(offset, 1536); - } - } - - #[test] - fn test_read_cyclic_bounded_vec_first_last() { - let mut vec = CyclicBoundedVec::::with_capacity(2); - vec.push(0); - vec.push(37); - vec.push(49); - - let metadata_bytes = vec.metadata().to_le_bytes(); - let metadata = CyclicBoundedVecMetadata::from_le_bytes(metadata_bytes); - let bytes = unsafe { - slice::from_raw_parts( - vec.as_mut_ptr() as *mut u8, - mem::size_of::() * vec.capacity(), - ) - }; - - let mut offset = 0; - let vec_copy: CyclicBoundedVec = - unsafe { read_cyclic_bounded_vec_at(bytes, &mut offset, &metadata) }; - - assert_eq!(*vec.first().unwrap(), 37); - assert_eq!(vec.first(), vec_copy.first()); // Fails. Both should be 37 - assert_eq!(*vec.last().unwrap(), 49); - assert_eq!(vec.last(), vec_copy.last()); // Fails. Both should be 49 - } -} diff --git a/program-libs/bounded-vec/src/offset/mod.rs b/program-libs/bounded-vec/src/offset/mod.rs deleted file mode 100644 index 03452ca062..0000000000 --- a/program-libs/bounded-vec/src/offset/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod copy; -pub mod zero_copy; diff --git a/program-libs/bounded-vec/src/offset/zero_copy.rs b/program-libs/bounded-vec/src/offset/zero_copy.rs deleted file mode 100644 index 513ce8cd96..0000000000 --- a/program-libs/bounded-vec/src/offset/zero_copy.rs +++ /dev/null @@ -1,336 +0,0 @@ -use std::mem; - -/// Casts a part of provided `bytes` buffer with the given `offset` to a -/// mutable pointer to `T`. -/// -/// Should be used for single values. -/// -/// # Safety -/// -/// This is higly unsafe. This function doesn't ensure alignment and -/// correctness of provided buffer. The responsibility of such checks is on -/// the caller. -pub unsafe fn read_ptr_at(bytes: &[u8], offset: &mut usize) -> *mut T { - let size = mem::size_of::(); - let ptr = bytes[*offset..*offset + size].as_ptr() as *mut T; - *offset += size; - ptr -} - -/// Casts a part of provided `bytes` buffer with the given `offset` to a -/// mutable pointer to `T`. -/// -/// Should be used for array-type sequences. -/// -/// # Safety -/// -/// This is higly unsafe. This function doesn't ensure alignment and -/// correctness of provided buffer. The responsibility of such checks is on -/// the caller. -pub unsafe fn read_array_like_ptr_at(bytes: &[u8], offset: &mut usize, len: usize) -> *mut T { - let size = mem::size_of::() * len; - let ptr = bytes[*offset..*offset + size].as_ptr() as *mut T; - *offset += size; - ptr -} - -/// Casts a part of provided `bytes` buffer with the given `offset` to a -/// mutable pointer to `T`. -/// -/// Should be used for array-type sequences. -/// -/// # Safety -/// -/// This is higly unsafe. This function doesn't ensure alignment and -/// correctness of provided buffer. The responsibility of such checks is on -/// the caller. -pub unsafe fn read_array_like_ptr_at_mut( - bytes: &mut [u8], - offset: &mut usize, - len: usize, -) -> *mut T { - let size = mem::size_of::() * len; - let ptr = bytes[*offset..*offset + size].as_ptr() as *mut T; - *offset += size; - ptr -} - -/// Writes provided `data` into provided `bytes` buffer with the given -/// `offset`. -pub fn write_at(bytes: &mut [u8], data: &[u8], offset: &mut usize) { - let size = mem::size_of::(); - bytes[*offset..*offset + size].copy_from_slice(data); - *offset += size; -} - -#[cfg(test)] -mod test { - use bytemuck::{Pod, Zeroable}; - use memoffset::offset_of; - - use super::*; - - #[test] - fn test_read_ptr_at() { - #[derive(Clone, Copy, Pod, Zeroable)] - #[repr(C)] - struct TestStruct { - a: isize, - b: usize, - c: i64, - d: u64, - e: i32, - f: u32, - g: i16, - _padding_1: [u8; 2], - h: u16, - _padding_2: [u8; 2], - i: i8, - _padding_3: [i8; 3], - j: u8, - _padding_4: [i8; 3], - } - - let mut buf = vec![0_u8; mem::size_of::()]; - let s = buf.as_mut_ptr() as *mut TestStruct; - - unsafe { - (*s).a = isize::MIN; - (*s).b = usize::MAX; - (*s).c = i64::MIN; - (*s).d = u64::MAX; - (*s).e = i32::MIN; - (*s).f = u32::MAX; - (*s).g = i16::MIN; - (*s).h = u16::MAX; - (*s).i = i8::MIN; - (*s).j = u8::MAX; - - let mut offset = offset_of!(TestStruct, a); - assert_eq!(offset, 0); - assert_eq!(*read_ptr_at::(&buf, &mut offset), isize::MIN); - assert_eq!(offset, 8); - - let mut offset = offset_of!(TestStruct, b); - assert_eq!(offset, 8); - assert_eq!(*read_ptr_at::(&buf, &mut offset), usize::MAX); - assert_eq!(offset, 16); - - let mut offset = offset_of!(TestStruct, c); - assert_eq!(offset, 16); - assert_eq!(*read_ptr_at::(&buf, &mut offset), i64::MIN); - assert_eq!(offset, 24); - - let mut offset = offset_of!(TestStruct, d); - assert_eq!(offset, 24); - assert_eq!(*read_ptr_at::(&buf, &mut offset), u64::MAX); - assert_eq!(offset, 32); - - let mut offset = offset_of!(TestStruct, e); - assert_eq!(offset, 32); - assert_eq!(*read_ptr_at::(&buf, &mut offset), i32::MIN); - assert_eq!(offset, 36); - - let mut offset = offset_of!(TestStruct, f); - assert_eq!(offset, 36); - assert_eq!(*read_ptr_at::(&buf, &mut offset), u32::MAX); - assert_eq!(offset, 40); - - let mut offset = offset_of!(TestStruct, g); - assert_eq!(offset, 40); - assert_eq!(*read_ptr_at::(&buf, &mut offset), i16::MIN); - assert_eq!(offset, 42); - - let mut offset = offset_of!(TestStruct, h); - assert_eq!(offset, 44); - assert_eq!(*read_ptr_at::(&buf, &mut offset), u16::MAX); - assert_eq!(offset, 46); - - let mut offset = offset_of!(TestStruct, i); - assert_eq!(offset, 48); - assert_eq!(*read_ptr_at::(&buf, &mut offset), i8::MIN); - assert_eq!(offset, 49); - - let mut offset = offset_of!(TestStruct, j); - assert_eq!(offset, 52); - assert_eq!(*read_ptr_at::(&buf, &mut offset), u8::MAX); - assert_eq!(offset, 53); - } - } - - #[test] - fn test_read_array_like_ptr_at() { - #[derive(Clone, Copy, Pod, Zeroable)] - #[repr(C)] - struct TestStruct { - a: [i64; 32], - b: [u64; 32], - } - - let mut buf = vec![0_u8; mem::size_of::()]; - let s = buf.as_mut_ptr() as *mut TestStruct; - - unsafe { - for (i, element) in (*s).a.iter_mut().enumerate() { - *element = -(i as i64); - } - for (i, element) in (*s).b.iter_mut().enumerate() { - *element = i as u64; - } - - let mut offset = offset_of!(TestStruct, a); - assert_eq!(offset, 0); - let ptr: *mut i64 = read_array_like_ptr_at(&buf, &mut offset, 32); - for i in 0..32 { - assert_eq!(*(ptr.add(i)), -(i as i64)); - } - assert_eq!(offset, 256); - - let mut offset = offset_of!(TestStruct, b); - assert_eq!(offset, 256); - let ptr: *mut u64 = read_array_like_ptr_at(&buf, &mut offset, 32); - for i in 0..32 { - assert_eq!(*(ptr.add(i)), i as u64); - } - assert_eq!(offset, 512); - } - } - - #[test] - fn test_read_array_like_ptr_at_mut() { - #[derive(Clone, Copy, Pod, Zeroable)] - #[repr(C)] - struct TestStruct { - a: [i64; 32], - b: [u64; 32], - } - - let mut buf = vec![0_u8; mem::size_of::()]; - let s = buf.as_mut_ptr() as *mut TestStruct; - - unsafe { - for (i, element) in (*s).a.iter_mut().enumerate() { - *element = -(i as i64); - } - for (i, element) in (*s).b.iter_mut().enumerate() { - *element = i as u64; - } - - let mut offset = offset_of!(TestStruct, a); - assert_eq!(offset, 0); - let ptr: *mut i64 = read_array_like_ptr_at_mut(&mut buf, &mut offset, 32); - for i in 0..32 { - assert_eq!(*(ptr.add(i)), -(i as i64)); - } - assert_eq!(offset, 256); - - let mut offset = offset_of!(TestStruct, b); - assert_eq!(offset, 256); - let ptr: *mut u64 = read_array_like_ptr_at_mut(&mut buf, &mut offset, 32); - for i in 0..32 { - assert_eq!(*(ptr.add(i)), i as u64); - } - assert_eq!(offset, 512); - } - } - - #[test] - fn test_write_at() { - #[derive(Clone, Copy, Pod, Zeroable)] - #[repr(C)] - struct TestStruct { - a: isize, - b: usize, - c: i64, - d: u64, - e: i32, - f: u32, - g: i16, - _padding_1: [u8; 2], - h: u16, - _padding_2: [u8; 2], - i: i8, - _padding_3: [i8; 3], - j: u8, - _padding_4: [i8; 3], - } - - let mut buf = vec![0_u8; mem::size_of::()]; - - let a: isize = isize::MIN; - let b: usize = usize::MAX; - let c: i64 = i64::MIN; - let d: u64 = u64::MAX; - let e: i32 = i32::MIN; - let f: u32 = u32::MAX; - let g: i16 = i16::MIN; - let h: u16 = u16::MAX; - let i: i8 = i8::MIN; - let j: u8 = u8::MAX; - - let mut offset = offset_of!(TestStruct, a); - assert_eq!(offset, 0); - write_at::(&mut buf, &a.to_le_bytes(), &mut offset); - assert_eq!(offset, 8); - - let mut offset = offset_of!(TestStruct, b); - assert_eq!(offset, 8); - write_at::(&mut buf, &b.to_le_bytes(), &mut offset); - assert_eq!(offset, 16); - - let mut offset = offset_of!(TestStruct, c); - assert_eq!(offset, 16); - write_at::(&mut buf, &c.to_le_bytes(), &mut offset); - assert_eq!(offset, 24); - - let mut offset = offset_of!(TestStruct, d); - assert_eq!(offset, 24); - write_at::(&mut buf, &d.to_le_bytes(), &mut offset); - assert_eq!(offset, 32); - - let mut offset = offset_of!(TestStruct, e); - assert_eq!(offset, 32); - write_at::(&mut buf, &e.to_le_bytes(), &mut offset); - assert_eq!(offset, 36); - - let mut offset = offset_of!(TestStruct, f); - assert_eq!(offset, 36); - write_at::(&mut buf, &f.to_le_bytes(), &mut offset); - assert_eq!(offset, 40); - - let mut offset = offset_of!(TestStruct, g); - assert_eq!(offset, 40); - write_at::(&mut buf, &g.to_le_bytes(), &mut offset); - assert_eq!(offset, 42); - - let mut offset = offset_of!(TestStruct, h); - assert_eq!(offset, 44); - write_at::(&mut buf, &h.to_le_bytes(), &mut offset); - assert_eq!(offset, 46); - - let mut offset = offset_of!(TestStruct, i); - assert_eq!(offset, 48); - write_at::(&mut buf, &i.to_le_bytes(), &mut offset); - assert_eq!(offset, 49); - - let mut offset = offset_of!(TestStruct, j); - assert_eq!(offset, 52); - write_at::(&mut buf, &j.to_le_bytes(), &mut offset); - assert_eq!(offset, 53); - - let s = buf.as_mut_ptr() as *mut TestStruct; - - unsafe { - assert_eq!((*s).a, a); - assert_eq!((*s).b, b); - assert_eq!((*s).c, c); - assert_eq!((*s).d, d); - assert_eq!((*s).e, e); - assert_eq!((*s).f, f); - assert_eq!((*s).g, g); - assert_eq!((*s).h, h); - assert_eq!((*s).i, i); - assert_eq!((*s).j, j); - } - } -} diff --git a/program-libs/concurrent-merkle-tree/Cargo.toml b/program-libs/concurrent-merkle-tree/Cargo.toml index 4e495e0ff7..e88ebfe1b8 100644 --- a/program-libs/concurrent-merkle-tree/Cargo.toml +++ b/program-libs/concurrent-merkle-tree/Cargo.toml @@ -19,7 +19,7 @@ borsh = "0.10" bytemuck = { version = "1.17", features = ["derive"] } light-bounded-vec = { workspace = true } light-hasher = { workspace = true } -light-utils = { workspace = true } +light-utils = { version = "1.1.0"} memoffset = "0.9" solana-program = { workspace = true, optional = true } thiserror = "1.0" diff --git a/program-libs/concurrent-merkle-tree/src/copy.rs b/program-libs/concurrent-merkle-tree/src/copy.rs index 9be6afcf08..5f9408ae83 100644 --- a/program-libs/concurrent-merkle-tree/src/copy.rs +++ b/program-libs/concurrent-merkle-tree/src/copy.rs @@ -1,10 +1,8 @@ use std::ops::Deref; -use light_bounded_vec::{ - offset::copy::{read_bounded_vec_at, read_cyclic_bounded_vec_at, read_value_at}, - BoundedVecMetadata, CyclicBoundedVecMetadata, -}; +use light_bounded_vec::{BoundedVecMetadata, CyclicBoundedVecMetadata}; use light_hasher::Hasher; +use light_utils::offset::copy::{read_bounded_vec_at, read_cyclic_bounded_vec_at, read_value_at}; use memoffset::{offset_of, span_of}; use crate::{errors::ConcurrentMerkleTreeError, ConcurrentMerkleTree}; diff --git a/program-libs/concurrent-merkle-tree/src/zero_copy.rs b/program-libs/concurrent-merkle-tree/src/zero_copy.rs index 1c79c77d7a..e82cea1f34 100644 --- a/program-libs/concurrent-merkle-tree/src/zero_copy.rs +++ b/program-libs/concurrent-merkle-tree/src/zero_copy.rs @@ -5,10 +5,10 @@ use std::{ }; use light_bounded_vec::{ - offset::zero_copy::{read_array_like_ptr_at, read_ptr_at, write_at}, BoundedVec, BoundedVecMetadata, CyclicBoundedVec, CyclicBoundedVecMetadata, }; use light_hasher::Hasher; +use light_utils::offset::zero_copy::{read_array_like_ptr_at, read_ptr_at, write_at}; use memoffset::{offset_of, span_of}; use crate::{errors::ConcurrentMerkleTreeError, ConcurrentMerkleTree}; diff --git a/program-libs/indexed-merkle-tree/Cargo.toml b/program-libs/indexed-merkle-tree/Cargo.toml index 222098e07c..c9d011d22f 100644 --- a/program-libs/indexed-merkle-tree/Cargo.toml +++ b/program-libs/indexed-merkle-tree/Cargo.toml @@ -18,7 +18,7 @@ light-bounded-vec = { workspace = true } light-hasher = { workspace = true } light-concurrent-merkle-tree = { workspace = true } light-merkle-tree-reference = { workspace = true } -light-utils = { workspace = true } +light-utils = { version = "1.1.0"} memoffset = "0.9" num-bigint = "0.4" num-traits = "0.2" @@ -32,4 +32,4 @@ thiserror = "1.0" rand = "0.8" hex = "0.4" serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" \ No newline at end of file +serde_json = "1.0" diff --git a/program-libs/indexed-merkle-tree/src/copy.rs b/program-libs/indexed-merkle-tree/src/copy.rs index e83c36f3e6..546110af0e 100644 --- a/program-libs/indexed-merkle-tree/src/copy.rs +++ b/program-libs/indexed-merkle-tree/src/copy.rs @@ -1,13 +1,11 @@ use std::{fmt, marker::PhantomData, ops::Deref}; -use light_bounded_vec::{ - offset::copy::{read_cyclic_bounded_vec_at, read_value_at}, - CyclicBoundedVecMetadata, -}; +use light_bounded_vec::CyclicBoundedVecMetadata; use light_concurrent_merkle_tree::{ copy::ConcurrentMerkleTreeCopy, errors::ConcurrentMerkleTreeError, }; use light_hasher::Hasher; +use light_utils::offset::copy::{read_cyclic_bounded_vec_at, read_value_at}; use num_traits::{CheckedAdd, CheckedSub, ToBytes, Unsigned}; use crate::{errors::IndexedMerkleTreeError, IndexedMerkleTree}; diff --git a/program-libs/indexed-merkle-tree/src/zero_copy.rs b/program-libs/indexed-merkle-tree/src/zero_copy.rs index bf7e36b7c3..1bb8b37f1d 100644 --- a/program-libs/indexed-merkle-tree/src/zero_copy.rs +++ b/program-libs/indexed-merkle-tree/src/zero_copy.rs @@ -5,16 +5,14 @@ use std::{ ops::{Deref, DerefMut}, }; -use light_bounded_vec::{ - offset::zero_copy::{read_array_like_ptr_at, read_ptr_at, write_at}, - CyclicBoundedVec, CyclicBoundedVecMetadata, -}; +use light_bounded_vec::{CyclicBoundedVec, CyclicBoundedVecMetadata}; use light_concurrent_merkle_tree::{ errors::ConcurrentMerkleTreeError, zero_copy::{ConcurrentMerkleTreeZeroCopy, ConcurrentMerkleTreeZeroCopyMut}, ConcurrentMerkleTree, }; use light_hasher::Hasher; +use light_utils::offset::zero_copy::{read_array_like_ptr_at, read_ptr_at, write_at}; use num_traits::{CheckedAdd, CheckedSub, ToBytes, Unsigned}; use crate::{errors::IndexedMerkleTreeError, IndexedMerkleTree}; diff --git a/program-libs/bounded-vec/Cargo.toml b/program-libs/zero-copy/Cargo.toml similarity index 53% rename from program-libs/bounded-vec/Cargo.toml rename to program-libs/zero-copy/Cargo.toml index 84e79a353c..0cc3078017 100644 --- a/program-libs/bounded-vec/Cargo.toml +++ b/program-libs/zero-copy/Cargo.toml @@ -1,20 +1,20 @@ [package] -name = "light-bounded-vec" -version = "1.1.0" -description = "Bounded and cyclic vector implementations" +name = "light-zero-copy" +version = "0.1.0" +description = "Zero copy vector and utils for Solana programs." repository = "https://github.com/Lightprotocol/light-protocol" license = "Apache-2.0" edition = "2021" [features] +default = [] solana = ["solana-program"] [dependencies] -bytemuck = { version = "1.17", features = ["min_const_generics"] } -memoffset = "0.9" solana-program = { workspace = true, optional = true } thiserror = "1.0" +num-traits = { version = "0.2" } [dev-dependencies] rand = "0.8" -bytemuck = { version = "1.17", features = ["derive"] } +num-traits.workspace = true diff --git a/program-libs/zero-copy/src/cyclic_vec.rs b/program-libs/zero-copy/src/cyclic_vec.rs new file mode 100644 index 0000000000..3911c17dd4 --- /dev/null +++ b/program-libs/zero-copy/src/cyclic_vec.rs @@ -0,0 +1,330 @@ +use core::fmt; +use std::{ + fmt::Debug, + marker::PhantomData, + mem::size_of, + ops::{Index, IndexMut}, +}; + +use num_traits::{FromPrimitive, PrimInt, ToPrimitive}; + +use crate::{ + add_padding, errors::ZeroCopyError, vec::ZeroCopyVec, wrapped_pointer_mut::WrappedPointerMut, +}; + +pub type ZeroCopyCyclicVecUsize = ZeroCopyCyclicVec; +pub type ZeroCopyCyclicVecU32 = ZeroCopyCyclicVec; +pub type ZeroCopyCyclicVecU64 = ZeroCopyCyclicVec; +pub type ZeroCopyCyclicVecU16 = ZeroCopyCyclicVec; +pub type ZeroCopyCyclicVecU8 = ZeroCopyCyclicVec; + +pub struct ZeroCopyCyclicVec +where + LEN: FromPrimitive + ToPrimitive + PrimInt, + T: Copy, +{ + current_index: WrappedPointerMut, + vec: ZeroCopyVec, +} + +impl ZeroCopyCyclicVec +where + LEN: FromPrimitive + ToPrimitive + PrimInt, + T: Copy, +{ + pub fn new(capacity: LEN, vec: &mut [u8]) -> Result { + Self::new_at(capacity, vec, &mut 0) + } + + pub fn new_at( + capacity: LEN, + vec: &mut [u8], + offset: &mut usize, + ) -> Result { + let current_index = WrappedPointerMut::::new_at(LEN::zero(), vec, offset)?; + add_padding::(offset); + let vec = ZeroCopyVec::::new_at(capacity, vec, offset)?; + Ok(Self { current_index, vec }) + } + + pub fn new_at_multiple( + num: usize, + capacity: LEN, + account_data: &mut [u8], + offset: &mut usize, + ) -> Result>, ZeroCopyError> { + let mut value_vecs = Vec::with_capacity(num); + for _ in 0..num { + let vec = Self::new_at(capacity, account_data, offset)?; + value_vecs.push(vec); + } + Ok(value_vecs) + } +} + +impl ZeroCopyCyclicVec +where + LEN: FromPrimitive + ToPrimitive + PrimInt, + T: Copy, +{ + pub fn from_bytes(account_data: &mut [u8]) -> Result { + Self::from_bytes_at(account_data, &mut 0) + } + + pub fn from_bytes_at( + account_data: &mut [u8], + offset: &mut usize, + ) -> Result { + let current_index = WrappedPointerMut::::from_bytes_at(account_data, offset)?; + add_padding::(offset); + let vec = ZeroCopyVec::::from_bytes_at(account_data, offset)?; + Ok(Self { current_index, vec }) + } + + pub fn from_bytes_at_multiple( + num: usize, + account_data: &mut [u8], + offset: &mut usize, + ) -> Result, ZeroCopyError> { + let mut value_vecs = Vec::with_capacity(num); + for _ in 0..num { + let vec = Self::from_bytes_at(account_data, offset)?; + value_vecs.push(vec); + } + Ok(value_vecs) + } +} + +impl ZeroCopyCyclicVec +where + LEN: FromPrimitive + ToPrimitive + PrimInt, + T: Copy, +{ + #[inline] + pub fn push(&mut self, value: T) { + if self.vec.len() < self.vec.capacity() { + self.vec.push(value).unwrap(); + } else { + let current_index = self.current_index(); + self.vec[current_index] = value; + } + let new_index = (self.current_index() + 1) % self.vec.capacity(); + *self.current_index = LEN::from_usize(new_index).unwrap(); + } + + #[inline] + pub fn clear(&mut self) { + *self.current_index = LEN::zero(); + self.vec.clear(); + } + + #[inline] + pub fn first(&self) -> Option<&T> { + self.vec.get(self.first_index()) + } + + #[inline] + pub fn first_mut(&mut self) -> Option<&mut T> { + self.vec.get_mut(self.first_index()) + } + + #[inline] + pub fn last(&self) -> Option<&T> { + self.vec.get(self.last_index()) + } + + #[inline] + pub fn last_mut(&mut self) -> Option<&mut T> { + self.vec.get_mut(self.last_index()) + } + + #[inline] + fn current_index(&self) -> usize { + (*self.current_index.get()).to_usize().unwrap() + } + + /// First index is the next index after the last index mod capacity. + #[inline] + pub fn first_index(&self) -> usize { + if self.len() < self.vec.capacity() || self.last_index() == self.vec.capacity() { + 0 + } else { + self.last_index().saturating_add(1) % (self.vec.capacity()) + } + } + + #[inline] + pub fn last_index(&self) -> usize { + if self.current_index() == 0 && self.vec.len() == self.vec.capacity() { + self.vec.capacity().saturating_sub(1) + } else { + self.current_index().saturating_sub(1) % self.vec.capacity() + } + } + + #[inline] + pub fn iter(&self) -> ZeroCopyCyclicVecIterator<'_, LEN, T> { + ZeroCopyCyclicVecIterator { + vec: self, + current: self.first_index(), + is_finished: false, + _marker: PhantomData, + } + } + + #[inline] + pub fn iter_from( + &self, + start: usize, + ) -> Result, ZeroCopyError> { + if start >= self.len() { + return Err(ZeroCopyError::IterFromOutOfBounds); + } + Ok(ZeroCopyCyclicVecIterator { + vec: self, + current: start, + is_finished: false, + _marker: PhantomData, + }) + } + + pub fn metadata_size() -> usize { + let mut size = size_of::(); + add_padding::(&mut size); + size + } + + pub fn data_size(length: LEN) -> usize { + ZeroCopyVec::::required_size_for_capacity(length.to_usize().unwrap()) + } + + pub fn required_size_for_capacity(capacity: usize) -> usize { + Self::metadata_size() + Self::data_size(LEN::from_usize(capacity).unwrap()) + } + + #[inline] + pub fn len(&self) -> usize { + self.vec.len() + } + + #[inline] + pub fn capacity(&self) -> usize { + self.vec.capacity() + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.vec.is_empty() + } + + #[inline] + pub fn get(&self, index: usize) -> Option<&T> { + self.vec.get(index) + } + + #[inline] + pub fn get_mut(&mut self, index: usize) -> Option<&mut T> { + self.vec.get_mut(index) + } + + #[inline] + pub fn as_slice(&self) -> &[T] { + self.vec.as_slice() + } + + #[inline] + pub fn as_mut_slice(&mut self) -> &mut [T] { + self.vec.as_mut_slice() + } + + pub fn try_into_array(&self) -> Result<[T; N], ZeroCopyError> { + self.vec.try_into_array() + } + + #[inline] + pub fn to_vec(&self) -> Vec { + self.vec.to_vec() + } +} + +pub struct ZeroCopyCyclicVecIterator<'a, LEN, T> +where + LEN: FromPrimitive + ToPrimitive + PrimInt, + T: Copy, +{ + vec: &'a ZeroCopyCyclicVec, + current: usize, + is_finished: bool, + _marker: PhantomData, +} + +impl<'a, LEN, T> Iterator for ZeroCopyCyclicVecIterator<'a, LEN, T> +where + LEN: FromPrimitive + ToPrimitive + PrimInt, + T: Copy, +{ + type Item = &'a T; + + #[inline] + fn next(&mut self) -> Option { + if self.vec.capacity() == 0 || self.is_finished { + None + } else { + if self.current == self.vec.last_index() { + self.is_finished = true; + } + let new_current = (self.current + 1) % self.vec.capacity(); + let element = self.vec.get(self.current); + self.current = new_current; + element + } + } +} + +impl IndexMut for ZeroCopyCyclicVec +where + LEN: FromPrimitive + ToPrimitive + PrimInt, + T: Copy, +{ + #[inline] + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + // Access the underlying mutable slice using as_mut_slice() and index it + &mut self.as_mut_slice()[index] + } +} + +impl Index for ZeroCopyCyclicVec +where + LEN: FromPrimitive + ToPrimitive + PrimInt, + T: Copy, +{ + type Output = T; + + #[inline] + fn index(&self, index: usize) -> &Self::Output { + // Access the underlying slice using as_slice() and index it + &self.as_slice()[index] + } +} + +impl PartialEq for ZeroCopyCyclicVec +where + LEN: FromPrimitive + ToPrimitive + PrimInt, + T: Copy + PartialEq, +{ + #[inline] + fn eq(&self, other: &Self) -> bool { + self.vec == other.vec && *self.current_index == *other.current_index + } +} + +impl fmt::Debug for ZeroCopyCyclicVec +where + LEN: FromPrimitive + ToPrimitive + PrimInt, + T: Copy + Debug, +{ + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self.to_vec()) + } +} diff --git a/program-libs/zero-copy/src/errors.rs b/program-libs/zero-copy/src/errors.rs new file mode 100644 index 0000000000..b4349e4e00 --- /dev/null +++ b/program-libs/zero-copy/src/errors.rs @@ -0,0 +1,38 @@ +use thiserror::Error; + +#[derive(Debug, Error, PartialEq)] +pub enum ZeroCopyError { + #[error("The vector is full, cannot push any new elements")] + Full, + #[error("Requested array of size {0}, but the vector has {1} elements")] + ArraySize(usize, usize), + #[error("The requested start index is out of bounds.")] + IterFromOutOfBounds, + #[error("Memory allocated {0}, Memory required {0}")] + InsufficientMemoryAllocated(usize, usize), + #[error("Invalid Account size.")] + InvalidAccountSize, + #[error("Unaligned pointer.")] + UnalignedPointer, +} + +#[cfg(feature = "solana")] +impl From for u32 { + fn from(e: ZeroCopyError) -> u32 { + match e { + ZeroCopyError::Full => 15001, + ZeroCopyError::ArraySize(_, _) => 15002, + ZeroCopyError::IterFromOutOfBounds => 15003, + ZeroCopyError::InsufficientMemoryAllocated(_, _) => 15004, + ZeroCopyError::InvalidAccountSize => 15005, + ZeroCopyError::UnalignedPointer => 15006, + } + } +} + +#[cfg(feature = "solana")] +impl From for solana_program::program_error::ProgramError { + fn from(e: ZeroCopyError) -> Self { + solana_program::program_error::ProgramError::Custom(e.into()) + } +} diff --git a/program-libs/zero-copy/src/lib.rs b/program-libs/zero-copy/src/lib.rs new file mode 100644 index 0000000000..abfbe2181a --- /dev/null +++ b/program-libs/zero-copy/src/lib.rs @@ -0,0 +1,51 @@ +use errors::ZeroCopyError; + +pub mod cyclic_vec; +pub mod errors; +pub mod slice_mut; +pub mod vec; +pub mod wrapped_pointer; +pub mod wrapped_pointer_mut; + +use std::mem::{align_of, size_of}; + +pub const SIZE_OF_ZERO_COPY_SLICE_METADATA: usize = 8; +pub const SIZE_OF_ZERO_COPY_VEC_METADATA: usize = 16; +pub const SIZE_OF_ZERO_COPY_CYCLIC_VEC_METADATA: usize = 24; +pub const DISCRIMINATOR_LEN: usize = 8; + +pub fn is_aligned(ptr: *const T) -> bool { + (ptr as usize) % align_of::() == 0 +} + +pub fn check_alignment(ptr: *const T) -> Result<(), errors::ZeroCopyError> { + if !is_aligned(ptr) { + println!("Alignment mismatch: {}", (ptr as usize) % align_of::()); + println!("align_of::(): {}", align_of::()); + return Err(errors::ZeroCopyError::UnalignedPointer); + } + Ok(()) +} + +pub fn size_is_ok(data_size: usize) -> bool { + data_size >= size_of::() +} + +pub fn check_size(bytes: &[u8]) -> Result<(), ZeroCopyError> { + if !size_is_ok::(bytes.len()) { + return Err(ZeroCopyError::InsufficientMemoryAllocated( + bytes.len(), + size_of::(), + )); + } + Ok(()) +} + +pub fn add_padding(offset: &mut usize) +where + LEN: Copy, + T: Copy, +{ + let padding = align_of::().saturating_sub(size_of::()); + *offset += padding; +} diff --git a/program-libs/zero-copy/src/slice_mut.rs b/program-libs/zero-copy/src/slice_mut.rs new file mode 100644 index 0000000000..a096c90426 --- /dev/null +++ b/program-libs/zero-copy/src/slice_mut.rs @@ -0,0 +1,333 @@ +use core::{fmt, slice}; +use std::{ + marker::PhantomData, + mem::{size_of, ManuallyDrop}, + ops::{Index, IndexMut}, +}; + +use num_traits::{FromPrimitive, PrimInt, ToPrimitive}; + +use crate::{add_padding, check_alignment, errors::ZeroCopyError, wrapped_pointer::WrappedPointer}; + +pub type ZeroCopySliceMutUsize = ZeroCopySliceMut; +pub type ZeroCopySliceMutU32 = ZeroCopySliceMut; +pub type ZeroCopySliceMutU64 = ZeroCopySliceMut; +pub type ZeroCopySliceMutU16 = ZeroCopySliceMut; +pub type ZeroCopySliceMutU8 = ZeroCopySliceMut; + +#[repr(C)] +pub struct ZeroCopySliceMut +where + LEN: Copy, +{ + length: WrappedPointer, + data: ManuallyDrop<*mut T>, + _marker: PhantomData, +} + +impl ZeroCopySliceMut +where + LEN: ToPrimitive + Copy, + T: Copy, +{ + pub fn new(length: LEN, data: &mut [u8]) -> Result { + Self::new_at(length, data, &mut 0) + } + + pub fn new_at(length: LEN, data: &mut [u8], offset: &mut usize) -> Result { + let data = data.split_at_mut(*offset).1; + if Self::required_size_for_capacity(length) > data.len() { + return Err(ZeroCopyError::InsufficientMemoryAllocated( + data.len(), + Self::required_size_for_capacity(length), + )); + } + + let metadata_size = Self::metadata_size(); + *offset += metadata_size; + let (metadata, data) = data.split_at_mut(metadata_size); + let len = WrappedPointer::::new(length, metadata)?; + let data = Self::data_ptr_from_bytes(data)?; + *offset += Self::data_size(length); + + Ok(Self { + length: len, + data, + _marker: PhantomData, + }) + } + + pub fn new_at_multiple( + num_slices: usize, + capacity: LEN, + bytes: &mut [u8], + offset: &mut usize, + ) -> Result>, ZeroCopyError> { + let mut slices = Vec::with_capacity(num_slices); + for _ in 0..num_slices { + let slice = Self::new_at(capacity, bytes, offset)?; + slices.push(slice); + } + Ok(slices) + } + + fn data_ptr_from_bytes(bytes: &mut [u8]) -> Result, ZeroCopyError> { + let data_ptr = bytes.as_mut_ptr() as *mut T; + check_alignment(data_ptr)?; + let data = ManuallyDrop::new(data_ptr); + Ok(data) + } + + pub fn from_bytes(bytes: &mut [u8]) -> Result { + Self::from_bytes_at(bytes, &mut 0) + } + + pub fn from_bytes_at( + bytes: &mut [u8], + offset: &mut usize, + ) -> Result, ZeroCopyError> { + let meta_data_size = Self::metadata_size(); + if bytes.len().saturating_sub(*offset) < meta_data_size { + return Err(ZeroCopyError::InsufficientMemoryAllocated( + bytes.len().saturating_sub(*offset), + meta_data_size, + )); + } + let length = WrappedPointer::::from_bytes_at(bytes, offset)?; + add_padding::(offset); + let full_vector_size = Self::data_size(*length); + if bytes.len().saturating_sub(*offset) < full_vector_size { + return Err(ZeroCopyError::InsufficientMemoryAllocated( + bytes.len().saturating_sub(*offset), + full_vector_size + meta_data_size, + )); + } + let bytes = &mut bytes[*offset..]; + *offset += full_vector_size; + + Ok(ZeroCopySliceMut { + length, + data: Self::data_ptr_from_bytes(bytes)?, + _marker: PhantomData, + }) + } + + pub fn from_bytes_at_multiple( + num_slices: usize, + bytes: &mut [u8], + offset: &mut usize, + ) -> Result>, ZeroCopyError> { + let mut slices = Vec::with_capacity(num_slices); + for _ in 0..num_slices { + let slice = Self::from_bytes_at(bytes, offset)?; + slices.push(slice); + } + Ok(slices) + } + + pub fn try_into_array(&self) -> Result<[T; N], ZeroCopyError> { + if self.len() != N { + return Err(ZeroCopyError::ArraySize(N, self.len())); + } + Ok(std::array::from_fn(|i| *self.get(i).unwrap())) + } + + #[inline] + pub fn metadata_size() -> usize { + let mut size = size_of::(); + add_padding::(&mut size); + size + } + + #[inline] + pub fn data_size(length: LEN) -> usize { + length.to_usize().unwrap() * size_of::() + } + + #[inline] + pub fn required_size_for_capacity(capacity: LEN) -> usize { + Self::metadata_size() + Self::data_size(capacity) + } +} + +impl ZeroCopySliceMut +where + LEN: ToPrimitive + Copy, + T: Copy, +{ + pub fn copy_from_slice(&mut self, slice: &[T]) { + let len = slice.len(); + if len != self.len() { + panic!( + "Slice length mismatch. Expected: {}, Found: {}", + self.len(), + len + ); + } + + unsafe { + std::ptr::copy_nonoverlapping(slice.as_ptr(), self.data_as_mut_ptr(), len); + } + } +} + +impl ZeroCopySliceMut +where + LEN: ToPrimitive + Copy, + T: Copy, +{ + #[inline] + pub fn len(&self) -> usize { + (*self.length).to_usize().unwrap() + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + #[inline] + pub fn first(&self) -> Option<&T> { + self.get(0) + } + + #[inline] + pub fn first_mut(&mut self) -> Option<&mut T> { + self.get_mut(0) + } + + #[inline] + pub fn last(&self) -> Option<&T> { + self.get(self.len().saturating_sub(1)) + } + + #[inline] + pub fn last_mut(&mut self) -> Option<&mut T> { + self.get_mut(self.len().saturating_sub(1)) + } + + #[inline] + pub fn as_slice(&self) -> &[T] { + unsafe { slice::from_raw_parts(*self.data as *const T, self.len()) } + } + + #[inline] + pub fn as_mut_slice(&mut self) -> &mut [T] { + unsafe { slice::from_raw_parts_mut(*self.data, self.len()) } + } + + #[inline] + pub fn data_as_ptr(&self) -> *const T { + self.as_slice().as_ptr() + } + + #[inline] + pub fn data_as_mut_ptr(&mut self) -> *mut T { + self.as_mut_slice().as_mut_ptr() + } + + #[inline] + pub fn get(&self, index: usize) -> Option<&T> { + self.as_slice().get(index) + } + + #[inline] + pub fn get_mut(&mut self, index: usize) -> Option<&mut T> { + self.as_mut_slice().get_mut(index) + } + + #[inline] + pub fn to_vec(&self) -> Vec { + self.as_slice().to_vec() + } +} + +impl IndexMut for ZeroCopySliceMut +where + LEN: FromPrimitive + ToPrimitive + Copy, + T: Copy, +{ + #[inline] + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.as_mut_slice()[index] + } +} + +impl Index for ZeroCopySliceMut +where + LEN: FromPrimitive + ToPrimitive + Copy, + T: Copy, +{ + type Output = T; + + #[inline] + fn index(&self, index: usize) -> &Self::Output { + &self.as_slice()[index] + } +} + +impl<'a, LEN, T> IntoIterator for &'a ZeroCopySliceMut +where + LEN: FromPrimitive + ToPrimitive + PrimInt, + T: Copy, +{ + type Item = &'a T; + type IntoIter = slice::Iter<'a, T>; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a, LEN, T> IntoIterator for &'a mut ZeroCopySliceMut +where + LEN: FromPrimitive + ToPrimitive + PrimInt, + T: Copy, +{ + type Item = &'a mut T; + type IntoIter = slice::IterMut<'a, T>; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.iter_mut() + } +} + +impl<'b, LEN, T> ZeroCopySliceMut +where + LEN: FromPrimitive + ToPrimitive + PrimInt, + T: Copy, +{ + #[inline] + pub fn iter(&'b self) -> slice::Iter<'b, T> { + self.as_slice().iter() + } + + #[inline] + pub fn iter_mut(&'b mut self) -> slice::IterMut<'b, T> { + self.as_mut_slice().iter_mut() + } +} + +impl PartialEq for ZeroCopySliceMut +where + LEN: FromPrimitive + ToPrimitive + Copy, + T: Copy + PartialEq, +{ + #[inline] + fn eq(&self, other: &Self) -> bool { + self.as_slice() == other.as_slice() && self.len() == other.len() + } +} + +impl fmt::Debug for ZeroCopySliceMut +where + T: Copy + fmt::Debug, + LEN: FromPrimitive + ToPrimitive + Copy, +{ + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self.to_vec()) + } +} diff --git a/program-libs/zero-copy/src/vec.rs b/program-libs/zero-copy/src/vec.rs new file mode 100644 index 0000000000..0c44b0fe3b --- /dev/null +++ b/program-libs/zero-copy/src/vec.rs @@ -0,0 +1,308 @@ +use core::slice; +use std::{ + fmt, + mem::size_of, + ops::{Index, IndexMut}, + ptr::{self}, +}; + +use num_traits::{FromPrimitive, PrimInt, ToPrimitive}; + +use crate::{ + add_padding, errors::ZeroCopyError, slice_mut::ZeroCopySliceMut, + wrapped_pointer_mut::WrappedPointerMut, +}; + +pub type ZeroCopyVecUsize = ZeroCopyVec; +pub type ZeroCopyVecU64 = ZeroCopyVec; +pub type ZeroCopyVecU32 = ZeroCopyVec; +pub type ZeroCopyVecU16 = ZeroCopyVec; +pub type ZeroCopyVecU8 = ZeroCopyVec; + +/// `ZeroCopyVec` is a custom vector implementation which forbids +/// post-initialization reallocations. The size is not known during compile +/// time (that makes it different from arrays), but can be defined only once +/// (that makes it different from [`Vec`](std::vec::Vec)). +pub struct ZeroCopyVec +where + LEN: FromPrimitive + Copy, + T: Copy, +{ + length: WrappedPointerMut, + data: ZeroCopySliceMut, +} + +impl ZeroCopyVec +where + LEN: FromPrimitive + ToPrimitive + PrimInt, + T: Copy, +{ + pub fn new(capacity: LEN, data: &mut [u8]) -> Result { + Self::new_at(capacity, data, &mut 0) + } + + pub fn new_at( + capacity: LEN, + data: &mut [u8], + offset: &mut usize, + ) -> Result { + let length = WrappedPointerMut::::new_at(LEN::zero(), data, offset).unwrap(); + add_padding::(offset); + let data = ZeroCopySliceMut::::new_at(capacity, data, offset)?; + Ok(Self { length, data }) + } + + pub fn new_at_multiple( + num: usize, + capacity: LEN, + bytes: &mut [u8], + offset: &mut usize, + ) -> Result>, ZeroCopyError> { + let mut value_vecs = Vec::with_capacity(num); + for _ in 0..num { + let vec = Self::new_at(capacity, bytes, offset)?; + value_vecs.push(vec); + } + Ok(value_vecs) + } + + pub fn from_bytes(bytes: &mut [u8]) -> Result, ZeroCopyError> { + Self::from_bytes_at(bytes, &mut 0) + } + + pub fn from_bytes_at( + bytes: &mut [u8], + offset: &mut usize, + ) -> Result, ZeroCopyError> { + let length = WrappedPointerMut::::from_bytes_at(bytes, offset)?; + add_padding::(offset); + let data = ZeroCopySliceMut::from_bytes_at(bytes, offset)?; + Ok(ZeroCopyVec { length, data }) + } + + pub fn from_bytes_at_multiple( + num: usize, + bytes: &mut [u8], + offset: &mut usize, + ) -> Result, ZeroCopyError> { + let mut value_vecs = Vec::with_capacity(num); + for _ in 0..num { + let vec = Self::from_bytes_at(bytes, offset)?; + value_vecs.push(vec); + } + Ok(value_vecs) + } + + #[inline] + pub fn capacity(&self) -> usize { + self.data.len() + } + + #[inline] + pub fn push(&mut self, value: T) -> Result<(), ZeroCopyError> { + if self.len() == self.capacity() { + return Err(ZeroCopyError::Full); + } + + unsafe { ptr::write(self.data.data_as_mut_ptr().add(self.len()), value) }; + *self.length = *self.length + LEN::one(); + + Ok(()) + } + + #[inline] + pub fn clear(&mut self) { + *self.length.get_mut() = LEN::zero(); + } + + #[inline] + pub fn metadata_size() -> usize { + let mut size = size_of::(); + add_padding::(&mut size); + size + } + + #[inline] + pub fn data_size(length: LEN) -> usize { + ZeroCopySliceMut::::required_size_for_capacity(length) + } + + #[inline] + pub fn required_size_for_capacity(capacity: usize) -> usize { + Self::metadata_size() + Self::data_size(LEN::from_usize(capacity).unwrap()) + } +} + +impl ZeroCopyVec +where + LEN: FromPrimitive + ToPrimitive + PrimInt, + T: Copy, +{ + #[inline] + pub fn len(&self) -> usize { + (*self.length).to_usize().unwrap() + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + #[inline] + pub fn get(&self, index: usize) -> Option<&T> { + if index >= self.len() { + return None; + } + Some(&self.data[index]) + } + + #[inline] + pub fn get_mut(&mut self, index: usize) -> Option<&mut T> { + if index >= self.len() { + return None; + } + Some(&mut self.data[index]) + } + + #[inline] + pub fn first(&self) -> Option<&T> { + self.get(0) + } + + #[inline] + pub fn first_mut(&mut self) -> Option<&mut T> { + self.get_mut(0) + } + + #[inline] + pub fn last(&self) -> Option<&T> { + self.get(self.len().saturating_sub(1)) + } + + #[inline] + pub fn last_mut(&mut self) -> Option<&mut T> { + self.get_mut(self.len().saturating_sub(1)) + } + + #[inline] + pub fn as_slice(&self) -> &[T] { + &self.data.as_slice()[..self.len()] + } + + #[inline] + pub fn as_mut_slice(&mut self) -> &mut [T] { + let len = self.len(); + &mut self.data.as_mut_slice()[..len] + } + + pub fn extend_from_slice(&mut self, slice: &[T]) { + let len = self.len(); + let new_len = len + slice.len(); + if new_len > self.capacity() { + panic!("Capacity overflow. Cannot copy slice into ZeroCopyVec"); + } + self.data.as_mut_slice()[len..].copy_from_slice(slice); + *self.length = LEN::from_usize(new_len).unwrap(); + } + + #[inline] + pub fn to_vec(&self) -> Vec { + self.as_slice().to_vec() + } + + pub fn try_into_array(&self) -> Result<[T; N], ZeroCopyError> { + self.data.try_into_array() + } +} + +impl IndexMut for ZeroCopyVec +where + LEN: FromPrimitive + ToPrimitive + PrimInt, + T: Copy, +{ + #[inline] + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + // Access the underlying mutable slice using as_mut_slice() and index it + &mut self.as_mut_slice()[index] + } +} + +impl Index for ZeroCopyVec +where + LEN: FromPrimitive + ToPrimitive + PrimInt, + T: Copy, +{ + type Output = T; + + #[inline] + fn index(&self, index: usize) -> &Self::Output { + // Access the underlying slice using as_slice() and index it + &self.as_slice()[index] + } +} + +impl<'a, LEN, T> IntoIterator for &'a ZeroCopyVec +where + LEN: FromPrimitive + ToPrimitive + PrimInt, + T: Copy, +{ + type Item = &'a T; + type IntoIter = slice::Iter<'a, T>; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a, LEN, T> IntoIterator for &'a mut ZeroCopyVec +where + LEN: FromPrimitive + ToPrimitive + PrimInt, + T: Copy, +{ + type Item = &'a mut T; + type IntoIter = slice::IterMut<'a, T>; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.iter_mut() + } +} + +impl<'b, LEN, T> ZeroCopyVec +where + LEN: FromPrimitive + ToPrimitive + PrimInt, + T: Copy, +{ + #[inline] + pub fn iter(&'b self) -> slice::Iter<'b, T> { + self.as_slice().iter() + } + + #[inline] + pub fn iter_mut(&'b mut self) -> slice::IterMut<'b, T> { + self.as_mut_slice().iter_mut() + } +} + +impl PartialEq for ZeroCopyVec +where + LEN: FromPrimitive + ToPrimitive + PrimInt, + T: Copy + PartialEq, +{ + #[inline] + fn eq(&self, other: &Self) -> bool { + self.data == other.data && self.len() == other.len() + } +} + +impl fmt::Debug for ZeroCopyVec +where + LEN: FromPrimitive + ToPrimitive + PrimInt, + T: Copy + fmt::Debug, +{ + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self.to_vec()) + } +} diff --git a/program-libs/zero-copy/src/wrapped_pointer.rs b/program-libs/zero-copy/src/wrapped_pointer.rs new file mode 100644 index 0000000000..c61be5ca4a --- /dev/null +++ b/program-libs/zero-copy/src/wrapped_pointer.rs @@ -0,0 +1,286 @@ +use core::fmt; +use std::{ + fmt::{Debug, Formatter}, + marker::PhantomData, + mem::{size_of, ManuallyDrop}, + ops::Deref, +}; + +use crate::{check_alignment, check_size, errors::ZeroCopyError}; + +pub struct WrappedPointer +where + T: Copy, +{ + ptr: ManuallyDrop<*const T>, + _marker: PhantomData, +} + +impl WrappedPointer +where + T: Copy, +{ + pub fn new(value: T, data: &mut [u8]) -> Result { + check_size::(data)?; + let ptr = data.as_mut_ptr() as *mut T; + check_alignment(ptr)?; + unsafe { + ptr.write(value); + } + let ptr = data.as_ptr() as *const T; + + unsafe { Self::from_raw_parts(ptr) } + } + + pub fn new_at(value: T, data: &mut [u8], offset: &mut usize) -> Result { + let new = Self::new(value, &mut data[*offset..])?; + *offset += new.size(); + Ok(new) + } + + /// Create a new `WrappedPointer` from a raw pointer. + /// # Safety + /// This function is unsafe because it creates a `WrappedPointer` from a raw pointer. + /// The caller must ensure that the pointer is valid and properly aligned. + pub unsafe fn from_raw_parts(ptr: *const T) -> Result { + Ok(WrappedPointer { + ptr: ManuallyDrop::new(ptr), + _marker: PhantomData, + }) + } + + pub fn size(&self) -> usize { + size_of::() + } + + pub fn get(&self) -> &T { + unsafe { &**self.ptr } + } + + pub fn from_bytes(bytes: &[u8]) -> Result { + check_size::(bytes)?; + let ptr = bytes.as_ptr() as *const T; + check_alignment(ptr)?; + unsafe { Self::from_raw_parts(ptr) } + } + + pub fn from_bytes_at(bytes: &[u8], offset: &mut usize) -> Result { + let new = Self::from_bytes(&bytes[*offset..])?; + *offset += new.size(); + Ok(new) + } + + pub fn from_bytes_with_discriminator(bytes: &[u8]) -> Result { + let mut offset = crate::DISCRIMINATOR_LEN; + Self::from_bytes_at(bytes, &mut offset) + } + + pub fn as_ptr(&self) -> *const T { + *self.ptr + } +} + +impl PartialEq for WrappedPointer +where + T: Copy + PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + *self.get() == *other.get() + } +} + +impl Debug for WrappedPointer +where + T: Copy + Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self.get()) + } +} + +impl Deref for WrappedPointer +where + T: Copy, +{ + type Target = T; + + fn deref(&self) -> &Self::Target { + self.get() + } +} + +/// Test coverage: +/// 1. Test `WrappedPointer::new` success +/// 2. Test `WrappedPointer::new` with unaligned pointer +/// 3. Test `WrappedPointer::new` with insufficient space +/// 4. Test `WrappedPointer::new_at` success +/// 5. Test `WrappedPointer::new_at` with out of bounds +/// 6. Test `WrappedPointer::new_at` with insufficient memory +/// 7. Test `WrappedPointer::from_bytes` with success +/// 8. Test `WrappedPointer::from_bytes` with insufficient memory +/// 9. Test `WrappedPointer::from_bytes_at` with success +/// 10. Test `WrappedPointer::from_bytes_with_discriminator` with success +/// 11. Test `WrappedPointer::from_bytes_with_discriminator` with insufficient memory (out of bounds) +/// 11. Test `WrappedPointer::from_bytes_with_discriminator` with insufficient memory (value) +/// 12. Test `WrappedPointer::deref` success +/// 13. Test `WrappedPointer::size` success +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_rawpointer_new_success() { + let mut buffer = [0u8; 16]; + let value = 42u32; + + let pointer = WrappedPointer::new(value, &mut buffer).unwrap(); + assert_eq!(*pointer.get(), value); + assert_eq!(buffer[0..4], value.to_le_bytes()); + assert_eq!(buffer[4..16], [0u8; 12]); + } + + #[test] + fn test_rawpointer_new_unaligned() { + let mut buffer = [0u8; 5]; + let value = 42u32; + + let result = WrappedPointer::new(value, &mut buffer[1..]); + assert_eq!(result, Err(ZeroCopyError::UnalignedPointer)); + } + + #[test] + fn test_rawpointer_new_insufficient_space() { + let mut buffer = [0u8; 3]; + let value = 42u32; + + let result = WrappedPointer::new(value, &mut buffer); + assert_eq!( + result, + Err(ZeroCopyError::InsufficientMemoryAllocated(3, 4)) + ); + } + + #[test] + fn test_rawpointer_new_at_success() { + let mut buffer = [0u8; 16]; + let mut offset = 4; + let value = 42u32; + + let pointer = WrappedPointer::new_at(value, &mut buffer, &mut offset).unwrap(); + assert_eq!(*pointer.get(), value); + assert_eq!(offset, 8); // Size of u32 + assert_eq!(buffer[0..4], [0u8; 4]); + assert_eq!(buffer[4..8], value.to_le_bytes()); + assert_eq!(buffer[8..16], [0u8; 8]); + } + + #[test] + #[should_panic(expected = "out of range for slice of")] + fn test_rawpointer_new_at_out_of_bounds() { + let mut buffer = [0u8; 4]; + let mut offset = 5; + let value = 42u32; + + WrappedPointer::new_at(value, &mut buffer, &mut offset).unwrap(); + } + + #[test] + fn test_rawpointer_new_at_insufficient_memory() { + let mut buffer = [0u8; 4]; + let mut offset = 4; + let value = 42u32; + + let result = WrappedPointer::new_at(value, &mut buffer, &mut offset); + assert_eq!( + result, + Err(ZeroCopyError::InsufficientMemoryAllocated(0, 4)) + ); + } + + #[test] + fn test_rawpointer_from_bytes_success() { + let mut buffer = [0u8; 4]; + let value = 42u32; + + // Write value to buffer + unsafe { *(buffer.as_ptr() as *mut u32) = value }; + + let pointer: WrappedPointer = WrappedPointer::from_bytes(&mut buffer).unwrap(); + assert_eq!(*pointer.get(), value); + } + + #[test] + fn test_rawpointer_from_bytes_insufficient_memory() { + let value = 42u32; + let mut buffer = value.to_le_bytes(); + + let result = WrappedPointer::::from_bytes(&mut buffer[0..2]); + assert_eq!( + result, + Err(ZeroCopyError::InsufficientMemoryAllocated(2, 4)) + ); + } + + #[test] + fn test_rawpointer_from_bytes_at_success() { + let mut buffer = [0u8; 8]; + let value = 42u32; + let mut offset = 4; + // Write value to buffer + unsafe { *(buffer[offset..].as_ptr() as *mut u32) = value }; + + let pointer: WrappedPointer = + WrappedPointer::from_bytes_at(&mut buffer, &mut offset).unwrap(); + assert_eq!(*pointer.get(), value); + assert_eq!(offset, 8); + } + + #[test] + fn test_rawpointer_from_bytes_with_discriminator_success() { + let mut buffer = [0u8; 12]; + let value = 42u32; + + // Write discriminator and value + buffer[..8].copy_from_slice(&1u64.to_le_bytes()); // Fake discriminator + unsafe { *(buffer[8..].as_ptr() as *mut u32) = value }; + + let pointer = WrappedPointer::::from_bytes_with_discriminator(&mut buffer).unwrap(); + assert_eq!(*pointer.get(), value); + } + + #[test] + #[should_panic(expected = "out of range for slice of length")] + fn test_rawpointer_from_bytes_with_discriminator_fail() { + let mut buffer = [0u8; 7]; // Not enough space for discriminator + let result = WrappedPointer::::from_bytes_with_discriminator(&mut buffer); + assert_eq!( + result, + Err(ZeroCopyError::InsufficientMemoryAllocated(7, 8)) + ); + } + + #[test] + fn test_rawpointer_from_bytes_with_discriminator_insufficient_memory() { + let mut buffer = [0u8; 9]; + let result = WrappedPointer::::from_bytes_with_discriminator(&mut buffer); + assert_eq!( + result, + Err(ZeroCopyError::InsufficientMemoryAllocated(1, 4)) + ); + } + + #[test] + fn test_rawpointer_deref_success() { + let mut buffer = [0u8; 8]; + let value = 42u32; + + let pointer = WrappedPointer::new(value, &mut buffer).unwrap(); + assert_eq!(*pointer, value); + } + + #[test] + fn test_size() { + let pointer = WrappedPointer::::new(42, &mut [0u8; 4]).unwrap(); + assert_eq!(pointer.size(), size_of::()); + } +} diff --git a/program-libs/zero-copy/src/wrapped_pointer_mut.rs b/program-libs/zero-copy/src/wrapped_pointer_mut.rs new file mode 100644 index 0000000000..5fabeed0ee --- /dev/null +++ b/program-libs/zero-copy/src/wrapped_pointer_mut.rs @@ -0,0 +1,308 @@ +use core::fmt; +use std::{ + fmt::{Debug, Formatter}, + marker::PhantomData, + mem::{size_of, ManuallyDrop}, + ops::{Deref, DerefMut}, +}; + +use crate::{check_alignment, check_size, errors::ZeroCopyError}; + +pub struct WrappedPointerMut +where + T: Copy, +{ + ptr: ManuallyDrop<*mut T>, + _marker: PhantomData, +} + +impl WrappedPointerMut +where + T: Copy, +{ + pub fn new(value: T, data: &mut [u8]) -> Result { + check_size::(data)?; + let ptr = data.as_mut_ptr() as *mut T; + check_alignment(ptr)?; + unsafe { + ptr.write(value); + } + + unsafe { Self::from_raw_parts(ptr) } + } + + pub fn new_at(value: T, data: &mut [u8], offset: &mut usize) -> Result { + let new = Self::new(value, &mut data[*offset..])?; + *offset += new.size(); + Ok(new) + } + + /// Create a new `WrappedPointerMut` from a raw pointer. + /// # Safety + /// This function is unsafe because it creates a `WrappedPointerMut` from a raw pointer. + /// The caller must ensure that the pointer is valid and properly aligned. + pub unsafe fn from_raw_parts(ptr: *mut T) -> Result { + Ok(WrappedPointerMut { + ptr: ManuallyDrop::new(ptr), + _marker: PhantomData, + }) + } + + pub fn size(&self) -> usize { + size_of::() + } + + pub fn get(&self) -> &T { + unsafe { &**self.ptr } + } + + pub fn get_mut(&mut self) -> &mut T { + unsafe { &mut **self.ptr } + } + + pub fn from_bytes(bytes: &mut [u8]) -> Result { + check_size::(bytes)?; + let ptr = bytes.as_mut_ptr() as *mut T; + check_alignment(ptr)?; + unsafe { Self::from_raw_parts(ptr) } + } + + pub fn from_bytes_at(bytes: &mut [u8], offset: &mut usize) -> Result { + let new = Self::from_bytes(&mut bytes[*offset..])?; + *offset += new.size(); + Ok(new) + } + + pub fn from_bytes_with_discriminator(bytes: &mut [u8]) -> Result { + let mut offset = crate::DISCRIMINATOR_LEN; + Self::from_bytes_at(bytes, &mut offset) + } + + pub fn as_mut_ptr(&mut self) -> *mut T { + *self.ptr + } + + pub fn as_ptr(&self) -> *const T { + *self.ptr as *const T + } +} + +impl PartialEq for WrappedPointerMut +where + T: Copy + PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + *self.get() == *other.get() + } +} + +impl Debug for WrappedPointerMut +where + T: Copy + Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self.get()) + } +} + +impl Deref for WrappedPointerMut +where + T: Copy, +{ + type Target = T; + + fn deref(&self) -> &Self::Target { + self.get() + } +} + +impl DerefMut for WrappedPointerMut +where + T: Copy, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + self.get_mut() + } +} + +/// Test coverage: +/// 1. Test `WrappedPointerMut::new` success +/// 2. Test `WrappedPointerMut::new` with unaligned pointer +/// 3. Test `WrappedPointerMut::new` with insufficient space +/// 4. Test `WrappedPointerMut::new_at` success +/// 5. Test `WrappedPointerMut::new_at` with out of bounds +/// 6. Test `WrappedPointerMut::new_at` with insufficient memory +/// 7. Test `WrappedPointerMut::from_bytes` with success +/// 8. Test `WrappedPointerMut::from_bytes` with insufficient memory +/// 9. Test `WrappedPointerMut::from_bytes_at` with success +/// 10. Test `WrappedPointerMut::from_bytes_with_discriminator` with success +/// 11. Test `WrappedPointerMut::from_bytes_with_discriminator` with insufficient memory (out of bounds) +/// 11. Test `WrappedPointerMut::from_bytes_with_discriminator` with insufficient memory (value) +/// 12. Test `WrappedPointerMut::deref` success +/// 13. Test `WrappedPointerMut::size` success +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_rawmutpointer_new_success() { + let mut buffer = [0u8; 16]; + let value = 42u32; + + let pointer = WrappedPointerMut::new(value, &mut buffer).unwrap(); + assert_eq!(*pointer.get(), value); + assert_eq!(buffer[0..4], value.to_le_bytes()); + assert_eq!(buffer[4..16], [0u8; 12]); + } + + #[test] + fn test_rawmutpointer_new_unaligned() { + let mut buffer = [0u8; 5]; + let value = 42u32; + + let result = WrappedPointerMut::new(value, &mut buffer[1..]); + assert_eq!(result, Err(ZeroCopyError::UnalignedPointer)); + } + + #[test] + fn test_rawmutpointer_new_insufficient_space() { + let mut buffer = [0u8; 3]; + let value = 42u32; + + let result = WrappedPointerMut::new(value, &mut buffer); + assert_eq!( + result, + Err(ZeroCopyError::InsufficientMemoryAllocated(3, 4)) + ); + } + + #[test] + fn test_rawmutpointer_new_at_success() { + let mut buffer = [0u8; 16]; + let mut offset = 4; + let value = 42u32; + + let pointer = WrappedPointerMut::new_at(value, &mut buffer, &mut offset).unwrap(); + assert_eq!(*pointer.get(), value); + assert_eq!(offset, 8); // Size of u32 + assert_eq!(buffer[0..4], [0u8; 4]); + assert_eq!(buffer[4..8], value.to_le_bytes()); + assert_eq!(buffer[8..16], [0u8; 8]); + } + + #[test] + #[should_panic(expected = "out of range for slice of")] + fn test_rawmutpointer_new_at_out_of_bounds() { + let mut buffer = [0u8; 4]; + let mut offset = 5; + let value = 42u32; + + WrappedPointerMut::new_at(value, &mut buffer, &mut offset).unwrap(); + } + + #[test] + fn test_rawmutpointer_new_at_insufficient_memory() { + let mut buffer = [0u8; 4]; + let mut offset = 4; + let value = 42u32; + + let result = WrappedPointerMut::new_at(value, &mut buffer, &mut offset); + assert_eq!( + result, + Err(ZeroCopyError::InsufficientMemoryAllocated(0, 4)) + ); + } + + #[test] + fn test_rawmutpointer_from_bytes_success() { + let mut buffer = [0u8; 4]; + let value = 42u32; + + // Write value to buffer + unsafe { *(buffer.as_mut_ptr() as *mut u32) = value }; + + let pointer: WrappedPointerMut = WrappedPointerMut::from_bytes(&mut buffer).unwrap(); + assert_eq!(*pointer.get(), value); + } + + #[test] + fn test_rawmutpointer_from_bytes_insufficient_memory() { + let value = 42u32; + let mut buffer = value.to_le_bytes(); + + let result = WrappedPointerMut::::from_bytes(&mut buffer[0..2]); + assert_eq!( + result, + Err(ZeroCopyError::InsufficientMemoryAllocated(2, 4)) + ); + } + + #[test] + fn test_rawmutpointer_from_bytes_at_success() { + let mut buffer = [0u8; 8]; + let value = 42u32; + let mut offset = 4; + // Write value to buffer + unsafe { *(buffer[offset..].as_mut_ptr() as *mut u32) = value }; + + let pointer: WrappedPointerMut = + WrappedPointerMut::from_bytes_at(&mut buffer, &mut offset).unwrap(); + assert_eq!(*pointer.get(), value); + assert_eq!(offset, 8); + } + + #[test] + fn test_rawmutpointer_from_bytes_with_discriminator_success() { + let mut buffer = [0u8; 12]; + let value = 42u32; + + // Write discriminator and value + buffer[..8].copy_from_slice(&1u64.to_le_bytes()); // Fake discriminator + unsafe { *(buffer[8..].as_mut_ptr() as *mut u32) = value }; + + let mut pointer = + WrappedPointerMut::::from_bytes_with_discriminator(&mut buffer).unwrap(); + assert_eq!(*pointer.get(), value); + assert_eq!(*pointer.get_mut(), value); + } + + #[test] + #[should_panic(expected = "out of range for slice of length")] + fn test_rawmutpointer_from_bytes_with_discriminator_fail() { + let mut buffer = [0u8; 7]; // Not enough space for discriminator + let result = WrappedPointerMut::::from_bytes_with_discriminator(&mut buffer); + assert_eq!( + result, + Err(ZeroCopyError::InsufficientMemoryAllocated(7, 8)) + ); + } + + #[test] + fn test_rawmutpointer_from_bytes_with_discriminator_insufficient_memory() { + let mut buffer = [0u8; 9]; + let result = WrappedPointerMut::::from_bytes_with_discriminator(&mut buffer); + assert_eq!( + result, + Err(ZeroCopyError::InsufficientMemoryAllocated(1, 4)) + ); + } + + #[test] + fn test_rawmutpointer_deref_success() { + let mut buffer = [0u8; 8]; + let value = 42u32; + + let mut pointer = WrappedPointerMut::new(value, &mut buffer).unwrap(); + assert_eq!(*pointer, value); + + // Update value via mutable dereference + *pointer = 84; + assert_eq!(*pointer, 84); + } + + #[test] + fn test_size() { + let pointer = WrappedPointerMut::::new(42, &mut [0u8; 4]).unwrap(); + assert_eq!(pointer.size(), size_of::()); + } +} diff --git a/program-libs/zero-copy/tests/cyclic_vec_tests.rs b/program-libs/zero-copy/tests/cyclic_vec_tests.rs new file mode 100644 index 0000000000..2f2c74c99c --- /dev/null +++ b/program-libs/zero-copy/tests/cyclic_vec_tests.rs @@ -0,0 +1,705 @@ +use light_zero_copy::{ + cyclic_vec::{ZeroCopyCyclicVec, ZeroCopyCyclicVecUsize}, + errors::ZeroCopyError, +}; +use rand::{thread_rng, Rng}; + +#[test] +fn test_cyclic_bounded_vec_with_capacity() { + for capacity in 0..1024 { + let mut data = vec![0; ZeroCopyCyclicVecUsize::::required_size_for_capacity(capacity)]; + let mut cyclic_bounded_vec = + ZeroCopyCyclicVecUsize::::new(capacity, &mut data).unwrap(); + + assert_eq!(cyclic_bounded_vec.capacity(), capacity); + assert_eq!(cyclic_bounded_vec.len(), 0); + assert_eq!(cyclic_bounded_vec.first_index(), 0); + assert_eq!(cyclic_bounded_vec.last_index(), 0); + assert_eq!(cyclic_bounded_vec.as_slice(), &[]); + assert_eq!(cyclic_bounded_vec.as_mut_slice(), &mut []); + } +} + +#[test] +fn test_cyclic_bounded_vec_is_empty() { + let mut rng = thread_rng(); + let capacity = 1000; + let mut data = vec![0; ZeroCopyCyclicVecUsize::::required_size_for_capacity(capacity)]; + let mut vec = ZeroCopyCyclicVecUsize::::new(capacity, &mut data).unwrap(); + + assert!(vec.is_empty()); + let mut ref_vec = Vec::new(); + for _ in 0..1000 { + let element = rng.gen(); + vec.push(element); + ref_vec.push(element); + + assert!(!vec.is_empty()); + } + assert_eq!(vec.as_slice(), ref_vec.as_slice()); + assert_eq!(vec.as_mut_slice(), ref_vec.as_mut_slice()); + let array: [u32; 1000] = vec.try_into_array().unwrap(); + assert_eq!(array, <[u32; 1000]>::try_from(ref_vec).unwrap()); +} + +#[test] +fn test_cyclic_bounded_vec_get() { + let capacity = 1000; + let mut data = vec![0; ZeroCopyCyclicVecUsize::::required_size_for_capacity(capacity)]; + let mut vec = ZeroCopyCyclicVecUsize::::new(capacity, &mut data).unwrap(); + + for i in 0..1000 { + vec.push(i); + } + + for i in 0..1000 { + assert_eq!(vec.get(i), Some(&i)); + } + for i in 1000..10_000 { + assert!(vec.get(i).is_none()); + } +} + +#[test] +fn test_cyclic_bounded_vec_get_mut() { + let capacity = 1000; + let mut data = vec![0; ZeroCopyCyclicVecUsize::::required_size_for_capacity(capacity)]; + let mut vec = ZeroCopyCyclicVecUsize::::new(capacity, &mut data).unwrap(); + + for i in 0..2000 { + vec.push(i); + } + + for i in 0..1000 { + let element = vec.get_mut(i).unwrap(); + assert_eq!(*element, 1000 + i); + *element = i * 2; + } + for i in 0..1000 { + assert_eq!(vec.get_mut(i), Some(&mut (i * 2))); + } + for i in 1000..10_000 { + assert!(vec.get_mut(i).is_none()); + } +} + +#[test] +fn test_cyclic_bounded_vec_first() { + let capacity = 500; + let mut data = vec![0; ZeroCopyCyclicVecUsize::::required_size_for_capacity(capacity)]; + let mut vec = ZeroCopyCyclicVecUsize::::new(capacity, &mut data).unwrap(); + + assert!(vec.first().is_none()); + + for i in 0..1000 { + vec.push(i); + assert_eq!(vec.last(), Some(&i)); + assert_eq!(vec.last_mut(), Some(&mut i.clone())); + assert_eq!(vec.first(), Some(&((i).saturating_sub(499)))); + assert_eq!(vec.first_mut(), Some(&mut ((i).saturating_sub(499)))); + } +} + +#[test] +fn test_cyclic_bounded_vec_last() { + let mut rng = thread_rng(); + let capacity = 500; + let mut data = vec![0; ZeroCopyCyclicVecUsize::::required_size_for_capacity(capacity)]; + let mut vec = ZeroCopyCyclicVecUsize::::new(capacity, &mut data).unwrap(); + + assert!(vec.last().is_none()); + + for _ in 0..1000 { + let element = rng.gen(); + vec.push(element); + + assert_eq!(vec.last(), Some(&element)); + } +} + +#[test] +fn test_cyclic_bounded_vec_last_mut() { + let mut rng = thread_rng(); + let capacity = 500; + let mut data = vec![0; ZeroCopyCyclicVecUsize::::required_size_for_capacity(capacity)]; + let mut vec = ZeroCopyCyclicVecUsize::::new(capacity, &mut data).unwrap(); + + assert!(vec.last_mut().is_none()); + + for i in 0..1000 { + let element_old = rng.gen(); + vec.push(element_old); + println!("last index {:?}", vec.last_index()); + println!("i: {}", i); + let element_ref = vec.last_mut().unwrap(); + assert_eq!(*element_ref, element_old); + + // Assign a new value. + let element_new = rng.gen(); + *element_ref = element_new; + + // Assert that it took the effect. + let element_ref = vec.last_mut().unwrap(); + assert_eq!(*element_ref, element_new); + } +} + +#[test] +fn test_cyclic_bounded_vec_manual() { + let capacity = 8; + let mut data = vec![0; ZeroCopyCyclicVecUsize::::required_size_for_capacity(capacity)]; + let mut cyclic_bounded_vec = ZeroCopyCyclicVecUsize::::new(capacity, &mut data).unwrap(); + + // Fill up the cyclic vector. + // + // ``` + // ^ $ + // index [0, 1, 2, 3, 4, 5, 6, 7] + // value [0, 1, 2, 3, 4, 5, 6, 7] + // ``` + // + // * `^` - first element + // * `$` - last element + for i in 0..8 { + cyclic_bounded_vec.push(i); + } + assert_eq!(cyclic_bounded_vec.first_index(), 0); + assert_eq!(cyclic_bounded_vec.last_index(), 7); + assert_eq!( + cyclic_bounded_vec.iter().collect::>().as_slice(), + &[&0, &1, &2, &3, &4, &5, &6, &7] + ); + + // Overwrite half of values. + // + // ``` + // $ ^ + // index [0, 1, 2, 3, 4, 5, 6, 7] + // value [8, 9, 10, 11, 4, 5, 6, 7] + // ``` + // + // * `^` - first element + // * `$` - last element + for i in 0..4 { + cyclic_bounded_vec.push(i + 8); + } + assert_eq!(cyclic_bounded_vec.first_index(), 4); + assert_eq!(cyclic_bounded_vec.last_index(), 3); + assert_eq!( + cyclic_bounded_vec.iter().collect::>().as_slice(), + &[&4, &5, &6, &7, &8, &9, &10, &11] + ); + + // Overwrite even more. + // + // ``` + // $ ^ + // index [0, 1, 2, 3, 4, 5, 6, 7] + // value [8, 9, 10, 11, 12, 13, 6, 7] + // ``` + // + // * `^` - first element + // * `$` - last element + for i in 0..2 { + cyclic_bounded_vec.push(i + 12); + } + assert_eq!(cyclic_bounded_vec.first_index(), 6); + assert_eq!(cyclic_bounded_vec.last_index(), 5); + assert_eq!( + cyclic_bounded_vec + .iter() + .cloned() + .collect::>() + .as_slice(), + &[6, 7, 8, 9, 10, 11, 12, 13] + ); + + // Overwrite all values from the first loop. + // + // ``` + // ^ $ + // index [0, 1, 2, 3, 4, 5, 6, 7] + // value [8, 9, 10, 11, 12, 13, 14, 15] + // ``` + // + // * `^` - first element + // * `$` - last element + for i in 0..2 { + cyclic_bounded_vec.push(i + 14); + } + assert_eq!(cyclic_bounded_vec.first_index(), 0); + assert_eq!(cyclic_bounded_vec.last_index(), 7); + assert_eq!( + cyclic_bounded_vec + .iter() + .cloned() + .collect::>() + .as_slice(), + &[8, 9, 10, 11, 12, 13, 14, 15] + ); +} + +/// Iteration on a vector with one element. +/// +/// ``` +/// ^$ +/// index [0] +/// value [0] +/// ``` +/// +/// * `^` - first element +/// * `$` - last element +/// * `#` - visited elements +/// +/// Length: 1 +/// Capacity: 8 +/// First index: 0 +/// Last index: 0 +/// +/// Start iteration from: 0 +/// +/// Should iterate over one element. +#[test] +fn test_cyclic_bounded_vec_iter_one_element() { + let capacity = 8; + let mut data = vec![0; ZeroCopyCyclicVecUsize::::required_size_for_capacity(capacity)]; + let mut cyclic_bounded_vec = ZeroCopyCyclicVecUsize::::new(capacity, &mut data).unwrap(); + + cyclic_bounded_vec.push(0); + + assert_eq!(cyclic_bounded_vec.len(), 1); + assert_eq!(cyclic_bounded_vec.capacity(), 8); + assert_eq!(cyclic_bounded_vec.first_index(), 0); + assert_eq!(cyclic_bounded_vec.last_index(), 0); + + let elements = cyclic_bounded_vec.iter().collect::>(); + assert_eq!(elements.len(), 1); + assert_eq!(elements.as_slice(), &[&0]); + + let elements = cyclic_bounded_vec.iter_from(0).unwrap().collect::>(); + assert_eq!(elements.len(), 1); + assert_eq!(elements.as_slice(), &[&0]); +} + +/// Iteration without reset in a vector which is not full. +/// +/// ``` +/// # # # # +/// ^ $ +/// index [0, 1, 2, 3, 4, 5] +/// value [0, 1, 2, 3, 4, 5] +/// ``` +/// +/// * `^` - first element +/// * `$` - last element +/// * `#` - visited elements +/// +/// Length: 6 +/// Capacity: 8 +/// First index: 0 +/// Last index: 5 +/// +/// Start iteration from: 2 +/// +/// Should iterate over elements from 2 to 5, with 4 iterations. +#[test] +fn test_cyclic_bounded_vec_iter_from_without_reset_not_full_6_8_4() { + let capacity = 8; + let mut data = vec![0; ZeroCopyCyclicVecUsize::::required_size_for_capacity(capacity)]; + let mut cyclic_bounded_vec = ZeroCopyCyclicVecUsize::::new(capacity, &mut data).unwrap(); + + for i in 0..6 { + cyclic_bounded_vec.push(i); + } + + assert_eq!(cyclic_bounded_vec.len(), 6); + assert_eq!(cyclic_bounded_vec.capacity(), 8); + assert_eq!(cyclic_bounded_vec.first_index(), 0); + assert_eq!(cyclic_bounded_vec.last_index(), 5); + + let elements = cyclic_bounded_vec.iter_from(2).unwrap().collect::>(); + assert_eq!(elements.len(), 4); + assert_eq!(elements.as_slice(), &[&2, &3, &4, &5]); +} +/// Iteration without reset in a vector which is full. +/// +/// ``` +/// # # # +/// ^ $ +/// index [0, 1, 2, 3, 4] +/// value [0, 1, 2, 3, 4] +/// ``` +/// +/// * `^` - first element +/// * `$` - last element +/// * `#` - visited elements +/// +/// Length: 5 +/// Capacity: 5 +/// First index: 0 +/// Last index: 4 +/// +/// Start iteration from: 2 +/// +/// Should iterate over elements 2..4 - 3 iterations. +#[test] +fn test_cyclic_bounded_vec_iter_from_without_reset_not_full_5_5_4() { + let capacity = 5; + let mut data = vec![0; ZeroCopyCyclicVecUsize::::required_size_for_capacity(capacity)]; + let mut cyclic_bounded_vec = ZeroCopyCyclicVecUsize::::new(capacity, &mut data).unwrap(); + + for i in 0..5 { + cyclic_bounded_vec.push(i); + } + + assert_eq!(cyclic_bounded_vec.len(), 5); + assert_eq!(cyclic_bounded_vec.capacity(), 5); + assert_eq!(cyclic_bounded_vec.first_index(), 0); + assert_eq!(cyclic_bounded_vec.last_index(), 4); + + let elements = cyclic_bounded_vec.iter_from(2).unwrap().collect::>(); + assert_eq!(elements.len(), 3); + assert_eq!(elements.as_slice(), &[&2, &3, &4]); +} + +/// Iteration without reset in a vector which is full. +/// +/// ``` +/// # # # # # # +/// ^ $ +/// index [0, 1, 2, 3, 4, 5, 6, 7] +/// value [0, 1, 2, 3, 4, 5, 6, 7] +/// ``` +/// +/// * `^` - first element +/// * `$` - last element +/// * `#` - visited elements +/// +/// Length: 8 +/// Capacity: 8 +/// First index: 0 +/// Last index: 7 +/// +/// Start iteration from: 2 +/// +/// Should iterate over elements 2..7 - 6 iterations. +#[test] +fn test_cyclic_bounded_vec_iter_from_without_reset_full_8_8_6() { + let capacity = 8; + let mut data = vec![0; ZeroCopyCyclicVecUsize::::required_size_for_capacity(capacity)]; + let mut cyclic_bounded_vec = ZeroCopyCyclicVecUsize::::new(capacity, &mut data).unwrap(); + + for i in 0..8 { + cyclic_bounded_vec.push(i); + } + + assert_eq!(cyclic_bounded_vec.len(), 8); + assert_eq!(cyclic_bounded_vec.capacity(), 8); + assert_eq!(cyclic_bounded_vec.first_index(), 0); + assert_eq!(cyclic_bounded_vec.last_index(), 7); + + let elements = cyclic_bounded_vec.iter_from(2).unwrap().collect::>(); + assert_eq!(elements.len(), 6); + assert_eq!(elements.as_slice(), &[&2, &3, &4, &5, &6, &7]); +} + +/// Iteration with reset. +/// +/// Insert elements over capacity, so the vector resets and starts +/// overwriting elements from the start - 12 elements into a vector with +/// capacity 8. +/// +/// The resulting data structure looks like: +/// +/// ``` +/// # # # # # # +/// $ ^ +/// index [0, 1, 2, 3, 4, 5, 6, 7] +/// value [8, 9, 10, 11, 4, 5, 6, 7] +/// ``` +/// +/// * `^` - first element +/// * `$` - last element +/// * `#` - visited elements +/// +/// Length: 8 +/// Capacity: 8 +/// First: 4 +/// Last: 3 +/// +/// Start iteration from: 6 +/// +/// Should iterate over elements 6..7 and 8..11 - 6 iterations. +#[test] +fn test_cyclic_bounded_vec_iter_from_reset() { + let capacity = 8; + let mut data = vec![0; ZeroCopyCyclicVecUsize::::required_size_for_capacity(capacity)]; + let mut cyclic_bounded_vec = ZeroCopyCyclicVecUsize::::new(capacity, &mut data).unwrap(); + + for i in 0..12 { + cyclic_bounded_vec.push(i); + } + + assert_eq!(cyclic_bounded_vec.len(), 8); + assert_eq!(cyclic_bounded_vec.capacity(), 8); + assert_eq!(cyclic_bounded_vec.first_index(), 4); + assert_eq!(cyclic_bounded_vec.last_index(), 3); + + let elements = cyclic_bounded_vec.iter_from(6).unwrap().collect::>(); + assert_eq!(elements.len(), 6); + assert_eq!(elements.as_slice(), &[&6, &7, &8, &9, &10, &11]); +} + +#[test] +fn test_cyclic_bounded_vec_iter_from_out_of_bounds_not_full() { + let capacity = 8; + let mut data = vec![0; ZeroCopyCyclicVecUsize::::required_size_for_capacity(capacity)]; + let mut cyclic_bounded_vec = ZeroCopyCyclicVecUsize::::new(capacity, &mut data).unwrap(); + + for i in 0..4 { + cyclic_bounded_vec.push(i); + } + + // Try `start` values in bounds. + for i in 0..4 { + let elements = cyclic_bounded_vec.iter_from(i).unwrap().collect::>(); + assert_eq!(elements.len(), 4 - i); + let expected = (i..4).collect::>(); + // Just to coerce it to have references... + let expected = expected.iter().collect::>(); + assert_eq!(elements.as_slice(), expected.as_slice()); + } + + // Try `start` values out of bounds. + for i in 4..1000 { + let elements = cyclic_bounded_vec.iter_from(i); + assert!(matches!(elements, Err(ZeroCopyError::IterFromOutOfBounds))); + } +} + +#[test] +fn test_cyclic_bounded_vec_iter_from_out_of_bounds_full() { + let capacity = 8; + let mut data = vec![0; ZeroCopyCyclicVecUsize::::required_size_for_capacity(capacity)]; + let mut cyclic_bounded_vec = ZeroCopyCyclicVecUsize::::new(capacity, &mut data).unwrap(); + + for i in 0..12 { + cyclic_bounded_vec.push(i); + } + + // Try different `start` values which are out of bounds. + for start in 8..1000 { + let elements = cyclic_bounded_vec.iter_from(start); + assert!(matches!(elements, Err(ZeroCopyError::IterFromOutOfBounds))); + } +} + +#[test] +fn test_cyclic_bounded_vec_iter_from_out_of_bounds_iter_from() { + let capacity = 8; + let mut data = vec![0; ZeroCopyCyclicVecUsize::::required_size_for_capacity(capacity)]; + let mut cyclic_bounded_vec = ZeroCopyCyclicVecUsize::::new(capacity, &mut data).unwrap(); + + for i in 0..8 { + assert!(matches!( + cyclic_bounded_vec.iter_from(i), + Err(ZeroCopyError::IterFromOutOfBounds) + )); + cyclic_bounded_vec.push(i); + } +} + +#[test] +fn test_cyclic_bounded_vec_overwrite() { + let capacity = 64; + let mut data = vec![0; ZeroCopyCyclicVecUsize::::required_size_for_capacity(capacity)]; + let mut cyclic_bounded_vec = ZeroCopyCyclicVecUsize::::new(capacity, &mut data).unwrap(); + + for i in 0..256 { + cyclic_bounded_vec.push(i); + } + + assert_eq!(cyclic_bounded_vec.len(), 64); + assert_eq!(cyclic_bounded_vec.capacity(), 64); + assert_eq!( + cyclic_bounded_vec.iter().collect::>().as_slice(), + &[ + &192, &193, &194, &195, &196, &197, &198, &199, &200, &201, &202, &203, &204, &205, + &206, &207, &208, &209, &210, &211, &212, &213, &214, &215, &216, &217, &218, &219, + &220, &221, &222, &223, &224, &225, &226, &227, &228, &229, &230, &231, &232, &233, + &234, &235, &236, &237, &238, &239, &240, &241, &242, &243, &244, &245, &246, &247, + &248, &249, &250, &251, &252, &253, &254, &255 + ] + ); +} + +#[test] +fn test_clear_pass() { + let capacity = 5; + let mut data = vec![0; ZeroCopyCyclicVecUsize::::required_size_for_capacity(capacity)]; + let mut vec = ZeroCopyCyclicVecUsize::::new(capacity, &mut data).unwrap(); + + vec.push(1); + vec.push(2); + vec.clear(); + assert_eq!(vec.len(), 0); + assert!(vec.get(0).is_none()); + assert!(vec.get(1).is_none()); +} + +#[test] +fn test_deserialize_pass() { + let mut account_data = vec![0u8; ZeroCopyCyclicVecUsize::::required_size_for_capacity(4)]; + let mut offset = 0; + + // Initialize data with valid ZeroCopyCyclicVecUsize metadata and elements + ZeroCopyCyclicVecUsize::::new_at(4, &mut account_data, &mut offset).unwrap(); + offset = 0; + + // Deserialize the ZeroCopyCyclicVecUsize + let deserialized_vec = + ZeroCopyCyclicVecUsize::::from_bytes_at(&mut account_data, &mut offset) + .expect("Failed to deserialize ZeroCopyCyclicVecUsize"); + + assert_eq!(deserialized_vec.capacity(), 4); + assert_eq!(deserialized_vec.len(), 0); + assert_eq!(offset, account_data.len()); +} + +#[test] +fn test_deserialize_multiple_pass() { + let mut account_data = + vec![0u8; ZeroCopyCyclicVecUsize::::required_size_for_capacity(4) * 2]; + let mut offset = 0; + + // Initialize data for multiple ZeroCopyCyclicVecs + ZeroCopyCyclicVecUsize::::new_at(4, &mut account_data, &mut offset).unwrap(); + ZeroCopyCyclicVecUsize::::new_at(4, &mut account_data, &mut offset).unwrap(); + offset = 0; + // Deserialize multiple ZeroCopyCyclicVecs + let deserialized_vecs = + ZeroCopyCyclicVecUsize::::from_bytes_at_multiple(2, &mut account_data, &mut offset) + .expect("Failed to deserialize multiple ZeroCopyCyclicVecs"); + + assert_eq!(deserialized_vecs.len(), 2); + for vec in deserialized_vecs.iter() { + assert_eq!(vec.capacity(), 4); + assert_eq!(vec.len(), 0); + assert_eq!(vec.to_vec(), vec![]); + } +} + +#[test] +fn test_init_pass() { + let mut account_data = vec![0u8; 64]; + let mut offset = 0; + + // Initialize a ZeroCopyCyclicVecUsize with capacity 4 + let mut vec = ZeroCopyCyclicVecUsize::::new_at(4, &mut account_data, &mut offset).unwrap(); + assert_eq!(vec.capacity(), 4); + assert_eq!(vec.len(), 0); + for i in 0..4 { + assert!(vec.get(i).is_none()); + vec.push(i as u64); + assert_eq!(*vec.get(i).unwrap(), i as u64); + assert!(vec.len() == i + 1); + } +} + +#[test] +fn test_init_multiple_pass() { + let mut account_data = vec![0u8; 128]; + let mut offset = 0; + let mut initialized_vecs = + ZeroCopyCyclicVecUsize::::new_at_multiple(2, 4, &mut account_data, &mut offset) + .unwrap(); + + assert_eq!(initialized_vecs.len(), 2); + assert_eq!(initialized_vecs[0].capacity(), 4); + assert_eq!(initialized_vecs[1].capacity(), 4); + assert_eq!(initialized_vecs[0].len(), 0); + assert_eq!(initialized_vecs[1].len(), 0); + for i in 0..4 { + for vec in initialized_vecs.iter_mut() { + assert!(vec.get(i).is_none()); + vec.push(i as u64); + assert_eq!(*vec.get(i).unwrap(), i as u64); + assert!(vec.len() == i + 1); + } + } +} + +#[test] +fn test_metadata_size() { + assert_eq!(ZeroCopyCyclicVec::::metadata_size(), 1); + assert_eq!(ZeroCopyCyclicVec::::metadata_size(), 2); + assert_eq!(ZeroCopyCyclicVec::::metadata_size(), 4); + assert_eq!(ZeroCopyCyclicVec::::metadata_size(), 8); + assert_eq!(ZeroCopyCyclicVec::::metadata_size(), 8); + + assert_eq!(ZeroCopyCyclicVec::::metadata_size(), 2); + assert_eq!(ZeroCopyCyclicVec::::metadata_size(), 2); + assert_eq!(ZeroCopyCyclicVec::::metadata_size(), 4); + assert_eq!(ZeroCopyCyclicVec::::metadata_size(), 8); + assert_eq!(ZeroCopyCyclicVec::::metadata_size(), 8); + + assert_eq!(ZeroCopyCyclicVec::::metadata_size(), 4); + assert_eq!(ZeroCopyCyclicVec::::metadata_size(), 4); + assert_eq!(ZeroCopyCyclicVec::::metadata_size(), 4); + assert_eq!(ZeroCopyCyclicVec::::metadata_size(), 8); + assert_eq!(ZeroCopyCyclicVec::::metadata_size(), 8); + + assert_eq!(ZeroCopyCyclicVec::::metadata_size(), 8); + assert_eq!(ZeroCopyCyclicVec::::metadata_size(), 8); + assert_eq!(ZeroCopyCyclicVec::::metadata_size(), 8); + assert_eq!(ZeroCopyCyclicVec::::metadata_size(), 8); + assert_eq!(ZeroCopyCyclicVec::::metadata_size(), 8); +} + +#[test] +fn test_data_size() { + assert_eq!(ZeroCopyCyclicVec::::data_size(64), 66); +} + +#[test] +fn test_required_size() { + // current index + length + capacity + data + assert_eq!( + ZeroCopyCyclicVec::::required_size_for_capacity(64), + 1 + 1 + 1 + 64 + ); + // current index + length + capacity + data + assert_eq!( + ZeroCopyCyclicVec::::required_size_for_capacity(64), + 8 + 8 + 8 + 8 * 64 + ); +} + +#[test] +fn test_partial_eq() { + let mut account_data = vec![0u8; ZeroCopyCyclicVecUsize::::required_size_for_capacity(4)]; + let mut offset = 0; + let mut vec = ZeroCopyCyclicVecUsize::::new_at(4, &mut account_data, &mut offset).unwrap(); + for i in 0..5 { + vec.push(i as u64 % 4); + } + + let mut account_data = vec![0u8; ZeroCopyCyclicVecUsize::::required_size_for_capacity(4)]; + let mut offset = 0; + let mut vec2 = + ZeroCopyCyclicVecUsize::::new_at(4, &mut account_data, &mut offset).unwrap(); + for i in 0..4 { + vec2.push(i as u64); + } + + // assert that current index is included in equality check + // -> values are the same but current index is different + assert_ne!(vec, vec2); + assert_eq!(vec.as_slice(), vec2.as_slice()); + + vec.clear(); + for i in 0..4 { + vec.push(i as u64); + } + assert_eq!(vec, vec2); +} diff --git a/program-libs/zero-copy/tests/slice_mut_test.rs b/program-libs/zero-copy/tests/slice_mut_test.rs new file mode 100644 index 0000000000..5ed5c8d784 --- /dev/null +++ b/program-libs/zero-copy/tests/slice_mut_test.rs @@ -0,0 +1,499 @@ +use core::fmt::Debug; + +use light_zero_copy::{ + add_padding, + errors::ZeroCopyError, + slice_mut::{ZeroCopySliceMut, ZeroCopySliceMutUsize}, +}; +use num_traits::{FromPrimitive, PrimInt, ToPrimitive}; +use rand::{distributions::Standard, prelude::*}; + +fn test_zero_copy_slice_mut_new(length: LEN) +where + LEN: ToPrimitive + FromPrimitive + PrimInt + num_traits::ToBytes, + T: Copy + Clone + PartialEq + Debug + Default, + Standard: Distribution, +{ + let mut rng = thread_rng(); + let mut data = vec![0; ZeroCopySliceMut::::required_size_for_capacity(length)]; + ZeroCopySliceMut::::new(length, &mut data).unwrap(); + + // Test from_bytes with a zeroed slice + { + let reference_vec = vec![T::default(); length.to_usize().unwrap()]; + let slice = ZeroCopySliceMut::::from_bytes(&mut data).unwrap(); + // 1. Validate length + assert_eq!(slice.len(), reference_vec.len()); + { + let data = data.clone(); + let mut metadata_size = size_of::(); + let length_bytes = data[0..metadata_size].to_vec(); + assert_eq!(length_bytes, length.to_ne_bytes().as_ref().to_vec()); + + let padding_start = metadata_size.clone(); + add_padding::(&mut metadata_size); + let padding_end = metadata_size; + let data = data[padding_start..padding_end].to_vec(); + // Padding should be zeroed + assert_eq!(data, vec![0; padding_end - padding_start]); + } + + // 2. Validate slice content + assert_eq!(slice.as_slice(), reference_vec.as_slice()); + + // 3. Validate is not_empty + assert!(!slice.is_empty()); + + // 4. Validate accessors return None + assert_eq!(T::default(), *slice.first().unwrap()); + assert_eq!(T::default(), *slice.last().unwrap()); + } + + let length_usize: usize = length.to_usize().unwrap(); + let mut reference_vec = vec![T::default(); length_usize]; + + // Fill the slice completely and verify properties + { + let mut slice = ZeroCopySliceMut::::from_bytes(&mut data).unwrap(); + + for i in 0..length_usize { + let element = rng.gen(); + slice.as_mut_slice()[i] = element; + reference_vec[i] = element; + + // 1. Check slice length + assert_eq!(slice.len(), length_usize); + assert!(!slice.is_empty()); + // 2. Validate elements by index + assert_eq!(slice[i], element); + // 3. Validate get & get_mut + assert_eq!(slice.get(i), Some(&element)); + assert_eq!(slice.get_mut(i), Some(&mut reference_vec[i])); + + // 4. Validate first & last + assert_eq!(slice.first(), Some(&reference_vec[0])); + assert_eq!(slice.first_mut(), Some(&mut reference_vec[0])); + assert_eq!(slice.last(), reference_vec.last()); + assert_eq!(slice.last_mut(), reference_vec.last_mut()); + // 5. Validate as_slice + assert_eq!(slice.as_slice(), reference_vec.as_slice()); + assert_eq!(slice.as_mut_slice(), reference_vec.as_mut_slice()); + assert_eq!(slice.to_vec(), reference_vec); + } + + // 6. iterate over elements + for (index, element) in slice.iter().enumerate() { + assert_eq!(element, &reference_vec[index]); + } + + // 7. Mutate elements via iter_mut + for (index, element) in slice.iter_mut().enumerate() { + let new_element = rng.gen(); + *element = new_element; + reference_vec[index] = new_element; + } + + // Ensure the slice matches the reference after mutation + assert_eq!(slice.as_slice(), reference_vec.as_slice()); + } +} + +#[test] +fn test_zero_copy_vec() { + test_zero_copy_slice_mut_new::(u8::MAX); + println!("test_zero_copy_vec_with_capacity::()"); + test_zero_copy_slice_mut_new::(u8::MAX as u16); + println!("test_zero_copy_vec_with_capacity::()"); + test_zero_copy_slice_mut_new::(u8::MAX as u32); + println!("test_zero_copy_vec_with_capacity::()"); + test_zero_copy_slice_mut_new::(u8::MAX as u64); + println!("test_zero_copy_vec_with_capacity::()"); + test_zero_copy_slice_mut_new::(10000 as usize); +} + +#[test] +fn test_zero_copy_u64_struct_vec() { + #[derive(Copy, Clone, PartialEq, Debug, Default)] + struct TestStruct { + a: u32, + aa: u32, + b: u64, + c: u16, + cc: u16, + ccc: u32, + d: [u8; 32], + } + impl Distribution for Standard { + fn sample(&self, rng: &mut R) -> TestStruct { + TestStruct { + a: rng.gen(), + aa: rng.gen(), + b: rng.gen(), + c: rng.gen(), + cc: rng.gen(), + ccc: rng.gen(), + d: rng.gen(), + } + } + } + + test_zero_copy_slice_mut_new::(u8::MAX); + test_zero_copy_slice_mut_new::(u8::MAX as u16); + test_zero_copy_slice_mut_new::(u8::MAX as u32); + test_zero_copy_slice_mut_new::(u8::MAX as u64); + test_zero_copy_slice_mut_new::(u8::MAX as usize); +} + +#[test] +fn test_zero_copy_u8_struct_vec() { + #[derive(Copy, Clone, PartialEq, Debug, Default)] + struct TestStruct { + a: [u8; 32], + } + impl Distribution for Standard { + fn sample(&self, rng: &mut R) -> TestStruct { + TestStruct { a: rng.gen() } + } + } + + test_zero_copy_slice_mut_new::(u8::MAX); + test_zero_copy_slice_mut_new::(u8::MAX as u16); + test_zero_copy_slice_mut_new::(u8::MAX as u32); + test_zero_copy_slice_mut_new::(u8::MAX as u64); + test_zero_copy_slice_mut_new::(u8::MAX as usize); +} + +#[test] +fn test_empty() { + let length = 0; + let mut data = vec![0; ZeroCopySliceMut::::required_size_for_capacity(length)]; + let mut zero_copy_slice = + ZeroCopySliceMut::::new(u8::try_from(length).unwrap(), &mut data).unwrap(); + assert_eq!(zero_copy_slice.len(), length.to_usize().unwrap()); + assert!(zero_copy_slice.is_empty()); + assert_eq!(zero_copy_slice.first(), None); + assert_eq!(zero_copy_slice.last(), None); + assert_eq!(zero_copy_slice.get(0), None); + assert_eq!(zero_copy_slice.get_mut(0), None); + assert_eq!(zero_copy_slice.as_slice(), &[]); + assert_eq!(zero_copy_slice.iter().cloned().collect::>(), vec![]); + for element in zero_copy_slice.iter_mut() { + panic!("Expected no elements, found {:?}", element); + } + assert_eq!(zero_copy_slice.as_mut_slice(), &[]); + assert_eq!(zero_copy_slice.to_vec(), vec![]); +} + +#[test] +#[should_panic = "out of bounds"] +fn test_index_out_of_bounds() { + let length = 1; + let mut data = vec![0; ZeroCopySliceMut::::required_size_for_capacity(length)]; + let zero_copy_slice = + ZeroCopySliceMut::::new(u8::try_from(length).unwrap(), &mut data).unwrap(); + zero_copy_slice[length as usize]; +} + +/// Test that metadata size is aligned to T. +#[test] +fn test_metadata_size() { + assert_eq!(ZeroCopySliceMut::::metadata_size(), 1); + assert_eq!(ZeroCopySliceMut::::metadata_size(), 2); + assert_eq!(ZeroCopySliceMut::::metadata_size(), 4); + assert_eq!(ZeroCopySliceMut::::metadata_size(), 8); + assert_eq!(ZeroCopySliceMut::::metadata_size(), 8); + + assert_eq!(ZeroCopySliceMut::::metadata_size(), 2); + assert_eq!(ZeroCopySliceMut::::metadata_size(), 2); + assert_eq!(ZeroCopySliceMut::::metadata_size(), 4); + assert_eq!(ZeroCopySliceMut::::metadata_size(), 8); + assert_eq!(ZeroCopySliceMut::::metadata_size(), 8); + + assert_eq!(ZeroCopySliceMut::::metadata_size(), 4); + assert_eq!(ZeroCopySliceMut::::metadata_size(), 4); + assert_eq!(ZeroCopySliceMut::::metadata_size(), 4); + assert_eq!(ZeroCopySliceMut::::metadata_size(), 8); + assert_eq!(ZeroCopySliceMut::::metadata_size(), 8); + + assert_eq!(ZeroCopySliceMut::::metadata_size(), 8); + assert_eq!(ZeroCopySliceMut::::metadata_size(), 8); + assert_eq!(ZeroCopySliceMut::::metadata_size(), 8); + assert_eq!(ZeroCopySliceMut::::metadata_size(), 8); + assert_eq!(ZeroCopySliceMut::::metadata_size(), 8); +} + +#[test] +fn test_data_size() { + let length = 64; + assert_eq!(ZeroCopySliceMut::::data_size(length), 64); + assert_eq!(ZeroCopySliceMut::::data_size(length as u16), 64); + assert_eq!(ZeroCopySliceMut::::data_size(length as u32), 64); + assert_eq!(ZeroCopySliceMut::::data_size(length as u64), 64); + assert_eq!( + ZeroCopySliceMut::::data_size(length as usize), + 64 + ); + + assert_eq!(ZeroCopySliceMut::::data_size(length), 128); + assert_eq!(ZeroCopySliceMut::::data_size(length as u16), 128); + assert_eq!(ZeroCopySliceMut::::data_size(length as u32), 128); + assert_eq!(ZeroCopySliceMut::::data_size(length as u64), 128); + assert_eq!( + ZeroCopySliceMut::::data_size(length as usize), + 128 + ); + + assert_eq!(ZeroCopySliceMut::::data_size(length), 256); + assert_eq!(ZeroCopySliceMut::::data_size(length as u16), 256); + assert_eq!(ZeroCopySliceMut::::data_size(length as u32), 256); + assert_eq!(ZeroCopySliceMut::::data_size(length as u64), 256); + assert_eq!( + ZeroCopySliceMut::::data_size(length as usize), + 256 + ); + + assert_eq!(ZeroCopySliceMut::::data_size(length), 512); + assert_eq!(ZeroCopySliceMut::::data_size(length as u16), 512); + assert_eq!(ZeroCopySliceMut::::data_size(length as u32), 512); + assert_eq!(ZeroCopySliceMut::::data_size(length as u64), 512); + assert_eq!( + ZeroCopySliceMut::::data_size(length as usize), + 512 + ); +} + +#[test] +fn test_required_size() { + let length = 64; + assert_eq!( + ZeroCopySliceMut::::required_size_for_capacity(length), + 64 + 1 + ); + assert_eq!( + ZeroCopySliceMut::::required_size_for_capacity(length as u16), + 64 + 2 + ); + assert_eq!( + ZeroCopySliceMut::::required_size_for_capacity(length as u32), + 64 + 4 + ); + assert_eq!( + ZeroCopySliceMut::::required_size_for_capacity(length as u64), + 64 + 8 + ); + assert_eq!( + ZeroCopySliceMut::::required_size_for_capacity(length as usize), + 64 + 8 + ); + + assert_eq!( + ZeroCopySliceMut::::required_size_for_capacity(length), + 128 + 2 + ); + assert_eq!( + ZeroCopySliceMut::::required_size_for_capacity(length as u16), + 128 + 2 + ); + assert_eq!( + ZeroCopySliceMut::::required_size_for_capacity(length as u32), + 128 + 4 + ); + assert_eq!( + ZeroCopySliceMut::::required_size_for_capacity(length as u64), + 128 + 8 + ); + assert_eq!( + ZeroCopySliceMut::::required_size_for_capacity(length as usize), + 128 + 8 + ); + + assert_eq!( + ZeroCopySliceMut::::required_size_for_capacity(length), + 256 + 4 + ); + assert_eq!( + ZeroCopySliceMut::::required_size_for_capacity(length as u16), + 256 + 4 + ); + assert_eq!( + ZeroCopySliceMut::::required_size_for_capacity(length as u32), + 256 + 4 + ); + assert_eq!( + ZeroCopySliceMut::::required_size_for_capacity(length as u64), + 256 + 8 + ); + assert_eq!( + ZeroCopySliceMut::::required_size_for_capacity(length as usize), + 256 + 8 + ); + + assert_eq!( + ZeroCopySliceMut::::required_size_for_capacity(length), + 512 + 8 + ); + assert_eq!( + ZeroCopySliceMut::::required_size_for_capacity(length as u16), + 512 + 8 + ); + assert_eq!( + ZeroCopySliceMut::::required_size_for_capacity(length as u32), + 512 + 8 + ); + assert_eq!( + ZeroCopySliceMut::::required_size_for_capacity(length as u64), + 512 + 8 + ); + assert_eq!( + ZeroCopySliceMut::::required_size_for_capacity(length as usize), + 512 + 8 + ); +} + +#[test] +fn test_new_at_and_from_bytes_at_multiple() { + let mut account_data = + vec![0u8; ZeroCopySliceMutUsize::::required_size_for_capacity(4) * 2]; + // test new_at & fill vectors + { + let mut offset = 0; + let mut vec = + ZeroCopySliceMutUsize::::new_at(4, &mut account_data, &mut offset).unwrap(); + for i in 0..4 { + vec[i] = i as u64; + } + assert_eq!( + offset, + ZeroCopySliceMutUsize::::required_size_for_capacity(4) + ); + + let mut vec = + ZeroCopySliceMutUsize::::new_at(4, &mut account_data, &mut offset).unwrap(); + for i in 0..4 { + vec[i] = i as u64 + 4; + } + assert_eq!( + offset, + ZeroCopySliceMutUsize::::required_size_for_capacity(4) * 2 + ); + } + // test from_bytes_at_multiple + { + let mut offset = 0; + let deserialized_vecs = + ZeroCopySliceMutUsize::::from_bytes_at_multiple(2, &mut account_data, &mut offset) + .expect("Failed to deserialize multiple ZeroCopyCyclicVecs"); + + assert_eq!(deserialized_vecs.len(), 2); + for (i, deserialized_vec) in deserialized_vecs.iter().enumerate() { + for (j, element) in deserialized_vec.iter().enumerate() { + assert_eq!(*element, (i * 4 + j) as u64); + } + } + assert_eq!( + offset, + ZeroCopySliceMutUsize::::required_size_for_capacity(4) * 2 + ); + } +} + +#[test] +fn test_new_at_multiple() { + let mut account_data = vec![0u8; 128]; + let mut offset = 0; + let capacity = 4; + let mut reference_vecs = vec![vec![], vec![]]; + + { + let mut initialized_vecs = ZeroCopySliceMutUsize::::new_at_multiple( + 2, + capacity, + &mut account_data, + &mut offset, + ) + .unwrap(); + assert_eq!( + offset, + ZeroCopySliceMutUsize::::required_size_for_capacity(capacity) * 2 + ); + assert_eq!(initialized_vecs.len(), 2); + assert_eq!(initialized_vecs[0].len(), capacity); + assert_eq!(initialized_vecs[1].len(), capacity); + for i in 0..capacity { + for (j, vec) in initialized_vecs.iter_mut().enumerate() { + assert_eq!(vec.get(i), Some(&0)); + vec[i] = i as u64; + reference_vecs[j].push(i as u64); + assert_eq!(*vec.get(i).unwrap(), i as u64); + assert_eq!(vec.len(), capacity); + } + } + } + { + let vecs = + ZeroCopySliceMutUsize::::from_bytes_at_multiple(2, &mut account_data, &mut 0) + .unwrap(); + for (i, vec) in vecs.iter().enumerate() { + assert_eq!(vec.to_vec(), reference_vecs[i]); + } + } +} + +#[test] +fn test_copy_from_slice_and_try_into_array() { + let capacity = 16; + let mut data = vec![0; ZeroCopySliceMutUsize::::required_size_for_capacity(capacity)]; + let mut vec = ZeroCopySliceMutUsize::::new(capacity, &mut data).unwrap(); + vec.copy_from_slice(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]); + + let arr: [u32; 16] = vec.try_into_array().unwrap(); + assert_eq!(arr, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]); + + assert!(matches!( + vec.try_into_array::<15>(), + Err(ZeroCopyError::ArraySize(_, _)) + )); + assert!(matches!( + vec.try_into_array::<17>(), + Err(ZeroCopyError::ArraySize(_, _)) + )); +} + +#[test] +fn test_at_ptr() { + let capacity = 16; + let mut data = vec![0; ZeroCopySliceMutUsize::::required_size_for_capacity(capacity)]; + let data_mut_ptr = data[ZeroCopySliceMutUsize::::metadata_size()..].as_mut_ptr(); + let data_ptr = data[ZeroCopySliceMutUsize::::metadata_size()..].as_ptr(); + let mut vec = ZeroCopySliceMutUsize::::new(capacity, &mut data).unwrap(); + assert_eq!(data_ptr as *const u32, vec.data_as_ptr()); + assert_eq!(data_mut_ptr as *mut u32, vec.data_as_mut_ptr()); +} + +#[test] +fn test_failing_new() { + let capacity = 16; + let mut data = vec![0; ZeroCopySliceMutUsize::::required_size_for_capacity(capacity) - 1]; + let vec = ZeroCopySliceMutUsize::::new(capacity, &mut data); + assert!(matches!( + vec, + Err(ZeroCopyError::InsufficientMemoryAllocated(_, _)) + )); +} + +#[test] +fn test_failing_from_bytes_at() { + let capacity = 16; + let mut data = vec![0; ZeroCopySliceMutUsize::::required_size_for_capacity(capacity)]; + ZeroCopySliceMutUsize::::new(capacity, &mut data).unwrap(); + let vec = ZeroCopySliceMutUsize::::from_bytes_at(&mut data[..7], &mut 0); + assert!(matches!( + vec, + Err(ZeroCopyError::InsufficientMemoryAllocated(_, _)) + )); + let vec = ZeroCopySliceMutUsize::::from_bytes_at(&mut data[..9], &mut 0); + assert!(matches!( + vec, + Err(ZeroCopyError::InsufficientMemoryAllocated(_, _)) + )); +} diff --git a/program-libs/zero-copy/tests/vec_tests.rs b/program-libs/zero-copy/tests/vec_tests.rs new file mode 100644 index 0000000000..aa4ad3830b --- /dev/null +++ b/program-libs/zero-copy/tests/vec_tests.rs @@ -0,0 +1,532 @@ +use std::fmt::Debug; + +use light_zero_copy::{ + add_padding, + errors::ZeroCopyError, + vec::{ZeroCopyVec, ZeroCopyVecUsize}, +}; +use num_traits::{FromPrimitive, PrimInt, ToPrimitive}; +use rand::{ + distributions::{Distribution, Standard}, + thread_rng, Rng, +}; + +/// Generates a random value, excluding the values provided +/// in `exclude`. +fn gen_exclude(rng: &mut N, exclude: &[T]) -> T +where + N: Rng, + T: PartialEq, + Standard: Distribution, +{ + loop { + // This utility is supposed to be used only in unit tests. This `clone` + // is harmless and necessary (can't pass a reference to range, it has + // to be moved). + let sample = rng.gen(); + if !exclude.contains(&sample) { + return sample; + } + } +} + +#[test] +fn test_zero_copy_vec() { + test_zero_copy_vec_new::(u8::MAX); + println!("test_zero_copy_vec_with_capacity::()"); + test_zero_copy_vec_new::(u8::MAX as u16); + println!("test_zero_copy_vec_with_capacity::()"); + test_zero_copy_vec_new::(u8::MAX as u32); + println!("test_zero_copy_vec_with_capacity::()"); + test_zero_copy_vec_new::(u8::MAX as u64); + println!("test_zero_copy_vec_with_capacity::()"); + test_zero_copy_vec_new::(10000 as usize); +} + +#[test] +fn test_zero_copy_u64_struct_vec() { + #[derive(Copy, Clone, PartialEq, Debug)] + struct TestStruct { + a: u32, + aa: u32, + b: u64, + c: u16, + cc: u16, + ccc: u32, + d: [u8; 32], + } + impl Distribution for Standard { + fn sample(&self, rng: &mut R) -> TestStruct { + TestStruct { + a: rng.gen(), + aa: rng.gen(), + b: rng.gen(), + c: rng.gen(), + cc: rng.gen(), + ccc: rng.gen(), + d: rng.gen(), + } + } + } + + test_zero_copy_vec_new::(u8::MAX); + test_zero_copy_vec_new::(u8::MAX as u16); + test_zero_copy_vec_new::(u8::MAX as u32); + test_zero_copy_vec_new::(u8::MAX as u64); + test_zero_copy_vec_new::(u8::MAX as usize); +} + +#[test] +fn test_zero_copy_u8_struct_vec() { + #[derive(Copy, Clone, PartialEq, Debug)] + struct TestStruct { + a: [u8; 32], + } + impl Distribution for Standard { + fn sample(&self, rng: &mut R) -> TestStruct { + TestStruct { a: rng.gen() } + } + } + + test_zero_copy_vec_new::(u8::MAX); + test_zero_copy_vec_new::(u8::MAX as u16); + test_zero_copy_vec_new::(u8::MAX as u32); + test_zero_copy_vec_new::(u8::MAX as u64); + test_zero_copy_vec_new::(u8::MAX as usize); +} + +fn test_zero_copy_vec_new(capacity: CAPACITY) +where + CAPACITY: FromPrimitive + ToPrimitive + PrimInt + num_traits::ToBytes + Debug, + T: Copy + Clone + PartialEq + Debug, + Standard: Distribution, +{ + let mut rng = thread_rng(); + let mut data = + vec![ + 0; + ZeroCopyVec::::required_size_for_capacity(capacity.to_usize().unwrap()) + ]; + println!("data len: {}", data.len()); + println!("capacity: {:?}", capacity); + // new + { + let zero_copy_vec = ZeroCopyVec::::new(capacity, &mut data).unwrap(); + assert_eq!(zero_copy_vec.capacity(), capacity.to_usize().unwrap()); + assert_eq!(zero_copy_vec.len(), 0); + assert!(zero_copy_vec.is_empty()); + } + // empty from bytes + { + let reference_vec = vec![]; + let vec = ZeroCopyVec::::from_bytes(&mut data).unwrap(); + assert_empty_vec(capacity, reference_vec, vec); + { + let data = data.clone(); + let mut metadata_size = size_of::(); + let length = data[0..metadata_size].to_vec(); + let ref_length: CAPACITY = CAPACITY::from_usize(0).unwrap(); + assert_eq!(length, ref_length.to_ne_bytes().as_ref().to_vec()); + + let padding_start = metadata_size.clone(); + add_padding::(&mut metadata_size); + let padding_end = metadata_size; + let data = data[padding_start..padding_end].to_vec(); + // Padding should be zeroed + assert_eq!(data, vec![0; padding_end - padding_start]); + } + } + let capacity_usize: usize = capacity.to_usize().unwrap(); + let mut reference_vec = vec![]; + // fill vector completely and assert: + { + let mut vec = ZeroCopyVec::::from_bytes(&mut data).unwrap(); + // 1. vector is not empty + // 2. vector length is correct + // 3. vector capacity is correct + // 4. vector elements can be accessed by index + // 5. vector elements can be accessed by get + // 6. vector elements can be accessed by get_mut + // 7. vector last element can be accessed + // 8. vector last element can be accessed mutably + // 9. vector first element can be accessed + // 10. vector first element can be accessed mutably + // 11. vector as_slice returns correct slice + // 12. vector as_mut_slice returns correct slice + // 13. vector to_vec returns correct vector + // 14. (iter) iterating over vector returns correct elements + // 15. (iter_mut) iterating over vector mutably returns correct elements + for i in 0..capacity_usize { + let element = rng.gen(); + vec.push(element).unwrap(); + reference_vec.push(element); + // 1. vector is not empty + assert!(!vec.is_empty()); + // 2. vector length is correct + assert_eq!(vec.len(), i + 1); + { + let data = data.clone(); + let mut metadata_size = size_of::(); + let length = data[0..metadata_size].to_vec(); + let ref_length: CAPACITY = CAPACITY::from_usize(i + 1).unwrap(); + assert_eq!(length, ref_length.to_ne_bytes().as_ref().to_vec()); + + let padding_start = metadata_size.clone(); + add_padding::(&mut metadata_size); + let padding_end = metadata_size; + let data = data[padding_start..padding_end].to_vec(); + // Padding should be zeroed + assert_eq!(data, vec![0; padding_end - padding_start]); + } + // 3. vector capacity is correct + assert_eq!(vec.capacity(), capacity_usize); + // 4. vector elements can be accessed by index + assert_eq!(vec[i], element); + // 5. vector elements can be accessed by get + assert_eq!(vec.get(i), Some(&element)); + // 6. vector elements can be accessed by get_mut + assert_eq!(vec.get_mut(i), Some(&mut reference_vec[i])); + // 7. vector last element can be accessed + assert_eq!(vec.last(), Some(&element)); + // 8. vector last element can be accessed mutably + assert_eq!(vec.last_mut(), Some(&mut reference_vec[i])); + // 9. vector first element can be accessed + assert_eq!(vec.first(), Some(&reference_vec[0])); + // 10. vector first element can be accessed mutably + assert_eq!(vec.first_mut(), Some(&mut reference_vec[0])); + // 11. vector as_slice returns correct slice + assert_eq!(vec.as_slice(), reference_vec.as_slice()); + assert_ne!(&vec.as_slice()[1..], reference_vec.as_slice()); + // 12. vector as_mut_slice returns correct slice + assert_eq!(vec.as_mut_slice(), reference_vec.as_mut_slice()); + assert_ne!(&vec.as_mut_slice()[1..], reference_vec.as_mut_slice()); + // 13. vector to_vec returns correct vector + assert_eq!(vec.to_vec(), reference_vec); + assert_ne!(vec.to_vec()[1..].to_vec(), reference_vec); + // 14. (iter) iterating over vector returns correct elements + for (index, element) in vec.iter().enumerate() { + assert_eq!(*element, reference_vec[index]); + } + // 15. (iter_mut) iterating over vector mutably returns correct elements + for (index, element) in vec.iter_mut().enumerate() { + assert_eq!(*element, reference_vec[index]); + let new_element = gen_exclude(&mut rng, &[*element]); + *element = new_element; + assert_ne!(element, &reference_vec[index]); + reference_vec[index] = *element; + } + } + } + // full vec from bytes + { + let mut vec = ZeroCopyVec::::from_bytes(&mut data).unwrap(); + assert_full_vec( + capacity, + &mut rng, + capacity_usize, + &mut reference_vec, + &mut vec, + ); + // Failing push into full vec + let result = vec.push(rng.gen()); + assert_eq!(result, Err(ZeroCopyError::Full)); + } + // clear + { + let mut vec = ZeroCopyVec::::from_bytes(&mut data).unwrap(); + vec.clear(); + assert_eq!(vec.len(), 0); + assert_eq!(vec.capacity(), capacity_usize); + assert!(vec.is_empty()); + assert_ne!(vec.as_slice(), reference_vec.as_slice()); + assert_ne!(vec.as_mut_slice(), reference_vec.as_mut_slice()); + assert_ne!(vec.to_vec(), reference_vec); + reference_vec.clear(); + assert_empty_vec(capacity, reference_vec, vec); + { + let data = data.clone(); + let mut metadata_size = size_of::(); + let length = data[0..metadata_size].to_vec(); + let ref_length: CAPACITY = CAPACITY::zero(); //;(0).to_usize().unwrap(); + assert_eq!(length, ref_length.to_ne_bytes().as_ref().to_vec()); + + let padding_start = metadata_size.clone(); + add_padding::(&mut metadata_size); + let padding_end = metadata_size; + let data = data[padding_start..padding_end].to_vec(); + // Padding should be zeroed + assert_eq!(data, vec![0; padding_end - padding_start]); + } + } +} + +fn assert_full_vec( + capacity: CAPACITY, + rng: &mut rand::prelude::ThreadRng, + capacity_usize: usize, + reference_vec: &mut Vec, + vec: &mut ZeroCopyVec, +) where + CAPACITY: FromPrimitive + ToPrimitive + PrimInt, + T: Copy + Clone + PartialEq + Debug, + Standard: Distribution, +{ + // 1. vector capacity is correct + assert_eq!(vec.capacity(), capacity.to_usize().unwrap()); + // 2. vector length is correct + assert_eq!(vec.len(), capacity_usize); + // 3. vector is not empty + assert!(!vec.is_empty()); + // 4. vector as slice returns correct slice + assert_eq!(vec.as_slice(), reference_vec.as_slice()); + assert_ne!(&vec.as_slice()[1..], reference_vec.as_slice()); + // 5. vector as_mut_slice returns correct slice + assert_eq!(vec.as_mut_slice(), reference_vec.as_mut_slice()); + assert_ne!(&vec.as_mut_slice()[1..], reference_vec.as_mut_slice()); + // 6. vector to_vec returns correct vector + assert_eq!(vec.to_vec(), *reference_vec); + assert_ne!(vec.to_vec()[1..].to_vec(), *reference_vec); + // 7. (iter) iterating over vector returns correct elements + for (index, element) in vec.iter().enumerate() { + assert_eq!(*element, reference_vec[index]); + } + // 8. (iter_mut) iterating over vector mutably returns correct elements + for (index, element) in vec.iter_mut().enumerate() { + assert_eq!(*element, reference_vec[index]); + *element = rng.gen(); + assert_ne!(element, &reference_vec[index]); + reference_vec[index] = *element; + } +} + +fn assert_empty_vec( + capacity: CAPACITY, + mut reference_vec: Vec, + mut vec: ZeroCopyVec, +) where + CAPACITY: FromPrimitive + ToPrimitive + PrimInt, + T: Copy + Clone + PartialEq + Debug, +{ + // 1. vector capacity is correct + assert_eq!(vec.capacity(), capacity.to_usize().unwrap()); + // 2. vector length is correct + assert_eq!(vec.len(), 0); + // 3. vector is empty + assert!(vec.is_empty()); + // 4. vector elements can be accessed by get + assert_eq!(vec.get(0), None); + // 5. vector elements can be accessed by get_mut + assert_eq!(vec.get_mut(0), None); + // 6. vector last element can be accessed + assert_eq!(vec.last(), None); + // 7. vector last element can be accessed mutably + assert_eq!(vec.last_mut(), None); + // 8. vector first element can be accessed + assert_eq!(vec.first(), None); + // 9. vector first element can be accessed mutably + assert_eq!(vec.first_mut(), None); + // 10. vector as_slice returns correct slice + assert_eq!(vec.as_slice(), reference_vec.as_slice()); + // 11. vector as_mut_slice returns correct slice + assert_eq!(vec.as_mut_slice(), reference_vec.as_mut_slice()); + // 12. vector to_vec returns correct vector + assert_eq!(vec.to_vec(), reference_vec); + // 13. (iter) iterating over vector returns correct elements + for (index, element) in vec.iter().enumerate() { + panic!( + "Should not iterate over empty vector: index={}, element={:?}", + index, element + ); + } + // 14. (iter_mut) iterating over vector mutably returns correct elements + for (index, element) in vec.iter_mut().enumerate() { + panic!( + "Should not iterate over empty vector: index={}, element={:?}", + index, element + ); + } +} + +#[test] +fn test_zero_copy_vec_to_array() { + let capacity = 16; + let mut data = vec![0; ZeroCopyVecUsize::::required_size_for_capacity(capacity)]; + let mut vec = ZeroCopyVecUsize::::new(capacity, &mut data).unwrap(); + vec.extend_from_slice(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]); + + let arr: [u32; 16] = vec.try_into_array().unwrap(); + assert_eq!(arr, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]); + + assert!(matches!( + vec.try_into_array::<15>(), + Err(ZeroCopyError::ArraySize(_, _)) + )); + assert!(matches!( + vec.try_into_array::<17>(), + Err(ZeroCopyError::ArraySize(_, _)) + )); +} + +#[test] +fn test_zero_copy_vec_into_iter() { + let capacity = 1000; + let mut data = vec![0; ZeroCopyVecUsize::::required_size_for_capacity(capacity)]; + let mut vec = ZeroCopyVecUsize::::new(capacity, &mut data).unwrap(); + + for i in 0..1000 { + vec.push(i).unwrap(); + } + + for (i, element) in vec.into_iter().enumerate() { + assert_eq!(*element, i); + } +} + +#[test] +fn test_new_at_and_from_bytes_at_multiple() { + let mut account_data = vec![0u8; ZeroCopyVecUsize::::required_size_for_capacity(4) * 2]; + // test new_at & fill vectors + { + let mut offset = 0; + let mut vec = ZeroCopyVecUsize::::new_at(4, &mut account_data, &mut offset).unwrap(); + for i in 0..4 { + vec.push(i as u64).unwrap(); + } + assert_eq!( + offset, + ZeroCopyVecUsize::::required_size_for_capacity(4) + ); + + let mut vec = ZeroCopyVecUsize::::new_at(4, &mut account_data, &mut offset).unwrap(); + for i in 4..8 { + vec.push(i as u64).unwrap(); + } + assert_eq!( + offset, + ZeroCopyVecUsize::::required_size_for_capacity(4) * 2 + ); + } + // test from_bytes_at_multiple + { + let mut offset = 0; + let deserialized_vecs = + ZeroCopyVecUsize::::from_bytes_at_multiple(2, &mut account_data, &mut offset) + .expect("Failed to deserialize multiple ZeroCopyCyclicVecs"); + + assert_eq!(deserialized_vecs.len(), 2); + for (i, deserialized_vec) in deserialized_vecs.iter().enumerate() { + for (j, element) in deserialized_vec.iter().enumerate() { + assert_eq!(*element, (i * 4 + j) as u64); + } + } + assert_eq!( + offset, + ZeroCopyVecUsize::::required_size_for_capacity(4) * 2 + ); + } +} + +#[test] +fn test_init_multiple_pass() { + let mut account_data = vec![0u8; 128]; + let mut offset = 0; + let capacity = 4; + let mut initialized_vecs = + ZeroCopyVecUsize::::new_at_multiple(2, capacity, &mut account_data, &mut offset) + .unwrap(); + assert_eq!( + offset, + ZeroCopyVecUsize::::required_size_for_capacity(capacity) * 2 + ); + assert_eq!(initialized_vecs.len(), 2); + assert_eq!(initialized_vecs[0].capacity(), capacity); + assert_eq!(initialized_vecs[1].capacity(), capacity); + assert_eq!(initialized_vecs[0].len(), 0); + assert_eq!(initialized_vecs[1].len(), 0); + let mut reference_vecs = vec![vec![], vec![]]; + for i in 0..capacity { + for (j, vec) in initialized_vecs.iter_mut().enumerate() { + assert!(vec.get(i).is_none()); + vec.push(i as u64).unwrap(); + reference_vecs[j].push(i as u64); + assert_eq!(*vec.get(i).unwrap(), i as u64); + assert!(vec.len() == i + 1); + } + } + for (i, vec) in initialized_vecs.iter_mut().enumerate() { + let mut rng = thread_rng(); + assert_full_vec(capacity, &mut rng, capacity, &mut reference_vecs[i], vec); + } +} + +#[test] +fn test_metadata_size() { + assert_eq!(ZeroCopyVec::::metadata_size(), 1); + assert_eq!(ZeroCopyVec::::metadata_size(), 2); + assert_eq!(ZeroCopyVec::::metadata_size(), 4); + assert_eq!(ZeroCopyVec::::metadata_size(), 8); + assert_eq!(ZeroCopyVec::::metadata_size(), 8); + + assert_eq!(ZeroCopyVec::::metadata_size(), 2); + assert_eq!(ZeroCopyVec::::metadata_size(), 2); + assert_eq!(ZeroCopyVec::::metadata_size(), 4); + assert_eq!(ZeroCopyVec::::metadata_size(), 8); + assert_eq!(ZeroCopyVec::::metadata_size(), 8); + + assert_eq!(ZeroCopyVec::::metadata_size(), 4); + assert_eq!(ZeroCopyVec::::metadata_size(), 4); + assert_eq!(ZeroCopyVec::::metadata_size(), 4); + assert_eq!(ZeroCopyVec::::metadata_size(), 8); + assert_eq!(ZeroCopyVec::::metadata_size(), 8); + + assert_eq!(ZeroCopyVec::::metadata_size(), 8); + assert_eq!(ZeroCopyVec::::metadata_size(), 8); + assert_eq!(ZeroCopyVec::::metadata_size(), 8); + assert_eq!(ZeroCopyVec::::metadata_size(), 8); + assert_eq!(ZeroCopyVec::::metadata_size(), 8); +} + +#[test] +fn test_data_size() { + assert_eq!(ZeroCopyVec::::data_size(64), 65); +} + +#[test] +fn test_required_size() { + // length + capacity + data + assert_eq!( + ZeroCopyVec::::required_size_for_capacity(64), + 1 + 1 + 64 + ); + // length + capacity + data + assert_eq!( + ZeroCopyVec::::required_size_for_capacity(64), + 8 + 8 + 8 * 64 + ); +} + +#[test] +fn test_partial_eq() { + let mut account_data = vec![0u8; ZeroCopyVecUsize::::required_size_for_capacity(5)]; + let mut offset = 0; + let mut vec = ZeroCopyVecUsize::::new_at(5, &mut account_data, &mut offset).unwrap(); + for i in 0..4 { + vec.push(i as u64).unwrap(); + } + + let mut account_data = vec![0u8; ZeroCopyVecUsize::::required_size_for_capacity(5)]; + let mut offset = 0; + let mut vec2 = ZeroCopyVecUsize::::new_at(5, &mut account_data, &mut offset).unwrap(); + for i in 0..4 { + vec2.push(i as u64).unwrap(); + } + + assert_eq!(vec, vec2); + + vec2.push(5).unwrap(); + assert_ne!(vec, vec2); + vec.push(5).unwrap(); + assert_eq!(vec, vec2); + vec.clear(); + assert_ne!(vec, vec2); +} diff --git a/program-tests/utils/src/assert_compressed_tx.rs b/program-tests/utils/src/assert_compressed_tx.rs index 753a03cc64..fd18b06aa9 100644 --- a/program-tests/utils/src/assert_compressed_tx.rs +++ b/program-tests/utils/src/assert_compressed_tx.rs @@ -149,7 +149,7 @@ pub async fn assert_nullifiers_exist_in_hash_sets( &mut merkle_tree_account_data, ) .unwrap(); - let mut batches = merkle_tree.batches.clone(); + let mut batches = merkle_tree.batches; batches.iter_mut().enumerate().any(|(i, batch)| { batch .check_non_inclusion( @@ -186,7 +186,7 @@ pub async fn assert_addresses_exist_in_hash_sets( let mut merkle_tree = BatchedMerkleTreeAccount::address_tree_from_bytes_mut(&mut account_data) .unwrap(); - let mut batches = merkle_tree.batches.clone(); + let mut batches = merkle_tree.batches; // Must be included in one batch batches.iter_mut().enumerate().any(|(i, batch)| { batch