Skip to content

Conversation

@ananas-block
Copy link
Contributor

@ananas-block ananas-block commented Oct 18, 2025

Summary by CodeRabbit

  • New Features

    • Adds a lightweight event crate, read-only compressed-account variants, new CPI signer macros, a CpiSigner type, and optional SHA256/Keccak hashing support.
  • Bug Fixes

    • Improved error messages, safer conditional logging for no_std builds, and explicit errors when crypto features are disabled.
  • Refactor

    • Serialization and many traits gated by feature flags; wider no_std/alloc/std configurability and reduced default features for finer-grained builds.
  • Tests

    • Expanded and reorganized tests for hashing, bigint conversions, zero-copy, and serialization; new no_std test harness.

- Add no_std attribute with std/alloc feature flags
- Replace std::result types with core::result equivalents
- Add conditional Vec imports with #[cfg(feature = "alloc")]
- Add keccak feature for optional v1 address derivation
- Gate solana_msg::msg! logging with #[cfg(feature = "std")]
- Remove unused light-zero-copy dependency
- Maintain backward compatibility with default = ["std"]
- All tests passing, dependent crates build successfully

Three supported modes:
- no_std without alloc: Pure no_std (Vec unavailable)
- no_std with alloc: For BPF programs with allocator
- std mode (default): Standard library with full compatibility

Part of ongoing no_std refactoring for Solana program compatibility.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 18, 2025

Walkthrough

Adds a new workspace crate light-event, moves event parsing/types into it, and applies widespread no_std/alloc and feature-gating refactors: conditional Anchor↔Borsh derives, core::result/core type normalization, hasher platform/feature gating, zero-copy alloc support, relocation of packing helpers, and CPI/LightAccount signature/export adjustments.

Changes

Cohort / File(s) Summary
New event crate
\sdk-libs/event/Cargo.toml`, `sdk-libs/event/src/{lib.rs,error.rs,event.rs,parse.rs}``
Adds light-event crate with modules error, event, parse; wires parsing/types to use light_compressed_account where needed.
Event import updates
program-tests/*, \sdk-libs/client/src/rpc/{client.rs,errors.rs,rpc_trait.rs}`, `sdk-libs/program-test/src/**``
Replace imports from light_compressed_account::indexer_event with light_event equivalents; adjust parse/error conversion and parse error handling.
Packing helpers moved
\program-libs/compressed-account/src/{address.rs,compressed_account.rs,...}`, `program-tests/utils/src/compressed_account_pack.rs`, `program-tests/utils/src/lib.rs`, `program-tests/**/sdk.rs``
Remove many packing helpers from compressed-account; add program-tests::compressed_account_pack with packing utilities and update call sites.
Serialization derive gating
\program-libs/compressed-account/src/**`, `program-libs/compressed-account/src/lib.rs`, `program-libs/compressed-account/src/pubkey.rs``
Replace unconditional Anchor derives with cfg_attr gating (std+anchor → Anchor; else → Borsh); adjust imports and trait visibility.
Hasher feature & platform gating
\program-libs/hasher/{Cargo.toml,src/}`, `program-libs/hasher/tests/``
Add sha256/keccak features, new HasherError variants for disabled features, platform-specific keccak/sha256 paths, alloc-gated hash helpers, and test additions/removals.
zero-copy & alloc reshaping
\program-libs/zero-copy/{Cargo.toml,src/**}`, `program-libs/compressed-account/src/instruction_data/zero_copy.rs``
Add alloc/std features, extern crate alloc for no_std+alloc, introduce VecU8 and alloc-gated Vec impls for ZeroCopy traits, move std→core in spots, add bounds checks.
core normalization (core::result / core types)
assorted (e.g. \program-libs/compressed-account/src/instruction_data/insert_into_queues.rs`, `sdk-libs/sdk-types/src/error.rs``)
Replace std::result / std::* usages with core equivalents and gate allocation-backed APIs with #[cfg(feature="alloc")].
Account-checks no_std adjustments
\program-libs/account-checks/Cargo.toml`, `program-libs/account-checks/src/*.rs``
Add std feature, gate logging with #[cfg(feature="std")], switch many std types to core (cell/panic/ops), rename unused params, re-export AccountInfoTrait/AccountIterator/AccountError.
SDK / LightAccount & CPI refactor
\sdk-libs/sdk-pinocchio/src/`, `sdk-libs/sdk-pinocchio/Cargo.toml`, `sdk-libs/sdk-types/src/`, `sdk-libs/sdk/src/**``
Remove local account module, gate LightAccount/Borsh re-exports, change CPI signatures to ProgramError/crate-scoped types, rehome CpiSigner, add invoke_array support and feature guards.
Macros / proc-macro changes
\program-libs/macros/src/cpi_signer.rs`, `program-libs/macros/src/lib.rs`, `sdk-libs/macros/src/**``
Add cpi_signer macros (derive_light_cpi_signer_pda, derive_light_cpi_signer) with CPI_AUTHORITY_SEED; move some derive exports to light_macros.
Tests moved/added/removed
\program-libs/hasher/tests/`, other `program-libs//tests/`, `sdk-tests/``
Remove many inline #[cfg(test)] modules; add focused integration/unit tests under tests/; relocate and gate tests by features/alloc/std.
Workspace Cargo feature toggles
many \Cargo.toml``
Toggle default-features, disable default-features for multiple light-* crates, add std/alloc/crypto features to crates, enable light-compressed-account std in many downstream manifests, add light-event to workspace.
Lint / CI script update
\scripts/lint.sh`, .github/workflows/*`
Replace single no_std check with iterative feature-combination cargo checks/tests; adjust CI matrices (remove some no-default-features runs).

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant OldIndexer as light_compressed_account::indexer_event
    participant EventCrate as light_event

    Note over Caller,OldIndexer: Before
    Caller->>OldIndexer: import types / call parse
    OldIndexer-->>Caller: parsed event / types

    Note over Caller,EventCrate: After
    Caller->>EventCrate: import types / call parse
    EventCrate-->>Caller: parsed event / types
Loading
sequenceDiagram
    participant Serializer
    participant Anchor as anchor_lang::AnchorSerialize
    participant Borsh as borsh::BorshSerialize

    Note left of Serializer: Serialization chosen at compile-time
    Serializer->>Anchor: if feature(std) && feature(anchor)
    Anchor-->>Serializer: Anchor serialization used
    Serializer->>Borsh: else
    Borsh-->>Serializer: Borsh serialization used
    Note right of Serializer: Controlled via #[cfg_attr(...)]
Loading
sequenceDiagram
    participant HasherCaller
    participant KeccakLocal
    participant KeccakSolana
    participant ErrorPath

    HasherCaller->>KeccakLocal: if !target_os=solana && feature(keccak)
    KeccakLocal-->>HasherCaller: compute via sha3 crate
    HasherCaller->>ErrorPath: if !target_os=solana && !feature(keccak)
    ErrorPath-->>HasherCaller: return HasherError::KeccakFeatureNotEnabled
    HasherCaller->>KeccakSolana: if target_os=solana
    KeccakSolana-->>HasherCaller: perform solana host keccak call
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60–90 minutes

Possibly related PRs

Suggested reviewers

  • sergeytimoshin
  • SwenSchaeferjohann

Poem

🐇 I hopped through crates at break of day,

moved events and packers on my way.
Core and alloc now hold the key,
derives pick Anchor or Borsh for me.
Tiny hop, big change — a carrot clap, hooray! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "refactor: libs to nostd" accurately captures the primary objective and scope of the changeset. The changes systematically introduce no_std (no standard library) support across multiple libraries through feature gating, conditional compilation, and appropriate import replacements (e.g., std:: to core::). The title is specific, concise, and meaningful—avoiding vague terminology like "misc updates" or "stuff"—and clearly communicates to reviewers that this is a refactoring effort focused on no_std compatibility rather than new features or bug fixes. A developer scanning the repository history would immediately understand the main intent of this PR.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch jorrit/refactor-compressed-account-nostd

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ananas-block ananas-block force-pushed the jorrit/refactor-compressed-account-nostd branch from 8dda9ef to cd07341 Compare October 18, 2025 11:56
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 22

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (12)
sdk-libs/sdk-types/src/instruction/account_info.rs (1)

76-80: Confirm the API asymmetry and allocation issue.

The original review comment is correct. Both input_compressed_account and output_compressed_account perform heap allocations, but only input_compressed_account is feature-gated with #[cfg(feature = "alloc")].

Evidence:

  • Line 195–197 (input_compressed_account): Creates Vec::new()
  • Line 214 (output_compressed_account): Calls output.data.clone()

Both require allocator support. When the alloc feature is disabled:

  • input_compressed_account becomes unavailable (compiler-enforced)
  • output_compressed_account remains callable but still allocates, breaking no-alloc guarantees

Recommendation: Add #[cfg(feature = "alloc")] to the trait declaration and implementation of output_compressed_account (lines 81 and 206) to maintain API consistency and enforce no-alloc correctness.

program-libs/compressed-account/Cargo.toml (1)

27-36: Fix unconditional std dependencies to support no_std builds

light-zero-copy has std as a default feature and requires default-features = false to disable it; borsh similarly has std as a default feature and needs the same treatment. The crate is designed for no_std (lib.rs has #![cfg_attr(not(feature = "std"), no_std)]) but currently breaks when compiled without the std feature because light-zero-copy/std is forced on unconditionally.

-light-zero-copy = { workspace = true, features = ["std", "mut", "derive"] }
+light-zero-copy = { workspace = true, default-features = false, features = ["mut", "derive"] }
-borsh = { workspace = true }
+borsh = { workspace = true, default-features = false }

And in [features]:

-std = ["alloc", "borsh/std", "dep:solana-msg"]
+std = ["alloc", "borsh/std", "dep:solana-msg", "light-zero-copy/std"]
sdk-libs/client/src/rpc/client.rs (1)

301-305: Do not unwrap parser result (panic risk on RPC path)

event_from_light_transaction(...).unwrap() will panic on parse errors. Propagate as RpcError instead.

Apply:

-        let parsed_event =
-            event_from_light_transaction(program_ids.as_slice(), vec.as_slice(), vec_accounts)
-                .unwrap();
+        let parsed_event = event_from_light_transaction(
+            program_ids.as_slice(),
+            vec.as_slice(),
+            vec_accounts,
+        )
+        .map_err(|e| RpcError::CustomError(format!("Failed to parse event: {e:?}")))?;
program-tests/create-address-test-program/src/lib.rs (2)

103-124: Bounds-check remaining_accounts before indexed access.

remaining_accounts[1] and [2] will panic if fewer than 3 accounts are supplied when write_cpi_context is true.

-            } else {
+            } else {
+                require!(
+                    ctx.remaining_accounts.len() >= 3,
+                    ErrorCode::AccountNotEnoughKeys
+                );
                 let mut account_metas = vec![];
                 account_metas.push(AccountMeta {
                     pubkey: *cpi_accounts.fee_payer().key,
                     is_signer: true,
                     is_writable: true,
                 });

281-288: Capacity arithmetic can underflow; use saturating math.

Vec::with_capacity(1 + len - PROGRAM_ACCOUNTS_LEN) underflows if len < PROGRAM_ACCOUNTS_LEN, yielding a huge capacity.

-    let mut account_metas =
-        Vec::with_capacity(1 + cpi_accounts.account_infos().len() - PROGRAM_ACCOUNTS_LEN);
+    let extra = cpi_accounts
+        .account_infos()
+        .len()
+        .saturating_sub(PROGRAM_ACCOUNTS_LEN);
+    let mut account_metas = Vec::with_capacity(1 + extra);
program-libs/hasher/src/hash_to_field_size.rs (1)

20-35: Avoid unwrap; use the existing Result to propagate errors consistently

Keccak::hashv already returns a Result in this codebase (see its use with ? below). Replace unwrap() with ? to avoid panics and align error handling across functions.

Apply this diff:

-        // SAFETY: cannot panic Hasher::hashv returns an error because Poseidon can panic.
-        let mut hashed_value = Keccak::hashv(slices.as_slice()).unwrap();
+        // Keccak::hashv is fallible (trait-unified), propagate errors instead of panicking.
+        let mut hashed_value = Keccak::hashv(slices.as_slice())?;
program-libs/compressed-account/src/instruction_data/with_readonly.rs (1)

525-542: Strengthen bounds to prevent OOM/DoS on untrusted input

if bytes.len() < num_slices { ... } is too weak. Each element consumes many bytes; a large num_slices can trigger huge allocations (Vec::with_capacity(num_slices)) and long loops.

Add:

  • A hard cap (e.g., MAX_INPUT_ACCOUNTS).
  • A size-based check using size_of::<ZInAccountMeta>() + 1 (for the Option tag) with checked multiplication.

Proposed patch:

-        let (num_slices, mut bytes) = Ref::<&[u8], U32>::from_prefix(bytes)?;
-        let num_slices = u32::from(*num_slices) as usize;
-        // Prevent heap exhaustion attacks by checking if num_slices is reasonable
-        // Each element needs at least 1 byte when serialized
-        if bytes.len() < num_slices {
+        let (num_slices, mut bytes) = Ref::<&[u8], U32>::from_prefix(bytes)?;
+        let num_slices = u32::from(*num_slices) as usize;
+        const MAX_INPUT_ACCOUNTS: usize = 10_000; // tune as appropriate
+        if num_slices > MAX_INPUT_ACCOUNTS {
+            return Err(ZeroCopyError::InsufficientMemoryAllocated(num_slices, MAX_INPUT_ACCOUNTS));
+        }
+        // Approximate lower bound per entry: meta + 1 byte Option tag for address
+        let min_per = core::mem::size_of::<ZInAccountMeta>() + 1;
+        let needed = num_slices.checked_mul(min_per).ok_or_else(|| {
+            ZeroCopyError::InsufficientMemoryAllocated(usize::MAX, num_slices)
+        })?;
+        if bytes.len() < needed {
             return Err(ZeroCopyError::InsufficientMemoryAllocated(
-                bytes.len(),
-                num_slices,
+                bytes.len(),
+                needed,
             ));
         }
         let mut slices = Vec::with_capacity(num_slices);

Based on learnings.

program-libs/compressed-account/src/address.rs (1)

7-17: Remove heap allocation in legacy address derivation for no_std compatibility

concat() allocates. Use the infallible const-array helper you already import; this also aligns with the new no_std direction.

 pub fn derive_address_legacy(
     merkle_tree_pubkey: &Pubkey,
     seed: &[u8; 32],
 ) -> Result<[u8; 32], CompressedAccountError> {
-    let hash = hash_to_bn254_field_size_be(
-        [merkle_tree_pubkey.as_ref(), seed.as_ref()]
-            .concat()
-            .as_slice(),
-    );
+    let slices = [merkle_tree_pubkey.as_ref(), seed.as_ref()];
+    let hash = hashv_to_bn254_field_size_be_const_array::<3>(&slices)
+        .expect("hashv_to_bn254_field_size_be_const_array::<3> should be infallible for Keccak");
     Ok(hash)
 }
program-libs/compressed-account/src/instruction_data/invoke_cpi.rs (1)

36-49: Fix compile error and encoding divergence in LightInstructionData::data()

The #[cfg(feature = "alloc")] guard allows this method to compile only when the struct derives BorshSerialize, but when all(feature = "std", feature = "anchor") is true, the struct derives AnchorSerialize instead—causing a compile error. Additionally, the 4-byte length prefix (line 45) diverges from the trait default, which encodes only discriminator || payload without a length byte.

Fix:

 impl LightInstructionData for InstructionDataInvokeCpi {
-    #[cfg(feature = "alloc")]
+    #[cfg(all(feature = "alloc", not(feature = "anchor")))]
     fn data(&self) -> Result<Vec<u8>, crate::CompressedAccountError> {
         use borsh::BorshSerialize;
         let inputs = self
             .try_to_vec()
             .map_err(|_| crate::CompressedAccountError::InvalidArgument)?;
-        let mut data = Vec::with_capacity(12 + inputs.len());
+        let mut data = Vec::with_capacity(8 + inputs.len());
         data.extend_from_slice(self.discriminator());
-        data.extend_from_slice(&(inputs.len() as u32).to_le_bytes());
         data.extend_from_slice(inputs.as_slice());
         Ok(data)
     }
 }

When feature = "anchor" is enabled, the struct derives AnchorSerialize and the trait default implementation (which uses AnchorSerialize::try_to_vec()) applies.

program-libs/compressed-account/src/lib.rs (2)

41-44: Fix typos in user-facing error messages

“thresold” → “threshold”; “lenght” → “length”.

-    #[error("Invalid rollover thresold")]
+    #[error("Invalid rollover threshold")]
     InvalidRolloverThreshold,
-    #[error("Invalid input lenght")]
+    #[error("Invalid input length")]
     InvalidInputLength,

149-160: Panicking From conversions accept untrusted input without validation

TreeType::from calls receive account data bytes without length checks or value validation:

  • remaining_account_checks.rs:90 extracts bytes [8..16] without verifying data length ≥ 16
  • context.rs:139 extracts bytes [8..16] without verifying data length ≥ 16

QueueType::from at queue.rs:515 receives a u64 parameter without pre-validation before conversion.

All three conversion sites will panic on invalid values. Use TryFrom, validate inputs before calling From, or document safety invariants if values are guaranteed valid.

sdk-libs/sdk-pinocchio/src/cpi/v1/invoke.rs (1)

128-133: BorshSerialize impl uses std::io::Write — breaks no_std

Use borsh::io::Write to be portable (borsh defines its own Writer in no_std). Also align the Result type.

-impl BorshSerialize for LightSystemProgramCpi {
-    fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
-        self.instruction_data.serialize(writer)
-    }
-}
+impl BorshSerialize for LightSystemProgramCpi {
+    fn serialize<W: borsh::io::Write>(&self, writer: &mut W) -> borsh::io::Result<()> {
+        self.instruction_data.serialize(writer)
+    }
+}
🧹 Nitpick comments (35)
sdk-libs/macros/src/program.rs (1)

15-15: Consider restoring the Debug derive.

The removal of Debug from InstructionParam reduces debuggability of the macro without providing clear benefits. Since this is a procedural macro crate that runs at compile-time with full std support, there's no technical reason to omit Debug. Having Debug on internal types significantly aids troubleshooting macro transformation issues during development.

Apply this diff to restore the Debug derive:

-#[derive(Clone)]
+#[derive(Clone, Debug)]
program-libs/hasher/tests/data_hasher.rs (3)

2-2: Simplify Vec import.

The explicit use std::vec::Vec; can be simplified to use std::vec; or removed entirely since Vec is available in the prelude for std environments.

Apply this diff:

-use std::vec::Vec;
+use std::vec;

Or simply remove the import since Vec is in the prelude.


48-55: Simplify hash initialization.

The initialization pattern creates the arrays, then immediately shadows them with mutable bindings. This can be simplified by declaring them as mutable directly.

Apply this diff:

-    let hash1 = [0u8; 32];
-    let hash2 = [0u8; 32];
-
-    let mut hash1 = hash1;
+    let mut hash1 = [0u8; 32];
     hash1[31] = 1;
 
-    let mut hash2 = hash2;
+    let mut hash2 = [0u8; 32];
     hash2[31] = 2;

84-134: Reduce code duplication across match arms.

Each match arm contains nearly identical code: construct array → hash → build expected slices → verify. The only differences are the array size and clone count. This duplication makes the test harder to maintain.

Consider extracting the common verification logic:

+        let verify_hash = |arr: &[TestHashable]| {
+            let expected_slices: Vec<&[u8]> =
+                expected_inputs.iter().map(|h| h.as_slice()).collect();
+            let expected_hash = Poseidon::hashv(&expected_slices).unwrap();
+            expected_hash
+        };
+
         match size {
             1 => {
                 let arr: [TestHashable; 1] = [array_slice[0].clone()];
                 let hash_result = arr.hash::<Poseidon>().unwrap();
-
-                let expected_slices: Vec<&[u8]> =
-                    expected_inputs.iter().map(|h| h.as_slice()).collect();
-                let expected_hash = Poseidon::hashv(&expected_slices).unwrap();
-
-                assert_eq!(hash_result, expected_hash);
+                assert_eq!(hash_result, verify_hash(&arr));
             }
             // Similar for cases 2-4...

Alternatively, use a macro to generate the test cases or reconsider whether the match-based approach is necessary.

sdk-libs/sdk-types/src/cpi_accounts/config.rs (1)

30-32: Consider renaming method to avoid field name collision.

The method cpi_signer() returns a [u8; 32] by accessing self.cpi_signer.cpi_signer, where cpi_signer is both the field name and the method name. This creates ambiguity and makes the code harder to read. Consider renaming the method to cpi_signer_pubkey() or get_cpi_signer() for clarity.

-    pub fn cpi_signer(&self) -> [u8; 32] {
+    pub fn cpi_signer_pubkey(&self) -> [u8; 32] {
         self.cpi_signer.cpi_signer
     }
forester-utils/Cargo.toml (1)

21-21: Consider feature-gating std or documenting std requirement.

The forester-utils crate unconditionally enables std for light-compressed-account, which limits potential reuse in no_std contexts. If this library is intentionally std-only (for forester binary support), consider documenting this requirement. Otherwise, gate the std feature behind a feature flag for flexibility.

If std is required, consider adding documentation:

[package]
name = "forester-utils"
# ...
# Note: This crate requires std and is designed for use with the forester binary

Or add feature propagation:

 [features]
-default = ["v2"]
+default = ["v2", "std"]
+std = ["light-compressed-account/std"]
 devenv = ["v2", "light-client/devenv", "light-prover-client/devenv"]
program-tests/system-cpi-test/src/sdk.rs (1)

46-69: Typo in field name: readonly_adresses

Consider renaming readonly_adresses → read_only_addresses to avoid confusion; provide a deprecation alias if public.

Is this field part of any public/test API consumed downstream? If yes, I can propose a minimal deprecation shim.

program-libs/compressed-account/src/instruction_data/zero_copy.rs (1)

222-239: Guard against malformed length to avoid panic

split_at(u64::from(*len) as usize) can panic on short buffers. Return ZeroCopyError instead.

-        let (data, bytes) = bytes.split_at(u64::from(*len) as usize);
+        let data_len = u64::from(*len) as usize;
+        if bytes.len() < data_len {
+            return Err(ZeroCopyError::InvalidConversion);
+        }
+        let (data, bytes) = bytes.split_at(data_len);
program-libs/hasher/tests/hash_chain.rs (2)

105-118: Redundant cfg gate

File is already gated with #![cfg(feature = "poseidon")]; the inner #[cfg(feature = "poseidon")] block is unnecessary.

-    #[cfg(feature = "poseidon")]
-    {
+    {
         // ...
     }

7-16: Minor: test descriptions and labels

Duplicate “2. Functional” label and verbose comments; simplify to keep tests focused.

Also applies to: 40-58, 60-78

program-libs/hasher/Cargo.toml (2)

17-18: Guard hash features on Solana target.

sha256/keccak map to sha2/sha3 only on non‑Solana targets. Enabling them on BPF will silently drop those deps. Add a compile‑time guard (or an alternative backend) to prevent foot‑guns.

In any common module (e.g., lib.rs), add:

#[cfg(all(target_os = "solana", feature = "sha256"))]
compile_error!("feature 'sha256' is not supported on target_os=solana. Use the Solana syscall backend or disable this feature.");

#[cfg(all(target_os = "solana", feature = "keccak"))]
compile_error!("feature 'keccak' is not supported on target_os=solana. Use the Solana syscall backend or disable this feature.");

Optionally, wire Solana keccak via a separate feature (e.g., solana-keccak) if you have a syscall-based implementation.

Also applies to: 35-36


10-13: Consider changing default = [] for better external library design.

Internal workspace dependents are already protected by root-level default-features = false, but external consumers pulling light-hasher from crates.io without explicit default-features = false would inadvertently include std. For a program library, defaulting to empty features is the recommended approach.

program-libs/compressed-account/src/instruction_data/compressed_proof.rs (1)

89-94: Prefer From<ValidityProof> for Option<CompressedProof> over Into

This removes the clippy allow and is the idiomatic direction.

Apply this diff:

-#[allow(clippy::from_over_into)]
-impl Into<Option<CompressedProof>> for ValidityProof {
-    fn into(self) -> Option<CompressedProof> {
-        self.0
-    }
-}
+impl From<ValidityProof> for Option<CompressedProof> {
+    fn from(v: ValidityProof) -> Option<CompressedProof> {
+        v.0
+    }
+}

Based on learnings.

sdk-libs/client/Cargo.toml (1)

46-46: Light-event feature flags: ensure std/alloc alignment

Client is a std crate. If light-event defaults to alloc/no_std, explicitly enable its std feature (if provided) to avoid missing impls/serde paths.

Check light-event’s Cargo.toml features and, if applicable, change to:

light-event = { workspace = true, features = ["std"] }

If std is default there, this is fine as-is; please confirm.

sdk-libs/sdk/src/lib.rs (1)

163-166: Macro re-exports OK; consider disambiguating LightDiscriminator

You export both a trait alias (LightDiscriminator from light_account_checks) and a derive macro (LightDiscriminator from light_sdk_macros) under the same name at crate root. Legal (separate namespaces) but confusing in docs/imports. Consider renaming the trait re-export (e.g., LightDiscriminatorTrait) or nest traits under a module to reduce ambiguity.

sdk-libs/client/src/rpc/client.rs (2)

658-659: Guard against empty batch vector

Indexing e[0] can panic if the vector is empty. Prefer first().

-        let event = parsed_event.map(|(e, signature, slot)| (e[0].event.clone(), signature, slot));
+        let event = parsed_event.and_then(|(e, signature, slot)| {
+            e.first().map(|b| (b.event.clone(), signature, slot))
+        });

295-296: Replace println! with tracing

Use warn!/debug! for consistency with tracing.

-                            println!("Parsed instructions are not implemented yet");
+                            tracing::warn!("Parsed instructions are not implemented yet");
sdk-libs/event/src/parse.rs (1)

519-537: Unchecked indexing may panic; add bounds checks

  • new_addresses: accounts[x.tree_index as usize]
  • create_nullifier_queue_indices: accounts[x.merkle_context.merkle_tree_pubkey_index as usize]

If indexes are malformed, this panics. Prefer get() with a clear ParseIndexerEventError.

-                mt_pubkey: associated_instructions.accounts[x.tree_index as usize],
+                mt_pubkey: *associated_instructions
+                    .accounts
+                    .get(x.tree_index as usize)
+                    .ok_or(ParseIndexerEventError::DeserializeSystemInstructionError)?,

And similarly guard merkle_tree_pubkey_index lookups in create_nullifier_queue_indices().

Also applies to: 612-621

program-libs/account-checks/src/packed_accounts.rs (1)

16-16: Reconsider _name prefix given conditional usage.

The underscore prefix conventionally indicates an unused parameter, but _name is actually used within the cfg(feature = "std") logging block on Line 23. While the code compiles correctly, this may confuse maintainers who expect underscore-prefixed parameters to be truly unused.

Consider one of these alternatives:

  1. Keep name without the underscore and add #[allow(unused_variables)] when std is disabled
  2. Use a more explicit name like name_for_logging to clarify its purpose

Apply this diff if choosing option 1:

-    pub fn get(&self, index: usize, _name: &str) -> Result<&A, AccountError> {
+    #[cfg_attr(not(feature = "std"), allow(unused_variables))]
+    pub fn get(&self, index: usize, name: &str) -> Result<&A, AccountError> {
         if index >= self.accounts.len() {
             #[cfg(feature = "std")]
             {
                 let location = Location::caller();
                 solana_msg::msg!(
                     "ERROR: Not enough accounts. Requested '{}' at index {} but only {} accounts available. {}:{}:{}",
-                    _name, index, self.accounts.len(), location.file(), location.line(), location.column()
+                    name, index, self.accounts.len(), location.file(), location.line(), location.column()
                 );
             }
sdk-libs/sdk-types/src/address.rs (1)

65-66: Avoid unwrap() in hashing paths (panic in no_std).

Replace unwrap() with expect() carrying context, or return a Result upstream. This prevents aborts if the slice exceeds N.

-        hashv_to_bn254_field_size_be_const_array::<2>(input.as_slice()).unwrap()
+        hashv_to_bn254_field_size_be_const_array::<2>(input.as_slice())
+            .expect("derive_address_from_seed: inputs must not exceed N")
-        AddressSeed(hashv_to_bn254_field_size_be_const_array::<17>(seeds).unwrap())
+        AddressSeed(
+            hashv_to_bn254_field_size_be_const_array::<17>(seeds)
+                .expect("v2::derive_address_seed: at most 16 seeds + 1 bump"),
+        )

Also applies to: 116-118

program-tests/create-address-test-program/src/lib.rs (1)

143-145: Minor: avoid constructing config just to pass bump.

You can use the const directly to reduce work.

-        let cpi_config = CpiAccountsConfig::new(crate::LIGHT_CPI_SIGNER);
-        invoke_light_system_program(&account_infos, instruction, cpi_config.bump())?;
+        invoke_light_system_program(&account_infos, instruction, crate::LIGHT_CPI_SIGNER.bump)?;
program-libs/macros/src/cpi_signer.rs (1)

118-123: Macro output type relies on CpiSigner in scope; enforce with path or clarify docs.

Current code emits CpiSigner { .. } unqualified. Either emit a fully qualified path or make the requirement explicit in docs across all entry points.

Consider:

  • Document: “CpiSigner must be in scope (e.g., use light_sdk_types::CpiSigner or use light_sdk::cpi::CpiSigner).”
  • Or emit ::light_sdk_types::CpiSigner { ... } if that’s the canonical type.
sdk-libs/sdk-pinocchio/src/cpi/v2/invoke.rs (1)

27-43: Unify ProgramError aliasing and conversion for readability.

Use a local alias and From<LightSdkError> for ProgramError to keep conversions consistent.

-    fn with_light_account<A>(
+    fn with_light_account<A>(
         mut self,
         account: crate::LightAccount<'_, A>,
     ) -> Result<Self, pinocchio::program_error::ProgramError>
@@
-        let account_info = account
-            .to_account_info()
-            .map_err(|e| pinocchio::program_error::ProgramError::Custom(u64::from(e) as u32))?;
+        use pinocchio::program_error::ProgramError;
+        let account_info = account
+            .to_account_info()
+            .map_err(|e| ProgramError::from(crate::error::LightSdkError::from(e)))?;

Also applies to: 112-116

program-libs/macros/src/lib.rs (1)

68-85: Docs: compute time is compile-time, not runtime.

These proc-macros precompute values during compilation. Update wording to avoid confusion.

-/// Derives a Light Protocol CPI signer PDA at runtime
+/// Derives a Light Protocol CPI signer PDA at compile time
@@
-/// for the given program ID. Uses `solana_pubkey` with `solana` feature,
-/// otherwise uses `pinocchio` (default).
+/// for the given program ID.
program-libs/hasher/src/hash_to_field_size.rs (2)

43-54: Library code: replace unwrap with expect (or return Result) and simplify push loop

Unwraps can panic in production. Either (a) keep the infallible signature and use expect(...) with rationale, or (b) introduce a Result-returning variant and keep this as a thin wrapper. Also, you can replace the for_each push loop with extend_from_slice.

Apply this diff:

 pub fn hashv_to_bn254_field_size_be(bytes: &[&[u8]]) -> [u8; 32] {
     let mut slices = Vec::with_capacity(bytes.len() + 1);
-    bytes.iter().for_each(|x| slices.push(*x));
+    slices.extend_from_slice(bytes);
     let bump_seed = [HASH_TO_FIELD_SIZE_SEED];
     slices.push(bump_seed.as_slice());
-    // SAFETY: cannot panic Hasher::hashv returns an error because Poseidon can panic.
-    let mut hashed_value: [u8; 32] = Keccak::hashv(&slices).unwrap();
+    let mut hashed_value: [u8; 32] =
+        Keccak::hashv(&slices).expect("Keccak::hashv is infallible for Keccak backend");

If you prefer the Result path, I can propose a hashv_to_bn254_field_size_be_result(...) -> Result<[u8;32], HasherError> and have this function call .unwrap_or_else(|_| unreachable!()).


57-67: Same unwrap concern here; align with the approach chosen above

Use expect(...) (or add a Result variant) instead of unwrap() to avoid panics.

-    let mut hashed_value: [u8; 32] = Keccak::hashv(&slices).unwrap();
+    let mut hashed_value: [u8; 32] =
+        Keccak::hashv(&slices).expect("Keccak::hashv is infallible for Keccak backend");
program-libs/compressed-account/src/instruction_data/with_readonly.rs (2)

61-81: Avoid panics in From<...>; prefer a fallible conversion

Two .expect("...") calls will panic if data is missing. Replace with TryFrom returning CompressedAccountError (or a dedicated error) to keep invariants enforced without panicking.

Example:

-impl From<PackedCompressedAccountWithMerkleContext> for InAccount {
-    fn from(value: PackedCompressedAccountWithMerkleContext) -> Self {
-        Self {
-            discriminator: value.compressed_account.data.as_ref().expect("Into InAccount expected data to exist.").discriminator,
-            merkle_context: value.merkle_context,
-            data_hash: value.compressed_account.data.as_ref().expect("Into InAccount expected data to exist.").data_hash,
-            ...
-        }
-    }
-}
+impl TryFrom<PackedCompressedAccountWithMerkleContext> for InAccount {
+    type Error = CompressedAccountError;
+    fn try_from(value: PackedCompressedAccountWithMerkleContext) -> Result<Self, Self::Error> {
+        let data = value
+            .compressed_account
+            .data
+            .as_ref()
+            .ok_or(CompressedAccountError::InvalidArgument)?;
+        Ok(Self {
+            discriminator: data.discriminator,
+            merkle_context: value.merkle_context,
+            data_hash: data.data_hash,
+            root_index: value.root_index,
+            lamports: value.compressed_account.lamports,
+            address: value.compressed_account.address,
+        })
+    }
+}

613-627: Gate tests for serializer and std to avoid cfg-mismatch build failures

These tests use BorshSerialize and std RNG/println. Add not(feature="anchor") and feature="std" to the cfg so they don’t compile under Anchor-only or no_std builds.

-#[cfg(all(not(feature = "pinocchio"), feature = "new-unique"))]
+#[cfg(all(
+    not(feature = "pinocchio"),
+    feature = "new-unique",
+    not(feature = "anchor"),
+    feature = "std"
+))]
program-libs/compressed-account/src/address.rs (1)

19-30: Optional: replace unwrap with expect for clearer invariants

If Keccak path is truly infallible, prefer expect("...") over a bare unwrap() to document the assumption.

-    hashv_to_bn254_field_size_be_const_array::<4>(&slices).unwrap()
+    hashv_to_bn254_field_size_be_const_array::<4>(&slices)
+        .expect("hashv_to_bn254_field_size_be_const_array::<4> should be infallible for Keccak")
program-libs/compressed-account/src/compressed_account.rs (2)

325-339: Avoid pattern-matching parameter to take ownership of Pubkey

Prefer borrowing directly to avoid needless copy/readability oddity.

-    pub fn hash(
-        &self,
-        &merkle_tree_pubkey: &Pubkey,
-        leaf_index: &u32,
-        is_batched: bool,
-    ) -> Result<[u8; 32], CompressedAccountError> {
+    pub fn hash(
+        &self,
+        merkle_tree_pubkey: &Pubkey,
+        leaf_index: &u32,
+        is_batched: bool,
+    ) -> Result<[u8; 32], CompressedAccountError> {
-    pub fn hash(
-        &self,
-        &merkle_tree_pubkey: &[u8; 32],
-        leaf_index: &u32,
-        is_batched: bool,
-    ) -> Result<[u8; 32], CompressedAccountError> {
+    pub fn hash(
+        &self,
+        merkle_tree_pubkey: &[u8; 32],
+        leaf_index: &u32,
+        is_batched: bool,
+    ) -> Result<[u8; 32], CompressedAccountError> {

Also applies to: 364-377


148-157: Consider also deriving ZeroCopy for PackedReadOnlyCompressedAccount

All fields look POD/ZeroCopy-compatible (repr(C), fixed-size fields, PackedMerkleContext is ZeroCopy). Adding ZeroCopy would allow read-only views without mutation APIs.

-#[derive(Debug, PartialEq, Default, Clone, ZeroCopyMut)]
+#[derive(Debug, PartialEq, Default, Clone, ZeroCopy, ZeroCopyMut)]
 pub struct PackedReadOnlyCompressedAccount {
program-libs/compressed-account/src/lib.rs (1)

27-30: Re-export hash_to_bn254_field_size_be without requiring alloc

hash_to_bn254_field_size_be doesn’t allocate; gate only the vector-based variant behind alloc.

-pub use light_hasher::hash_to_field_size::{
-    hash_to_bn254_field_size_be, hashv_to_bn254_field_size_be,
-};
+pub use light_hasher::hash_to_field_size::hash_to_bn254_field_size_be;
+#[cfg(feature = "alloc")]
+pub use light_hasher::hash_to_field_size::hashv_to_bn254_field_size_be;
program-tests/utils/src/compressed_account_pack.rs (3)

70-75: Avoid building a Vec just to take [0]

Pack single MerkleContext without allocating an intermediate Vec.

-                merkle_context: pack_merkle_context(&[merkle_context], remaining_accounts)[0],
+                merkle_context: {
+                    let m = merkle_context;
+                    PackedMerkleContext {
+                        leaf_index: m.leaf_index,
+                        merkle_tree_pubkey_index: pack_account(&m.merkle_tree_pubkey, remaining_accounts),
+                        queue_pubkey_index: pack_account(&m.queue_pubkey, remaining_accounts),
+                        prove_by_index: m.prove_by_index,
+                    }
+                },
-            merkle_context: pack_merkle_context(&[x.merkle_context], remaining_accounts)[0],
+            merkle_context: {
+                let m = x.merkle_context;
+                PackedMerkleContext {
+                    leaf_index: m.leaf_index,
+                    merkle_tree_pubkey_index: pack_account(&m.merkle_tree_pubkey, remaining_accounts),
+                    queue_pubkey_index: pack_account(&m.queue_pubkey, remaining_accounts),
+                    prove_by_index: m.prove_by_index,
+                }
+            },
-        .map(|merkle_context| PackedMerkleContext {
+        .map(|merkle_context| PackedMerkleContext {
             leaf_index: merkle_context.leaf_index,
             merkle_tree_pubkey_index: pack_account(
                 &merkle_context.merkle_tree_pubkey,
                 remaining_accounts,
             ),
             queue_pubkey_index: pack_account(&merkle_context.queue_pubkey, remaining_accounts),
             prove_by_index: merkle_context.prove_by_index,
         })

Also applies to: 236-241, 95-111


43-51: Deduplicate pack_account and pack_pubkey_usize

Both do the same. Keep one and call it everywhere to avoid divergence.

-pub fn pack_pubkey_usize(pubkey: &Pubkey, hash_set: &mut HashMap<Pubkey, usize>) -> u8 {
-    match hash_set.get(pubkey) {
-        Some(index) => (*index) as u8,
-        None => {
-            let index = hash_set.len();
-            hash_set.insert(*pubkey, index);
-            index as u8
-        }
-    }
-}
+pub fn pack_pubkey_usize(pubkey: &Pubkey, map: &mut HashMap<Pubkey, usize>) -> u8 {
+    pack_account(pubkey, map)
+}

Also applies to: 219-228


244-326: Tests are good; add cases for length mismatch and index overflow

Add tests that assert length mismatch panics and that inserting 256+ unique pubkeys trips the overflow guard.

Comment on lines +171 to +183
light-hasher = { path = "program-libs/hasher", version = "4.0.0", default-features = false }
light-macros = { path = "program-libs/macros", version = "2.1.0" }
light-merkle-tree-reference = { path = "program-tests/merkle-tree", version = "3.0.1" }
light-heap = { path = "program-libs/heap", version = "2.0.0" }
light-prover-client = { path = "prover/client", version = "3.0.0" }
light-sdk = { path = "sdk-libs/sdk", version = "0.15.0" }
light-sdk-pinocchio = { path = "sdk-libs/sdk-pinocchio", version = "0.13.0" }
light-sdk-macros = { path = "sdk-libs/macros", version = "0.15.0" }
light-sdk-types = { path = "sdk-libs/sdk-types", version = "0.15.0" }
light-compressed-account = { path = "program-libs/compressed-account", version = "0.5.0" }
light-sdk-types = { path = "sdk-libs/sdk-types", version = "0.15.0", default-features = false }
light-compressed-account = { path = "program-libs/compressed-account", version = "0.5.0", default-features = false }
light-compressible = { path = "program-libs/compressible", version = "0.1.0" }
light-ctoken-types = { path = "program-libs/ctoken-types", version = "0.1.0" }
light-account-checks = { path = "program-libs/account-checks", version = "0.4.0" }
light-account-checks = { path = "program-libs/account-checks", version = "0.4.0", default-features = false }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Major refactoring: default-features = false for 4 core crates.

Disabling default features for light-hasher, light-sdk-types, light-compressed-account, and light-account-checks is a significant architectural change enabling no_std support. This requires ALL downstream crate dependencies to explicitly enable needed features or risk compilation failure.

Verify that every workspace member depending on these 4 crates has explicit feature enablement where required. The 7 provided files show this pattern, but a comprehensive audit of all workspace members is needed to prevent regressions.


🏁 Script executed:

#!/bin/bash
# Description: Verify all workspace members properly handle default-features = false for core crates.
# Find all workspace members and check if they depend on the 4 affected crates without explicit features.

affected_crates="light-hasher|light-sdk-types|light-compressed-account|light-account-checks"

echo "=== Checking for potential unhandled feature defaults in workspace members ==="
fd 'Cargo.toml' --type f | while read -r toml; do
  # Skip the root Cargo.toml (already reviewed)
  if [[ "$toml" == "Cargo.toml" ]]; then
    continue
  fi
  
  # Search for dependencies on the 4 affected crates
  if grep -E "^\s*(light-hasher|light-sdk-types|light-compressed-account|light-account-checks)\s*=" "$toml" > /dev/null; then
    # Check if the line has explicit features
    if grep -E "^\s*(light-hasher|light-sdk-types|light-compressed-account|light-account-checks)\s*=.*features\s*=" "$toml" > /dev/null; then
      # Has explicit features, OK
      :
    else
      # Depends on affected crate WITHOUT explicit features - potential issue
      echo "⚠️  $toml depends on affected crate(s) without explicit features:"
      grep -E "^\s*(light-hasher|light-sdk-types|light-compressed-account|light-account-checks)\s*=" "$toml"
    fi
  fi
done

Length of output: 914


I've identified a critical issue. The script found 5 workspace members that depend on the 4 affected crates using { workspace = true } without explicit feature enablement. These will inherit default-features = false from the root workspace.

Let me verify if the 4 core crates are actually designed to compile with no features enabled:

Now I need to verify if the 4 core crates are designed to compile with no features. Let me check their feature definitions:Let me check the actual Cargo.toml files of the 4 core crates to see their feature definitions:


---

🏁 Script executed:

```shell
#!/bin/bash
# Check feature definitions in the 4 affected core crates
echo "=== Checking feature definitions in core crates ==="

for crate_dir in "program-libs/hasher" "program-libs/account-checks" "program-libs/compressed-account" "sdk-libs/sdk-types"; do
  if [ -f "$crate_dir/Cargo.toml" ]; then
    echo ""
    echo "--- $crate_dir/Cargo.toml ---"
    # Extract [features] section and show what features are defined
    sed -n '/^\[features\]/,/^\[/p' "$crate_dir/Cargo.toml" | head -20
  else
    echo "⚠️  $crate_dir/Cargo.toml not found"
  fi
done

Length of output: 1493


Now I need to check if the 5 downstream crates can actually compile with the 4 core crates having default-features = false. Let me inspect whether they have feature gates or conditional code that requires those features:


---

🏁 Script executed:

```shell
#!/bin/bash
# Check the 5 affected workspace members for code that depends on features from core crates
echo "=== Checking if affected crates can compile with default-features = false ==="

# The 5 affected crates
affected_members=(
  "program-libs/bloom-filter"
  "program-libs/compressible"
  "program-libs/hash-set"
  "program-tests/merkle-tree"
  "sdk-libs/sdk-types"
)

for member in "${affected_members[@]}"; do
  if [ -d "$member" ]; then
    echo ""
    echo "--- $member ---"
    
    # Check for feature-gated code that references the core crates
    echo "Checking for conditional imports or usage patterns..."
    rg -n 'use light_(hasher|account_checks|compressed_account)' "$member/src" --type rust -A2 -B2 2>/dev/null | head -15 || echo "  (no matches)"
    
    # Check for #[cfg(feature = ...)] patterns
    if rg -n '#\[cfg\(feature' "$member/src" --type rust 2>/dev/null | grep -qE 'solana|std|alloc'; then
      echo "⚠️  Found feature-gated code:"
      rg -n '#\[cfg\(feature' "$member/src" --type rust 2>/dev/null | head -10
    else
      echo "  (no feature-gated patterns found)"
    fi
  fi
done

Length of output: 4630


Based on the investigation, I've identified critical compilation issues. The workspace change sets default-features = false for 4 core crates, but those crates still have default = ["std"] or default = ["alloc"] defined. Additionally, 5 workspace members depend on these crates via { workspace = true } without explicit feature enablement, meaning they inherit the default-features = false setting.

The critical problems:

  1. program-tests/merkle-tree uses light_hasher APIs in main code (lib.rs line 5, indexed.rs line 3) but won't have access to the std feature required to compile those APIs.

  2. program-libs/compressible uses light_account_checks in main code (config.rs line 2) without feature gates—no guarantee it works with no features enabled.

  3. sdk-libs/sdk-types imports from light_compressed_account in main code with conditionally-gated std/alloc code, but won't have those features enabled.

The 4 affected crates (light-hasher, light-account-checks, light-compressed-account, sdk-types) still have default = ["std"] or default = ["alloc"]. When default-features = false is applied at workspace level, these defaults are ignored entirely. The crates will compile with NO features unless their code is refactored to not require std/alloc internally.

Critical fixes required:

  1. Either remove the default = [...] lines from the 4 core crates' Cargo.toml files (change to default = []), OR
  2. Explicitly enable required features in each of the 5 affected workspace members' dependencies (change { workspace = true } to { workspace = true, features = ["std"] } etc.)

The refactoring is incomplete—the PR must ensure all affected crates either compile without defaults or explicitly re-enable needed features.

🤖 Prompt for AI Agents
Cargo.toml lines 171-183: the workspace sets default-features = false so crates
that still declare default = ["std"] or ["alloc"] (light-hasher,
light-account-checks, light-compressed-account, sdk-types) end up built with NO
features, breaking dependents; fix by either removing or clearing the default
feature declarations in those four crates' Cargo.toml (change default =
["std"/"alloc"] to default = []) OR update each workspace-dependent entry that
uses { workspace = true } to explicitly enable the required features (e.g., {
workspace = true, features = ["std"] } or ["alloc"] as appropriate) for all
affected members (including program-tests/merkle-tree,
program-libs/compressible, sdk-libs/sdk-types and the other two workspace
dependents), and ensure any conditional code is either refactored or properly
feature-gated.

Comment on lines +10 to +11
default = ["std"]
std = []
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Violates coding guideline: default build should enable none.

The coding guideline explicitly states for program-libs/account-checks/**/Cargo.toml: "Define features solana, pinocchio, and test-only in Cargo.toml; default build should enable none." Setting default = ["std"] contradicts this requirement.

Apply this diff to align with the guideline:

-default = ["std"]
+default = []
 std = []

Based on coding guidelines.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
default = ["std"]
std = []
default = []
std = []
🤖 Prompt for AI Agents
In program-libs/account-checks/Cargo.toml around lines 10 to 11, the Cargo.toml
sets default = ["std"] which violates the guideline that the default build
should enable none; update the file to set default = [] and ensure the features
section explicitly defines the features "solana", "pinocchio", and "test-only"
(with any optional dependencies mapped under those feature names) so the default
build enables no features.

Comment on lines +5 to +12
#[cfg_attr(
all(feature = "std", feature = "anchor"),
derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
)]
#[cfg_attr(
not(feature = "anchor"),
derive(borsh::BorshDeserialize, borsh::BorshSerialize)
)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Serialization derives disappear when anchor is enabled without std

Current cfg gating applies Anchor derives only with all(std, anchor) and Borsh derives when not(anchor). If anchor is enabled on a no_std target (typical for BPF), neither derive applies, leaving these types without any serialization traits. Fix the gating to choose by anchor alone, or explicitly forbid the unsupported combo.

Apply this diff to both structs:

-#[cfg_attr(
-    all(feature = "std", feature = "anchor"),
-    derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
-)]
-#[cfg_attr(
-    not(feature = "anchor"),
-    derive(borsh::BorshDeserialize, borsh::BorshSerialize)
-)]
+#[cfg_attr(
+    feature = "anchor",
+    derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
+)]
+#[cfg_attr(
+    not(feature = "anchor"),
+    derive(borsh::BorshDeserialize, borsh::BorshSerialize)
+)]

Optionally, if anchor && !std truly isn’t supported, add a guard near the top of this file:

#[cfg(all(feature = "anchor", not(feature = "std")))]
compile_error!("Enabling `anchor` without `std` is not supported for this crate configuration. Enable `std` or disable `anchor`.");

Based on learnings.

Please confirm which crate targets require anchor in no_std so we align gating across the workspace (Anchor/Pinocchio/Borsh interop).

Also applies to: 49-56


I'll verify this serialization derives issue by examining the file and checking the cfg patterns in the workspace.


---

🏁 Script executed:

```shell
#!/bin/bash
# Examine the specific file and lines in question
echo "=== File: compressed_proof.rs (lines 1-60) ==="
fd -t f compressed_proof.rs --exec head -60 {}

Length of output: 2072


🏁 Script executed:

#!/bin/bash
# Search for other cfg patterns with anchor and std in the codebase
echo "=== Searching for cfg patterns with 'anchor' and 'std' ==="
rg -n "cfg_attr.*anchor.*std|cfg_attr.*std.*anchor" -C 2 --type rust

Length of output: 58


🏁 Script executed:

#!/bin/bash
# Check Cargo.toml files for feature definitions
echo "=== Feature definitions in Cargo.toml files ==="
fd -t f "Cargo.toml" | head -20 | xargs -I {} sh -c 'echo "=== {} ===" && grep -A 20 "\[features\]" {} | head -25'

Length of output: 9508


🏁 Script executed:

#!/bin/bash
# Search for usage of anchor feature with no_std context
echo "=== Checking for no_std usage context ==="
rg -n "no_std|std.*=.*false" --type rust | head -20

Length of output: 458



---

🏁 Script executed:

```shell
#!/bin/bash
# Get full file to count lines and find all cfg_attr patterns
echo "=== Full file with line numbers ==="
cat -n program-libs/compressed-account/src/instruction_data/compressed_proof.rs

Length of output: 2928


🏁 Script executed:

#!/bin/bash
# Search for anchor feature usage patterns across workspace
echo "=== Checking for anchor feature usage (potentially without std) ==="
rg -n "features.*anchor|anchor.*features" --type rust --type toml | grep -v "anchor-lang" | head -30

Length of output: 3375


🏁 Script executed:

#!/bin/bash
# Check if there are test configs using anchor without std
echo "=== Workspace tests or configs with anchor feature ==="
find . -name "Cargo.toml" -exec grep -l "anchor" {} \; | xargs -I {} sh -c 'echo "=== {} ===" && grep -B 2 -A 2 "\[features\]" {} | grep -A 15 "anchor"'

Length of output: 2012


Fix serialization cfg gating to prevent derives from disappearing when anchor is enabled without std

The cfg conditions create a serialization gap: when anchor feature is enabled without std, neither all(feature = "std", feature = "anchor") nor not(feature = "anchor") matches, leaving these types without any serialization traits. This breaks any anchor+no_std usage (e.g., no_std BPF programs using Anchor types).

The suggested fix correctly changes the condition to feature = "anchor" alone. Apply to both structs at lines 5-12 and 49-56:

-#[cfg_attr(
-    all(feature = "std", feature = "anchor"),
-    derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
-)]
-#[cfg_attr(
-    not(feature = "anchor"),
-    derive(borsh::BorshDeserialize, borsh::BorshSerialize)
-)]
+#[cfg_attr(
+    feature = "anchor",
+    derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
+)]
+#[cfg_attr(
+    not(feature = "anchor"),
+    derive(borsh::BorshDeserialize, borsh::BorshSerialize)
+)]

Optionally add a guard to forbid anchor+!std if unsupported:

#[cfg(all(feature = "anchor", not(feature = "std")))]
compile_error!("Enabling `anchor` without `std` is not supported for this crate. Enable `std` or disable `anchor`.");
🤖 Prompt for AI Agents
In program-libs/compressed-account/src/instruction_data/compressed_proof.rs
around lines 5-12 and 49-56, the current cfg_attr gating uses all(feature =
"std", feature = "anchor") and not(feature = "anchor") which leaves a gap when
anchor is enabled without std; change the derive gating to check feature =
"anchor" alone (i.e., #[cfg_attr(feature = "anchor",
derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize))]) for both
structs so they keep Anchor derives when anchor is enabled, and keep the borsh
derives under not(feature = "anchor"); optionally add a compile_error guard for
the unsupported combination (anchor enabled but std disabled) using
cfg(all(feature = "anchor", not(feature = "std"))) to surface a clear build-time
error.

Comment on lines +8 to +16
#[cfg_attr(
all(feature = "std", feature = "anchor"),
derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
)]
#[cfg_attr(
not(feature = "anchor"),
derive(borsh::BorshDeserialize, borsh::BorshSerialize)
)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, ZeroCopyMut)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Fix derive gating: Anchor should not depend on std.

cfg_attr(all(feature="std", feature="anchor"), …) drops derives when anchor is enabled without std. Align with no_std goals by deriving Anchor on feature="anchor" alone.

-#[cfg_attr(
-    all(feature = "std", feature = "anchor"),
-    derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
-)]
+#[cfg_attr(
+    feature = "anchor",
+    derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
+)]
 #[cfg_attr(
-    not(feature = "anchor"),
+    not(feature = "anchor"),
     derive(borsh::BorshDeserialize, borsh::BorshSerialize)
 )]

Search for similar gating elsewhere (should be synchronized with other instruction_data types).



---

🏁 Script executed:

```shell
#!/bin/bash
# Verify the specific file and find similar patterns
echo "=== Inspecting cpi_context.rs ==="
cat program-libs/compressed-account/src/instruction_data/cpi_context.rs | head -20

echo -e "\n=== Searching for similar cfg_attr patterns with all(feature=\"std\" ==="
rg -n 'all\(feature\s*=\s*"std".*feature\s*=\s*"anchor"' program-libs/compressed-account/

echo -e "\n=== Searching for any cfg_attr with both std and anchor features ==="
rg -nA2 'cfg_attr.*all.*"std"' program-libs/compressed-account/ | head -50

echo -e "\n=== Finding all instruction_data types ==="
fd -t f 'instruction_data' program-libs/compressed-account/src/ --exec ls -la {} \;

echo -e "\n=== List files in instruction_data directory ==="
find program-libs/compressed-account/src/instruction_data -type f -name "*.rs"

Length of output: 5416



---

🏁 Script executed:

```shell
#!/bin/bash
# Verify the problematic pattern in another file and check fix scope
echo "=== Inspecting data.rs (first occurrence) ==="
sed -n '8,20p' program-libs/compressed-account/src/instruction_data/data.rs

echo -e "\n=== Inspecting with_account_info.rs (first occurrence) ==="
sed -n '32,44p' program-libs/compressed-account/src/instruction_data/with_account_info.rs

echo -e "\n=== Check if review is scoped to single file or entire codebase ==="
# Count total instances in instruction_data directory only
echo "Instances in instruction_data/:"
rg -c 'all\(feature\s*=\s*"std".*feature\s*=\s*"anchor"' program-libs/compressed-account/src/instruction_data/ | grep -v ':0$'

Length of output: 1335


Fix derive gating pattern across all instruction_data types: Anchor should not depend on std.

The problematic pattern all(feature = "std", feature = "anchor") prevents Anchor derives from being applied when the anchor feature is enabled without std, breaking no_std support. This pattern appears 19 times across 6 instruction_data files:

  • cpi_context.rs (1 instance)
  • data.rs (9 instances)
  • with_account_info.rs (4 instances)
  • with_readonly.rs (2 instances)
  • compressed_proof.rs (2 instances)
  • invoke_cpi.rs (1 instance)

Apply the fix to all instances by changing the condition to feature = "anchor" alone:

 #[cfg_attr(
-    all(feature = "std", feature = "anchor"),
+    feature = "anchor",
     derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
 )]

This synchronizes the derive gating across all instruction_data types and aligns with the no_std design goal.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#[cfg_attr(
all(feature = "std", feature = "anchor"),
derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
)]
#[cfg_attr(
not(feature = "anchor"),
derive(borsh::BorshDeserialize, borsh::BorshSerialize)
)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, ZeroCopyMut)]
#[cfg_attr(
feature = "anchor",
derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
)]
#[cfg_attr(
not(feature = "anchor"),
derive(borsh::BorshDeserialize, borsh::BorshSerialize)
)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, ZeroCopyMut)]
🤖 Prompt for AI Agents
In program-libs/compressed-account/src/instruction_data/cpi_context.rs around
lines 8 to 16, the derive gating uses all(feature = "std", feature = "anchor")
which prevents Anchor derives when the crate is built no_std; change the
cfg_attr to only check feature = "anchor" (i.e., cfg_attr(feature = "anchor",
derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize))) so Anchor
derives are enabled regardless of std, and apply the same change consistently
across the other listed instruction_data files.

Comment on lines +219 to +228
pub fn pack_account(pubkey: &Pubkey, remaining_accounts: &mut HashMap<Pubkey, usize>) -> u8 {
match remaining_accounts.get(pubkey) {
Some(index) => *index as u8,
None => {
let next_index = remaining_accounts.len();
remaining_accounts.insert(*pubkey, next_index);
next_index as u8
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

u8 index overflow risk when >255 remaining accounts

Casting usize→u8 will wrap. Add a guard (assert in tests) to fail loudly.

-    match remaining_accounts.get(pubkey) {
+    match remaining_accounts.get(pubkey) {
         Some(index) => *index as u8,
         None => {
             let next_index = remaining_accounts.len();
+            assert!(next_index <= u8::MAX as usize, "remaining_accounts index overflow");
             remaining_accounts.insert(*pubkey, next_index);
             next_index as u8
         }
     }
-    match hash_set.get(pubkey) {
+    match hash_set.get(pubkey) {
         Some(index) => (*index) as u8,
         None => {
             let index = hash_set.len();
+            assert!(index <= u8::MAX as usize, "remaining_accounts index overflow");
             hash_set.insert(*pubkey, index);
             index as u8
         }
     }
-        vec.push(*remaining_accounts.get(pubkey).unwrap() as u8);
+        let idx = *remaining_accounts.get(pubkey).unwrap();
+        assert!(idx <= u8::MAX as usize, "remaining_accounts index overflow");
+        vec.push(idx as u8);

Also applies to: 43-51, 158-175

🤖 Prompt for AI Agents
In program-tests/utils/src/compressed_account_pack.rs around lines 219-228 (and
likewise at lines 43-51 and 158-175), the code casts usize→u8 which will wrap
when there are >255 accounts; add an explicit guard before casting: compute
next_index, assert or panic with a clear message if next_index > u8::MAX (or use
an if returning a Result with Err), then perform the cast only when safe so the
failure is loud and deterministic (also update tests to assert the guard
triggers for overflow).

pub mod assert_token_tx;
pub mod assert_transfer2;
pub mod batched_address_tree;
pub mod compressed_account_pack;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify no duplication with existing pack module.

A new compressed_account_pack module is being added alongside the existing pack module (line 43). Please confirm that these modules serve distinct purposes and that functionality is not duplicated.


🏁 Script executed:

#!/bin/bash
# Description: Compare exported symbols from pack and compressed_account_pack modules

echo "=== Checking pack module exports ==="
rg -n "^pub (fn|struct|enum|type|const)" program-tests/utils/src/pack.rs -A 1

echo "=== Checking compressed_account_pack module exports ==="
rg -n "^pub (fn|struct|enum|type|const)" program-tests/utils/src/compressed_account_pack.rs -A 1

Length of output: 2319


I need to verify if these are true duplicates by examining the actual implementations:

I need to verify if the implementations are actually duplicated by examining the code:


---

🏁 Script executed:

```shell
#!/bin/bash
# Compare implementations of overlapping functions

echo "=== pack.rs: pack_pubkey_usize implementation ==="
sed -n '211,220p' program-tests/utils/src/pack.rs

echo ""
echo "=== compressed_account_pack.rs: pack_pubkey_usize implementation ==="
sed -n '43,52p' program-tests/utils/src/compressed_account_pack.rs

echo ""
echo "=== pack.rs: pack_compressed_accounts implementation ==="
sed -n '159,183p' program-tests/utils/src/pack.rs

echo ""
echo "=== compressed_account_pack.rs: pack_compressed_accounts implementation ==="
sed -n '53,79p' program-tests/utils/src/compressed_account_pack.rs

echo ""
echo "=== Check lib.rs module declarations ==="
sed -n '36,50p' program-tests/utils/src/lib.rs

Length of output: 3149


Let me check if there's documentation or comments explaining this duplication:


---

🏁 Script executed:

```shell
#!/bin/bash
# Check for comments or documentation explaining module separation

echo "=== Check compressed_account_pack.rs header/comments ==="
head -20 program-tests/utils/src/compressed_account_pack.rs

echo ""
echo "=== Check pack.rs header/comments ==="
head -20 program-tests/utils/src/pack.rs

echo ""
echo "=== Search for usages of both modules in tests ==="
rg "use.*pack::" program-tests/ -c
rg "use.*compressed_account_pack::" program-tests/ -c

Length of output: 2276


Let me check which specific functions from each module are actually being used:


---

🏁 Script executed:

```shell
#!/bin/bash
# Check specific usages of functions from each module

echo "=== Functions from pack module used in tests ==="
rg "pack::" program-tests/ -n | head -20

echo ""
echo "=== Functions from compressed_account_pack module used in tests ==="
rg "compressed_account_pack::" program-tests/ -n | head -20

Length of output: 2436


Consolidate duplicate functions between pack and compressed_account_pack modules.

Verification confirms significant code duplication: 10 out of 11 functions in compressed_account_pack.rs are identical duplicates of functions in pack.rs, including pack_pubkey_usize, pack_compressed_accounts, pack_merkle_context, pack_new_address_params, and others. The pack module is more widely used (wildcard imports in e2e_test_env.rs and multiple direct imports), while compressed_account_pack has limited usage.

Options:

  • Consolidate functions into a single module and deprecate the other
  • Clarify if the separation serves a distinct architectural purpose (not apparent from current code)
  • Re-export overlapping functions from one module to avoid duplication
🤖 Prompt for AI Agents
In program-tests/utils/src/lib.rs around line 36, the compressed_account_pack
module duplicates most functions from pack; consolidate by moving the shared
implementations into the primary pack module (pack.rs) or a new common
submodule, then have compressed_account_pack either re-export those functions
(pub use crate::pack::{...};) or be removed and replaced with a thin deprecated
wrapper (add #[deprecated] to the wrapper module). Update all imports across the
repo (e2e_test_env.rs and others) to reference the consolidated module, run
cargo check/tests to ensure no breakage, and remove the duplicate
implementations from compressed_account_pack.rs.

Comment on lines +98 to 109
#[cfg(feature = "light-account")]
fn with_light_account<A>(
mut self,
account: crate::LightAccount<'_, A>,
) -> Result<Self, pinocchio::program_error::ProgramError>
where
A: BorshSerialize
+ BorshDeserialize
+ LightDiscriminator
A: crate::BorshSerialize
+ borsh::BorshDeserialize
+ crate::LightDiscriminator
+ light_hasher::DataHasher
+ Default,
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Inconsistent trait bounds may break feature-gated builds.

First impl uses crate::BorshDeserialize; second uses borsh::BorshDeserialize. Use the same alias to respect crate feature gating.

-        A: crate::BorshSerialize
-            + borsh::BorshDeserialize
+        A: crate::BorshSerialize
+            + crate::BorshDeserialize
             + crate::LightDiscriminator
             + light_hasher::DataHasher
             + Default,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#[cfg(feature = "light-account")]
fn with_light_account<A>(
mut self,
account: crate::LightAccount<'_, A>,
) -> Result<Self, pinocchio::program_error::ProgramError>
where
A: BorshSerialize
+ BorshDeserialize
+ LightDiscriminator
A: crate::BorshSerialize
+ borsh::BorshDeserialize
+ crate::LightDiscriminator
+ light_hasher::DataHasher
+ Default,
{
#[cfg(feature = "light-account")]
fn with_light_account<A>(
mut self,
account: crate::LightAccount<'_, A>,
) -> Result<Self, pinocchio::program_error::ProgramError>
where
A: crate::BorshSerialize
crate::BorshDeserialize
crate::LightDiscriminator
light_hasher::DataHasher
Default,
{
🤖 Prompt for AI Agents
In sdk-libs/sdk-pinocchio/src/cpi/v2/invoke.rs around lines 98 to 109, the trait
bounds are inconsistent between implementations: one uses
crate::BorshDeserialize while the other uses borsh::BorshDeserialize; to respect
feature-gating via the crate alias, change the borsh::BorshDeserialize reference
to crate::BorshDeserialize so both impls use the same alias and compile
correctly under feature flags.

Comment on lines +38 to +44
let mut inputs: [&[u8]; 16] = [&[]; 16];

inputs.push(program_id.as_slice());
inputs[0] = program_id.as_slice();

inputs.extend(seeds);
for (i, seed) in seeds.iter().enumerate() {
inputs[i + 1] = seed;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Guard against too many seeds (panic risk).

Fixed array has 16 slots (program_id + up to 15 seeds). If seeds.len() > 15, the inputs[i + 1] write will panic at runtime.

Apply a hard precondition before filling:

 pub fn derive_address_seed(seeds: &[&[u8]], program_id: &[u8; 32]) -> AddressSeed {
-        let mut inputs: [&[u8]; 16] = [&[]; 16];
+        assert!(
+            seeds.len() <= 15,
+            "v1::derive_address_seed supports at most 15 seeds (program_id + 15)"
+        );
+        let mut inputs: [&[u8]; 16] = [&[]; 16];
         inputs[0] = program_id.as_slice();
         for (i, seed) in seeds.iter().enumerate() {
             inputs[i + 1] = seed;
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let mut inputs: [&[u8]; 16] = [&[]; 16];
inputs.push(program_id.as_slice());
inputs[0] = program_id.as_slice();
inputs.extend(seeds);
for (i, seed) in seeds.iter().enumerate() {
inputs[i + 1] = seed;
}
assert!(
seeds.len() <= 15,
"v1::derive_address_seed supports at most 15 seeds (program_id + 15)"
);
let mut inputs: [&[u8]; 16] = [&[]; 16];
inputs[0] = program_id.as_slice();
for (i, seed) in seeds.iter().enumerate() {
inputs[i + 1] = seed;
}
🤖 Prompt for AI Agents
In sdk-libs/sdk-types/src/address.rs around lines 38 to 44, add an explicit
precondition that seeds.len() must be <= 15 before writing into the fixed
16-element inputs array to avoid a runtime panic; check if seeds.len() > 15 and
handle it immediately (prefer returning a descriptive Result::Err if the
surrounding function returns Result, otherwise panic with a clear message)
instead of letting the loop attempt out-of-bounds writes.

Comment on lines +1 to +4
#[cfg(all(not(feature = "std"), feature = "alloc"))]
use alloc::vec::Vec;
#[cfg(feature = "std")]
use std::vec::Vec;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Feature-gate methods that depend on Vec allocation.

The conditional Vec imports are correct, but methods to_account_infos() (line 204) and tree_pubkeys() (line 224) use Vec without being feature-gated. If neither "std" nor "alloc" feature is enabled, these methods will fail to compile because Vec won't be in scope.

Add feature gates to the methods:

+    #[cfg(any(feature = "std", feature = "alloc"))]
     pub fn to_account_infos(&self) -> Vec<T> {
+    #[cfg(any(feature = "std", feature = "alloc"))]
     pub fn tree_pubkeys(&self) -> Result<Vec<T::Pubkey>> {

Based on learnings.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
sdk-libs/sdk-types/src/cpi_accounts/v2.rs around lines 204 to 224: the methods
to_account_infos() (line ~204) and tree_pubkeys() (line ~224) use Vec but are
not feature-gated, so compilation fails when neither "std" nor "alloc" is
enabled; add a feature gate to each method such as #[cfg(any(feature = "std",
feature = "alloc"))] (or the equivalent project convention) above their
definitions so they are only compiled when Vec is available, ensuring the crate
builds when Vec is not in scope.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
program-libs/account-checks/Cargo.toml (1)

10-11: Violates coding guideline: default build should enable none.

This issue was already flagged in a previous review. The coding guideline explicitly states: "Define features solana, pinocchio, and test-only in Cargo.toml; default build should enable none." Setting default = ["std"] contradicts this requirement.

Apply this diff to align with the guideline:

-default = ["std"]
+default = []
 std = []

Based on coding guidelines.

program-libs/ctoken-types/Cargo.toml (1)

21-21: Unconditional std feature may conflict with no_std goals (unresolved from prior review).

The crate declares default = [] (line 9) to support no_std, but unconditionally enables std on light-compressed-account. This contradicts the stated no_std support and the PR's "refactor: libs to nostd" objective.

Consider gating the std feature behind a feature flag (e.g., std = ["light-compressed-account/std"]) and removing the unconditional feature from line 21.

🧹 Nitpick comments (1)
sdk-libs/sdk-pinocchio/src/cpi/traits.rs (1)

112-115: Consider more granular error mapping.

The error mapping collapses all CompressedAccountError variants except InputTooLarge into InvalidArgument. This loses diagnostic information compared to the invoke method's comprehensive error handling. For better debuggability, consider mapping additional error variants or using the error code directly.

For example:

-        let data = self.data_array::<N>().map_err(|e| match e {
-            CompressedAccountError::InputTooLarge(_) => ProgramError::InvalidInstructionData,
-            _ => ProgramError::InvalidArgument,
-        })?;
+        let data = self.data_array::<N>().map_err(|e| {
+            let error_code: u32 = e.into();
+            ProgramError::Custom(error_code)
+        })?;

This preserves the full error context by using the error code conversion defined in compressed-account/src/lib.rs.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cd07341 and f57ee0a.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • program-libs/account-checks/Cargo.toml (2 hunks)
  • program-libs/account-checks/src/account_info/test_account_info.rs (2 hunks)
  • program-libs/compressed-account/Cargo.toml (3 hunks)
  • program-libs/ctoken-types/Cargo.toml (2 hunks)
  • program-libs/hasher/Cargo.toml (2 hunks)
  • program-libs/zero-copy/Cargo.toml (1 hunks)
  • sdk-libs/macros/Cargo.toml (1 hunks)
  • sdk-libs/sdk-pinocchio/src/cpi/traits.rs (4 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
program-libs/account-checks/**/Cargo.toml

📄 CodeRabbit inference engine (program-libs/account-checks/CLAUDE.md)

Define features solana, pinocchio, and test-only in Cargo.toml; default build should enable none

Files:

  • program-libs/account-checks/Cargo.toml
program-libs/account-checks/src/account_info/{solana.rs,pinocchio.rs,test_account_info.rs}

📄 CodeRabbit inference engine (program-libs/account-checks/CLAUDE.md)

program-libs/account-checks/src/account_info/{solana.rs,pinocchio.rs,test_account_info.rs}: Provide SDK-specific AccountInfoTrait implementations in account_info/{solana.rs,pinocchio.rs,test_account_info.rs}
Gate SDK-specific implementations with #[cfg(feature = "solana"|"pinocchio"|"test-only")]

Files:

  • program-libs/account-checks/src/account_info/test_account_info.rs
program-libs/account-checks/src/**/*.rs

📄 CodeRabbit inference engine (program-libs/account-checks/CLAUDE.md)

program-libs/account-checks/src/**/*.rs: On account initialization, call account_info_init to set the 8-byte discriminator
Validate account type with 8-byte discriminators using check_discriminator before deserialization
Return AccountError variants (codes 12006–12021) and rely on automatic ProgramError conversions; avoid returning raw ProgramError directly

Files:

  • program-libs/account-checks/src/account_info/test_account_info.rs
program-libs/account-checks/src/account_info/test_account_info.rs

📄 CodeRabbit inference engine (program-libs/account-checks/CLAUDE.md)

Use the mock AccountInfo implementation under the test-only feature for unit tests

Files:

  • program-libs/account-checks/src/account_info/test_account_info.rs
🧠 Learnings (9)
📚 Learning: 2025-10-11T21:59:25.222Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-10-11T21:59:25.222Z
Learning: Applies to program-libs/account-checks/**/Cargo.toml : Define features solana, pinocchio, and test-only in Cargo.toml; default build should enable none

Applied to files:

  • program-libs/account-checks/Cargo.toml
  • sdk-libs/macros/Cargo.toml
  • program-libs/hasher/Cargo.toml
  • program-libs/ctoken-types/Cargo.toml
  • program-libs/compressed-account/Cargo.toml
📚 Learning: 2025-10-11T21:59:25.222Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-10-11T21:59:25.222Z
Learning: Applies to program-libs/account-checks/src/account_info/account_info_trait.rs : Ensure the crate compiles with no features enabled by keeping trait definitions free of SDK-specific dependencies

Applied to files:

  • program-libs/account-checks/Cargo.toml
  • program-libs/account-checks/src/account_info/test_account_info.rs
  • sdk-libs/sdk-pinocchio/src/cpi/traits.rs
📚 Learning: 2025-10-11T21:59:25.222Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-10-11T21:59:25.222Z
Learning: Applies to program-libs/account-checks/src/account_info/{solana.rs,pinocchio.rs,test_account_info.rs} : Gate SDK-specific implementations with #[cfg(feature = "solana"|"pinocchio"|"test-only")]

Applied to files:

  • program-libs/account-checks/Cargo.toml
  • sdk-libs/macros/Cargo.toml
  • program-libs/account-checks/src/account_info/test_account_info.rs
  • program-libs/hasher/Cargo.toml
  • program-libs/ctoken-types/Cargo.toml
  • sdk-libs/sdk-pinocchio/src/cpi/traits.rs
📚 Learning: 2025-10-11T21:59:25.222Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-10-11T21:59:25.222Z
Learning: Applies to program-libs/account-checks/src/!(account_info)/**/*.rs : Use AccountInfoTrait for runtime-agnostic account handling; avoid direct solana-program or pinocchio AccountInfo in general logic

Applied to files:

  • program-libs/account-checks/src/account_info/test_account_info.rs
  • program-libs/ctoken-types/Cargo.toml
📚 Learning: 2025-10-11T21:59:25.222Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-10-11T21:59:25.222Z
Learning: Applies to program-libs/account-checks/src/account_info/{solana.rs,pinocchio.rs,test_account_info.rs} : Provide SDK-specific AccountInfoTrait implementations in account_info/{solana.rs,pinocchio.rs,test_account_info.rs}

Applied to files:

  • program-libs/account-checks/src/account_info/test_account_info.rs
  • program-libs/ctoken-types/Cargo.toml
  • sdk-libs/sdk-pinocchio/src/cpi/traits.rs
📚 Learning: 2025-10-11T21:59:25.222Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-10-11T21:59:25.222Z
Learning: Applies to program-libs/account-checks/src/account_info/test_account_info.rs : Use the mock AccountInfo implementation under the test-only feature for unit tests

Applied to files:

  • program-libs/account-checks/src/account_info/test_account_info.rs
📚 Learning: 2025-10-16T06:33:19.426Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-10-16T06:33:19.426Z
Learning: Applies to program-libs/compressible/src/config.rs : Ensure serialization compatibility across Anchor, Pinocchio, and Borsh for core account types used by dependent programs

Applied to files:

  • program-libs/account-checks/src/account_info/test_account_info.rs
  • sdk-libs/sdk-pinocchio/src/cpi/traits.rs
📚 Learning: 2025-10-16T06:33:55.362Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-10-16T06:33:55.362Z
Learning: Applies to programs/compressed-token/program/program-libs/ctoken-types/** : Define all state and instruction data structures in the light-ctoken-types crate (program-libs/ctoken-types), including state/, instructions/, and state/extensions/

Applied to files:

  • program-libs/ctoken-types/Cargo.toml
📚 Learning: 2025-10-15T03:46:26.767Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: programs/registry/CLAUDE.md:0-0
Timestamp: 2025-10-15T03:46:26.767Z
Learning: Applies to programs/registry/src/account_compression_cpi/*.rs : Context structs for wrapper instructions must include standard accounts: optional registered_forester_pda (mut), authority Signer, cpi_authority with seeds/bump for CPI_AUTHORITY_PDA_SEED, registered_program_pda, target program handle, log_wrapper, and mutable target_account.

Applied to files:

  • sdk-libs/sdk-pinocchio/src/cpi/traits.rs
🧬 Code graph analysis (1)
sdk-libs/sdk-pinocchio/src/cpi/traits.rs (5)
sdk-libs/sdk-pinocchio/src/cpi/v2/invoke.rs (2)
  • with_light_account (28-76)
  • with_light_account (99-118)
sdk-libs/sdk-pinocchio/src/cpi/v1/invoke.rs (2)
  • with_light_account (70-117)
  • data (143-148)
program-libs/compressed-account/src/instruction_data/invoke_cpi.rs (1)
  • data (38-48)
program-libs/compressed-account/src/lib.rs (5)
  • from (84-108)
  • from (113-115)
  • from (120-122)
  • from (150-159)
  • from (204-212)
sdk-libs/sdk-pinocchio/src/error.rs (5)
  • from (90-92)
  • from (96-98)
  • from (102-104)
  • from (108-135)
  • from (139-180)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (21)
  • GitHub Check: Test program-libs-fast
  • GitHub Check: Test program-libs-slow
  • GitHub Check: Test batched-merkle-tree-simulate
  • GitHub Check: programs (system-cpi-test-v2-functional-account-infos, ["cargo-test-sbf -p system-cpi-v2-test -- ...
  • GitHub Check: programs (light-system-program-address, ["cargo-test-sbf -p system-test -- test_with_address", "c...
  • GitHub Check: programs (system-cpi-test-v2-functional-read-only, ["cargo-test-sbf -p system-cpi-v2-test -- func...
  • GitHub Check: programs (compressed-token-batched-tree, ["cargo-test-sbf -p compressed-token-test -- test_transf...
  • GitHub Check: programs (compressed-token-and-e2e, ["cargo-test-sbf -p compressed-token-test --test v1", "cargo-...
  • GitHub Check: programs (light-system-program-compression, ["cargo-test-sbf -p system-test -- test_with_compress...
  • GitHub Check: programs (system-cpi-test, ["cargo-test-sbf -p system-cpi-test", "cargo test -p light-system-prog...
  • GitHub Check: programs (account-compression-and-registry, ["cargo-test-sbf -p account-compression-test", "cargo...
  • GitHub Check: Forester e2e test
  • GitHub Check: system-programs (sdk-test-program, ["cargo-test-sbf -p sdk-native-test", "cargo-test-sbf -p sdk-v...
  • GitHub Check: system-programs (sdk-anchor-test-program, ["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -...
  • GitHub Check: system-programs (sdk-token-test-program, ["cargo-test-sbf -p sdk-token-test"])
  • GitHub Check: system-programs (sdk-libs, light-sdk-macros light-sdk light-program-test light-client light-compr...
  • GitHub Check: stateless-js-v2
  • GitHub Check: lint
  • GitHub Check: stateless-js-v1
  • GitHub Check: cli-v1
  • GitHub Check: cli-v2
🔇 Additional comments (15)
program-libs/zero-copy/Cargo.toml (1)

29-29: Verify the intent of the self-referential dev-dependency.

While valid in Rust, having light-zero-copy depend on itself as a dev-dependency is unusual. This pattern works because dev-dependencies don't create circular runtime dependencies, but it's worth confirming this is intentional.

Could you clarify the purpose? For example:

  • Are tests/examples using the crate's public API directly (e.g., to validate derive macros work with std enabled)?
  • Could this be replaced with direct imports or example code instead?
program-libs/compressed-account/Cargo.toml (4)

10-12: Verify default feature alignment with no_std migration goals.

The default feature now includes ["alloc"], but the PR objective is to refactor to no_std. Related guidance suggests workspace defaults should enable no features. This may create an implicit alloc dependency for users who simply depend on this crate without explicitly requesting it.

Clarify: Is alloc intended to be the default, or should default = [] be used with alloc as an opt-in feature?


27-27: Light-zero-copy's std feature is always enabled unconditionally.

Line 27 hard-codes features = ["std", "mut", "derive"] on the light-zero-copy dependency. This contradicts the no_std goal by always pulling in the std feature. Should this feature enablement be conditional (e.g., behind the std feature of this crate)?


39-39: Verify tinyvec is truly unconditional or should be feature-gated.

TinyVec is added as an unconditional workspace dependency (line 39). If it is only used in certain code paths (e.g., only in std code, or only behind the alloc feature), it should be moved into the appropriate feature gate to reduce the dependency footprint in minimal no_std configurations.


31-31: Good: solana-msg is now optional.

Making solana-msg optional and tying it to the std feature (line 12) aligns well with the no_std migration, allowing no_std builds to avoid the dependency.

program-libs/account-checks/src/account_info/test_account_info.rs (2)

61-64: LGTM! Appropriate migration to core for no_std compatibility.

The migration from std::mem::forget and std::mem::transmute to their core:: equivalents is correct for no_std support. These intrinsic operations are available in core and do not require the standard library.


106-109: LGTM! Proper feature gating for std-dependent code.

The enhanced feature gate #[cfg(all(feature = "solana", feature = "std"))] correctly restricts this module to builds where both features are enabled, since it requires std features like Rc, RefCell, and Vec. The explicit extern crate std; declaration makes the std dependency clear in a no_std context.

program-libs/ctoken-types/Cargo.toml (1)

52-52: Clarify self-referential dev-dependency.

Line 52 adds light-ctoken-types as a dev-dependency to itself (the package name is light-ctoken-types on line 2). This appears to be a copy-paste error or unintentional.

Verify whether this line was intended or should reference a different crate.

program-libs/hasher/Cargo.toml (4)

10-18: Feature architecture aligns with no_std patterns.

The feature structure correctly enables granular control:

  • std feature enables alloc, which is the standard no_std pattern
  • solana, pinocchio, poseidon, and the new sha256/keccak features are independent
  • Default includes std, allowing users to opt-in to no_std by disabling default features

This design is solid for the nostd refactor objective.


50-51: No issue found — self-dependency is intentional and actively used.

The "circular" dev-dependency is actually a standard self-dependency pattern. The package light-hasher correctly declares itself as a dev-dependency to test its functionality with specific feature combinations. All five test files (bigint.rs, data_hasher.rs, hash_chain.rs, hash_to_field_size.rs, to_byte_array.rs) actively import and use light_hasher, confirming the dev-dependency is necessary and intentional.

Likely an incorrect or invalid review comment.


31-31: Feature gating of borsh is correct and properly implemented.

Verification confirms:

  • borsh is configured as optional = true in Cargo.toml
  • The alloc feature properly gates borsh via alloc = ["dep:borsh"]
  • All borsh usage in the codebase is protected: hash_to_field_size.rs:2 has use borsh::BorshSerialize; correctly guarded with #[cfg(feature = "alloc")]

The implementation matches the review comment's assessment exactly.


24-24: tinyvec is actively used and correctly configured as an unconditional dependency.

The crate uses ArrayVec from tinyvec in the hashv_to_bn254_field_size_be_const_array function (line 67) to create a bounded, stack-allocated vector. This function and its caller hash_to_bn254_field_size_be are both unconditionally compiled, making tinyvec a legitimate unconditional dependency.

sdk-libs/sdk-pinocchio/src/cpi/traits.rs (3)

1-20: LGTM: Clean no_std feature gates.

The conditional imports follow standard Rust no_std patterns, properly gating Vec, Pubkey, and LightSdkError based on available allocator features.


43-67: LGTM: Trait signatures updated for no_std compatibility.

The changes properly:

  • Feature-gate with_light_account and invoke
  • Add invoke_array without feature gates, enabling no_std CPI calls
  • Standardize error handling with ProgramError
  • Update serialization traits to use external borsh:: paths

93-93: ****

The concern about a dangling reference is incorrect. Pubkey implements the Copy trait, which means that creating a reference to a temporary Pubkey value (via &Pubkey::from(...)) is safe and valid in Rust. The compiler ensures the copied value's lifetime is sufficient for the reference. No changes are needed at line 93.

Likely an incorrect or invalid review comment.

solana-pubkey = { workspace = true, features = ["curve25519", "sha2"] }

light-hasher = { workspace = true }
light-hasher = { workspace = true, features = ["sha256"] }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Feature selection looks aligned with no_std refactor.

The explicit feature selections are consistent with the PR goal:

  • Main dependencies (line 19) pin light-hasher to sha256, suitable for no_std builds.
  • Dev dependencies (line 22) enable std for light-compressed-account, which is appropriate for testing.
  • Test-specific features (line 29: poseidon, keccak) are isolated in dev-dependencies.

This follows the existing pattern of explicit feature declarations (e.g., solana-pubkey at line 17).

Please verify that the selected features (sha256, poseidon, keccak) are no_std-compatible and that enabling std on light-compressed-account in dev-dependencies does not inadvertently pull in std dependencies into the main build. You can verify this by checking the manifest for each dependency:

Also applies to: 22-22, 29-29


🏁 Script executed:

#!/bin/bash
# Verify feature definitions in workspace crates
echo "=== Checking light-hasher features ==="
fd 'light-hasher' --type d -x sh -c 'test -f "{}/Cargo.toml" && echo "File: {}/Cargo.toml" && rg -A 20 "\[features\]" "{}/Cargo.toml"'

echo ""
echo "=== Checking light-compressed-account features ==="
fd 'light-compressed-account' --type d -x sh -c 'test -f "{}/Cargo.toml" && echo "File: {}/Cargo.toml" && rg -A 20 "\[features\]" "{}/Cargo.toml"'

echo ""
echo "=== Verifying no_std references in light-sdk-macros dependencies ==="
rg 'default\s*=' --type toml | grep -A 3 -B 3 'light-hasher\|light-compressed-account'

Length of output: 160



---

🏁 Script executed:

```shell
#!/bin/bash
# Check the actual Cargo.toml file in question
echo "=== Contents of sdk-libs/macros/Cargo.toml ==="
cat sdk-libs/macros/Cargo.toml

echo ""
echo "=== Checking workspace members definition ==="
fd Cargo.toml --type f | head -5 | xargs -I {} sh -c 'echo "File: {}" && rg -A 50 "\[workspace\]" "{}" | head -20'

Length of output: 1865


🏁 Script executed:

#!/bin/bash
# Search for light-hasher and light-compressed-account in all Cargo.toml files
echo "=== Searching for light-hasher across all Cargo.toml files ==="
rg 'light-hasher' --type toml -B 2 -A 2

echo ""
echo "=== Searching for light-compressed-account across all Cargo.toml files ==="
rg 'light-compressed-account' --type toml -B 2 -A 2

Length of output: 67922


🏁 Script executed:

#!/bin/bash
# Check if these are external crates by looking at their presence in workspace
echo "=== Checking for workspace members that might contain these crates ==="
find . -name "Cargo.toml" -type f -exec grep -l "name.*light" {} \;

echo ""
echo "=== Listing all workspace members ==="
fd Cargo.toml --type f -x grep -H "^\[package\]" {} | head -20

Length of output: 2403


🏁 Script executed:

#!/bin/bash
# Verify feature definitions in the workspace crates
echo "=== program-libs/hasher/Cargo.toml features ==="
cat program-libs/hasher/Cargo.toml | sed -n '/\[features\]/,/\[/p' | head -30

echo ""
echo "=== program-libs/compressed-account/Cargo.toml features ==="
cat program-libs/compressed-account/Cargo.toml | sed -n '/\[features\]/,/\[/p' | head -40

echo ""
echo "=== program-libs/hasher/Cargo.toml default features ==="
rg 'default\s*=' program-libs/hasher/Cargo.toml

echo ""
echo "=== program-libs/compressed-account/Cargo.toml default features ==="
rg 'default\s*=' program-libs/compressed-account/Cargo.toml

Length of output: 332


🏁 Script executed:

#!/bin/bash
# Verify that dev-dependencies don't affect main build by checking Cargo.lock or workspace resolution
echo "=== Checking if light-hasher main dependency has default-features setting ==="
rg 'light-hasher.*workspace.*true' sdk-libs/macros/Cargo.toml -A 1 -B 1

echo ""
echo "=== Checking if light-compressed-account main dependency avoids std ==="
rg 'light-compressed-account' sdk-libs/macros/Cargo.toml -B 1 -A 1

Length of output: 493


🏁 Script executed:

#!/bin/bash
# Check the complete dependencies section with all flags
echo "=== Full [dependencies] section in sdk-libs/macros/Cargo.toml ==="
cat sdk-libs/macros/Cargo.toml | sed -n '/^\[dependencies\]/,/^\[/p'

echo ""
echo "=== Check if light-hasher uses default-features = false anywhere ==="
rg 'light-hasher.*default-features' sdk-libs/macros/Cargo.toml

echo ""
echo "=== Compare with how other SDK libs handle light-hasher ==="
rg 'light-hasher.*workspace.*true' sdk-libs/sdk/Cargo.toml -A 1 -B 1

Length of output: 645


🏁 Script executed:

#!/bin/bash
# Verify the entire file structure to ensure complete context
echo "=== Complete sdk-libs/macros/Cargo.toml ==="
cat sdk-libs/macros/Cargo.toml

Length of output: 1008


🏁 Script executed:

#!/bin/bash
# Check git history or PR diff to see if default-features = false was removed
echo "=== Searching for default-features = false patterns in comparable crates ==="
rg 'default-features\s*=\s*false' program-libs/hasher/Cargo.toml -B 2 -A 2

echo ""
echo "=== Check how other program-libs handle light-hasher deps ==="
rg 'light-hasher.*workspace' program-libs/*/Cargo.toml | head -20

echo ""
echo "=== Verify light-hasher can be compiled without std ==="
cat program-libs/hasher/Cargo.toml | sed -n '/\[features\]/,/\[dependencies\]/p'

Length of output: 1572


Add default-features = false to light-hasher main dependency (line 19).

The current configuration will inadvertently include the std feature despite the no_std refactor goal. In Cargo, specifying features = [...] without default-features = false enables both the default features AND the explicitly listed features.

Since light-hasher has default = ["std"], line 19 will activate both std and sha256. To maintain no_std compatibility, use:

light-hasher = { workspace = true, default-features = false, features = ["sha256"] }

Also applies to line 29 (dev-dependencies) for consistency:

light-hasher = { workspace = true, default-features = false, features = ["poseidon", "keccak"] }

Dev-dependencies won't affect the main build, but explicit default-features = false ensures the intent is clear and prevents accidental std inclusion during testing.

🤖 Prompt for AI Agents
In sdk-libs/macros/Cargo.toml around line 19 (and also update line 29 in the
[dev-dependencies] section), the light-hasher dependency currently specifies
features but does not disable default features which causes the crate's default
"std" feature to be enabled; update the dependency entries to add
default-features = false so the dependency entries explicitly disable defaults
and only enable the listed features (for line 19 set default-features = false
with features = ["sha256"], and for line 29 set default-features = false with
features = ["poseidon","keccak"]).

Comment on lines +105 to +109
use light_compressed_account::CompressedAccountError;
// Validate mode consistency
if accounts.get_mode() != self.get_mode() {
return Err(ProgramError::InvalidArgument);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Inconsistent error handling for mode mismatch.

Line 108 returns ProgramError::InvalidArgument for mode mismatch, while line 78 in the invoke method returns ProgramError::from(LightSdkError::ModeMismatch). These methods should handle the same validation error consistently.

Apply this diff to align the error handling:

-        if accounts.get_mode() != self.get_mode() {
-            return Err(ProgramError::InvalidArgument);
-        }
+        if accounts.get_mode() != self.get_mode() {
+            return Err(ProgramError::Custom(16035)); // LightSdkError::ModeMismatch
+        }

Note: Error code 16035 corresponds to ModeMismatch based on the error mappings in error.rs.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
use light_compressed_account::CompressedAccountError;
// Validate mode consistency
if accounts.get_mode() != self.get_mode() {
return Err(ProgramError::InvalidArgument);
}
use light_compressed_account::CompressedAccountError;
// Validate mode consistency
if accounts.get_mode() != self.get_mode() {
return Err(ProgramError::Custom(16035)); // LightSdkError::ModeMismatch
}
🤖 Prompt for AI Agents
In sdk-libs/sdk-pinocchio/src/cpi/traits.rs around lines 105 to 109, the
mode-mismatch branch returns ProgramError::InvalidArgument which is inconsistent
with the invoke method; change the return to
ProgramError::from(LightSdkError::ModeMismatch) so both places use the same
LightSdkError mapping (alternatively use ProgramError::Custom(16035) if the
LightSdkError type isn't in scope), and ensure the LightSdkError import is
present or add it.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
program-libs/zero-copy/src/traits/zero_copy_at.rs (1)

169-171: Missing alloc feature gate causes compilation error.

The ZeroCopyStructInner impl for Vec<T> uses Vec, which is only imported under #[cfg(feature = "alloc")] (Lines 1-2). Without gating this impl, the code will fail to compile when the alloc feature is disabled.

Apply this fix to add the missing feature gate:

+#[cfg(feature = "alloc")]
 impl<T: ZeroCopyStructInner + Copy> ZeroCopyStructInner for Vec<T> {
     type ZeroCopyInner = Vec<T::ZeroCopyInner>;
 }

Note: The equivalent impl in zero_copy_at_mut.rs (Lines 187-190) is correctly gated, demonstrating the expected pattern.

program-libs/zero-copy/src/traits/vec_u8.rs (1)

38-74: VecU8 uses u8 length encoding while Vec uses U32—this is a real inconsistency requiring clarification or remediation.

I've verified the claims in the review comment against the actual codebase:

Confirmed findings:

  1. Length encoding mismatch: VecU8 (lines 44, 64 in vec_u8.rs) uses Ref::<&[u8], u8>::from_prefix, limiting capacity to 255 elements. Vec (line 122 in zero_copy_at.rs) uses Ref::<&[u8], U32>::from_prefix, supporting ~4 billion elements.

  2. Heap exhaustion check present in Vec: Lines 124–130 in zero_copy_at.rs include an explicit heap exhaustion defense with the comment "Prevent heap exhaustion attacks by checking if num_slices is reasonable."

  3. Heap exhaustion check absent in VecU8: Neither VecU8::zero_copy_at() nor VecU8::zero_copy_at_mut() implements this check.

  4. No documented design rationale: The codebase contains no comments or documentation explaining why VecU8 intentionally deviates from Vec's approach.

Recommendation: This inconsistency requires action—either align VecU8 with Vec by using U32 and adding the heap exhaustion check, or document the intentional constraint in VecU8's type documentation explaining its limited capacity for small-vector use cases.

program-libs/compressed-account/src/instruction_data/invoke_cpi.rs (1)

37-48: <!-- [scratchpad_update]
[task verify_divergence 1/2 examine_usages 1/1]
[observations_added]

  • Standard Solana wire format: discriminator (8 bytes) + serialized args (variable)
  • The trait default matches Solana standard format (no length prefix)
  • invoke_cpi.rs adds non-standard 4-byte length: discriminator + u32_len + args
  • From web search: Anchor CPI standard does NOT include length prefix for inline args in instruction data
  • The pattern of adding 4-byte length is seen in some SDK code (transfer/system) but those are NOT using LightInstructionData trait

[analysis_update]

  • Review comment is identifying a genuine non-compliance with standard Solana wire format
  • invoke_cpi.rs deviates from both trait default AND Solana standard
  • The 4-byte length seen elsewhere (transfer/system) is for different purposes and not part of instruction CPI wire format standard
  • This is a critical compatibility issue

[pending]

  • Verify if deserialization code on CPI consumer expects this 4-byte length or follows standard format
  • Check if removing the length field would break any actual CPI consumers

[actions]
Search for CPI consumers/deserializers that process InstructionDataInvokeCpi data output
[scratchpad_end] --><!-- [scratchpad_update]
[task verify_divergence 1/2 examine_usages 2/2]

[observations_added]

  • Borsh format: "The size of dynamic containers (such as hash maps and hash sets) is written as a 32-bit unsigned integer before the values" (18-11)
  • Strings in Borsh have 4-byte length prefix (18-2)
  • When serializing a structure containing Vec, Borsh DOES include the length prefix
  • The manual 4-byte length in invoke_cpi.rs appears to be attempting to match this Borsh behavior

[analysis_update]

  • The 4-byte length is NOT for the instruction wire format, it's for the payload (inputs) serialization
  • invoke_cpi.rs is manually adding Vec length prefix that Borsh would automatically add
  • BUT: the payload inputs is already Borsh-serialized by try_to_vec() at line 42
  • When you call try_to_vec() on a BorshSerialize type, it already contains its own structure
  • The manual length field at line 45 is DOUBLE-WRAPPING the payload length

Key insight: If inputs is already Borsh-serialized data from self.try_to_vec(), adding another 4-byte length prefix creates: discriminator + length_of_borsh_data + borsh_data, which is incorrect

Need to verify: what exactly is in inputs and what does the trait default do?
From traits.rs:29-33, the default impl just does: discriminator + inputs (no extra length)

The review comment is correct: this is a divergence that will cause incompatibility.

[pending]

  • Verify if anything on the CPI consumer side expects and parses this extra 4-byte length field

[actions]
Search codebase for deserialization/parsing of LightInstructionData instruction output
[scratchpad_end] -->

Now let me search the codebase to verify if any CPI consumers parse this 4-byte length field:<!-- [scratchpad_update]
[task verify_divergence - conclusion 2/2]

[observations_final]

  • Light protocol uses CPI to call system program
  • Standard Solana CPI format: instruction discriminator + serialized args (no length prefix)
  • invoke_cpi.rs adds: discriminator + 4-byte-length + serialized-args
  • traits.rs default adds: discriminator + serialized-args (correct format)
  • No evidence of any deserializer expecting the 4-byte length prefix in Light protocol
  • The learnings note emphasizes serialization compatibility across feature combinations
  • Multiple trait impls use the default (no length), only InstructionDataInvokeCpi adds it

[analysis_final]
The review comment is CORRECT and identifies a critical compatibility issue:

  1. InstructionDataInvokeCpi diverges from the trait's standard wire format
  2. This diverges from Solana standard CPI instruction data format
  3. The 4-byte length appears to be an accidental double-wrapping of Borsh serialized data
  4. This will break CPI compatibility between programs built with different features

The suggested fix (remove the 4-byte length) is correct and matches:

  • The trait default implementation
  • The Solana standard CPI wire format
  • Other implementations using the default trait

[done]
Review comment is verified as correct. The 4-byte length field in invoke_cpi.rs lines 45 creates a wire-format divergence that will cause compatibility issues across feature combinations.
[scratchpad_end] -->

Based on my verification of the codebase and investigation of the underlying standards, I can confirm the review comment is accurate. The issue is a genuine wire-format divergence that requires correction.

Remove the 4-byte length field to match the trait's standard CPI wire format.

The InstructionDataInvokeCpi::data() implementation at lines 43-47 incorrectly adds a u32 length prefix that diverges from both the trait's default implementation and the Solana standard CPI instruction data format (which is: discriminator + serialized args, with no additional length field).

The trait default (traits.rs lines 27-33) correctly writes: discriminator + serialized args. This matches the Solana CPI standard and is used by other LightInstructionData implementations. The extra 4-byte prefix at line 45 will cause deserialization failures when CPI consumers expect the standard format.

Apply the suggested fix:

 #[cfg(all(feature = "alloc", not(feature = "anchor")))]
 fn data(&self) -> Result<Vec<u8>, crate::CompressedAccountError> {
     use borsh::BorshSerialize;
     let inputs = self
         .try_to_vec()
         .map_err(|_| crate::CompressedAccountError::InvalidArgument)?;
-    let mut data = Vec::with_capacity(12 + inputs.len());
+    let mut data = Vec::with_capacity(8 + inputs.len());
     data.extend_from_slice(self.discriminator());
-    data.extend_from_slice(&(inputs.len() as u32).to_le_bytes());
     data.extend_from_slice(inputs.as_slice());
     Ok(data)
 }
program-libs/hasher/src/hash_to_field_size.rs (3)

42-54: Avoid panics: gate infallible helpers to keccak/solana or make them fallible.

Both functions unwrap Keccak::hashv. Without feature="keccak" on non-Solana targets, hashv returns an error → panic.

Non-breaking option: compile only where keccak/solana is available.

-#[cfg(feature = "alloc")]
+#[cfg(all(feature = "alloc", any(target_os = "solana", feature = "keccak")))]
 pub fn hashv_to_bn254_field_size_be(bytes: &[&[u8]]) -> [u8; 32] {
@@
-    let mut hashed_value: [u8; 32] = Keccak::hashv(&slices).unwrap();
+    let mut hashed_value: [u8; 32] = Keccak::hashv(&slices).unwrap();
@@
 }
 
-#[cfg(feature = "alloc")]
+#[cfg(all(feature = "alloc", any(target_os = "solana", feature = "keccak")))]
 pub fn hashv_to_bn254_field_size_be_array(bytes: &[[u8; 32]]) -> [u8; 32] {
@@
-    let mut hashed_value: [u8; 32] = Keccak::hashv(&slices).unwrap();
+    let mut hashed_value: [u8; 32] = Keccak::hashv(&slices).unwrap();

Alternative (breaking but safer): change return types to Result<[u8;32], HasherError> and use ?.

Also applies to: 56-67


69-85: Fix potential underflow in MAX_SLICES check.

MAX_SLICES - 1 underflows when MAX_SLICES == 0 (debug panic, release wrap). Compare against bytes.len() + 1 instead.

-    if bytes.len() > MAX_SLICES - 1 {
+    if bytes.len() + 1 > MAX_SLICES {
         return Err(HasherError::InvalidInputLength(MAX_SLICES, bytes.len()));
     }

93-101: Also gate the convenience wrapper or make it fallible.

This unwrap will panic without keccak on non-Solana targets. Align gating with helpers.

-/// ```
-/// # #[cfg(feature = "keccak")]
+/// ```
+/// # #[cfg(feature = "keccak")]
 /// # use light_hasher::hash_to_field_size::hash_to_bn254_field_size_be;
@@
-pub fn hash_to_bn254_field_size_be(bytes: &[u8]) -> [u8; 32] {
+#[cfg(any(target_os = "solana", feature = "keccak"))]
+pub fn hash_to_bn254_field_size_be(bytes: &[u8]) -> [u8; 32] {
     hashv_to_bn254_field_size_be_const_array::<2>(&[bytes]).unwrap()
 }
♻️ Duplicate comments (3)
program-libs/ctoken-types/Cargo.toml (1)

21-21: Unconditional std feature contradicts no_std PR objective—still unresolved from prior review.

Line 21 still unconditionally enables features = ["std"] on light-compressed-account, despite the past review flagging this as conflicting with the no_std refactoring goal. The crate defines default = [] (line 9) to support no_std environments, but this dependency forces std transitively.

Add a std feature to this crate and gate the dependency's feature:

 [features]
 anchor = ["light-compressed-account/anchor", "dep:anchor-lang", "light-compressible/anchor"]
 solana = ["dep:solana-program-error", "dep:solana-sysvar"]
 default = []
+std = ["light-compressed-account/std"]
 profile-program = []
-light-compressed-account = { workspace = true, features = ["std"] }
+light-compressed-account = { workspace = true }
sdk-libs/sdk-types/src/address.rs (2)

38-44: Buffer overflow risk remains unaddressed (duplicate concern).

The previous review flagged that if seeds.len() > 15, writing inputs[i + 1] will panic at runtime. This critical issue has not been resolved in the current changes.


116-119: Guard against too many seeds (panic risk).

The function calls hashv_to_bn254_field_size_be_const_array::<17> with unwrap but doesn't validate that seeds.len() <= 17. If more than 17 seeds are passed, this will panic at runtime.

Add a precondition check:

 pub fn derive_address_seed(seeds: &[&[u8]]) -> AddressSeed {
+    assert!(
+        seeds.len() <= 17,
+        "v2::derive_address_seed supports at most 17 seeds (16 seeds + 1 for bump)"
+    );
     // Max 16 seeds + 1 for bump
     AddressSeed(hashv_to_bn254_field_size_be_const_array::<17>(seeds).unwrap())
 }
🧹 Nitpick comments (5)
program-libs/compressed-account/Cargo.toml (1)

39-39: Specify default-features for tinyvec to support no_std builds.

Line 39 adds tinyvec without default-features = false or explicit features. Per learnings, tinyvec may have default-enabled heap allocation behaviors. For consistency with the no_std refactoring goal and the explicit feature gating applied to other dependencies (e.g., light-zero-copy, borsh), explicitly disable defaults and/or gate allocation-related features:

-tinyvec = { workspace = true }
+tinyvec = { workspace = true, default-features = false, features = ["alloc"] }

(Adjust feature set based on actual tinyvec usage and no_std requirements.)

sdk-libs/client/src/rpc/client.rs (1)

686-686: Optional: Remove redundant import.

The use light_compressed_account::TreeType; statement is redundant since TreeType is already imported at the module level (line 9).

Apply this diff to remove the redundant import:

-            use light_compressed_account::TreeType;
             use solana_pubkey::pubkey;
sdk-libs/sdk-pinocchio/src/cpi/v1/invoke.rs (1)

91-92: Consider simplifying error conversion chain.

The two-step error conversion (.map_err(crate::error::LightSdkError::from).map_err(|e| ProgramError::Custom(u32::from(e)))) could potentially be simplified if there's a direct From<OriginalError> for ProgramError implementation available.

Based on the relevant code snippets, CompressedAccountError already implements From<CompressedAccountError> for ProgramError. If the intermediate LightSdkError conversion isn't adding value, consider using the direct conversion.

Also applies to: 108-109

program-libs/compressed-account/src/lib.rs (2)

131-139: Serialization derives: confirm borsh derive feature and std defaults.

Good switch to cfg_attr. Please ensure:

  • borsh has features = ["derive"] and default-features = false in this crate.
  • anchor derives only compile under all(feature = "std", feature = "anchor").

You can add a brief module-level doc explaining the feature matrix for Anchor/Borsh to prevent misuse. Based on learnings.

Also applies to: 167-176


215-221: Prefer Pubkey fields (and consider serde/borsh derives) for CpiSigner.

Using [u8; 32] loses type intent. Suggest:

  • Use Pubkey for program_id and cpi_signer.
  • If serialized across boundaries, gate derives:
    • #[cfg_attr(all(feature="std", feature="anchor"), derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize))]
    • #[cfg_attr(not(feature="anchor"), derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
-pub struct CpiSigner {
-    pub program_id: [u8; 32],
-    pub cpi_signer: [u8; 32],
-    pub bump: u8,
-}
+pub struct CpiSigner {
+    pub program_id: Pubkey,
+    pub cpi_signer: Pubkey,
+    pub bump: u8,
+}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f57ee0a and 1e4cd1e.

📒 Files selected for processing (21)
  • program-libs/account-checks/src/account_info/test_account_info.rs (3 hunks)
  • program-libs/compressed-account/Cargo.toml (3 hunks)
  • program-libs/compressed-account/src/address.rs (2 hunks)
  • program-libs/compressed-account/src/instruction_data/invoke_cpi.rs (2 hunks)
  • program-libs/compressed-account/src/instruction_data/zero_copy.rs (4 hunks)
  • program-libs/compressed-account/src/lib.rs (8 hunks)
  • program-libs/ctoken-types/Cargo.toml (2 hunks)
  • program-libs/hasher/src/hash_to_field_size.rs (6 hunks)
  • program-libs/zero-copy/Cargo.toml (2 hunks)
  • program-libs/zero-copy/src/lib.rs (2 hunks)
  • program-libs/zero-copy/src/traits/mod.rs (1 hunks)
  • program-libs/zero-copy/src/traits/vec_u8.rs (3 hunks)
  • program-libs/zero-copy/src/traits/zero_copy_at.rs (2 hunks)
  • program-libs/zero-copy/src/traits/zero_copy_at_mut.rs (3 hunks)
  • program-libs/zero-copy/src/traits/zero_copy_new.rs (2 hunks)
  • program-tests/create-address-test-program/src/lib.rs (6 hunks)
  • sdk-libs/client/src/rpc/client.rs (2 hunks)
  • sdk-libs/sdk-pinocchio/src/cpi/v1/invoke.rs (5 hunks)
  • sdk-libs/sdk-types/src/address.rs (5 hunks)
  • sdk-libs/sdk-types/src/instruction/account_info.rs (4 hunks)
  • sdk-libs/sdk/Cargo.toml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • program-libs/compressed-account/src/instruction_data/zero_copy.rs
  • program-libs/zero-copy/Cargo.toml
  • sdk-libs/sdk-types/src/instruction/account_info.rs
🧰 Additional context used
📓 Path-based instructions (3)
program-libs/account-checks/src/account_info/{solana.rs,pinocchio.rs,test_account_info.rs}

📄 CodeRabbit inference engine (program-libs/account-checks/CLAUDE.md)

program-libs/account-checks/src/account_info/{solana.rs,pinocchio.rs,test_account_info.rs}: Provide SDK-specific AccountInfoTrait implementations in account_info/{solana.rs,pinocchio.rs,test_account_info.rs}
Gate SDK-specific implementations with #[cfg(feature = "solana"|"pinocchio"|"test-only")]

Files:

  • program-libs/account-checks/src/account_info/test_account_info.rs
program-libs/account-checks/src/**/*.rs

📄 CodeRabbit inference engine (program-libs/account-checks/CLAUDE.md)

program-libs/account-checks/src/**/*.rs: On account initialization, call account_info_init to set the 8-byte discriminator
Validate account type with 8-byte discriminators using check_discriminator before deserialization
Return AccountError variants (codes 12006–12021) and rely on automatic ProgramError conversions; avoid returning raw ProgramError directly

Files:

  • program-libs/account-checks/src/account_info/test_account_info.rs
program-libs/account-checks/src/account_info/test_account_info.rs

📄 CodeRabbit inference engine (program-libs/account-checks/CLAUDE.md)

Use the mock AccountInfo implementation under the test-only feature for unit tests

Files:

  • program-libs/account-checks/src/account_info/test_account_info.rs
🧠 Learnings (15)
📚 Learning: 2025-10-11T21:59:25.222Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-10-11T21:59:25.222Z
Learning: Applies to program-libs/account-checks/src/!(account_info)/**/*.rs : Use AccountInfoTrait for runtime-agnostic account handling; avoid direct solana-program or pinocchio AccountInfo in general logic

Applied to files:

  • program-libs/account-checks/src/account_info/test_account_info.rs
  • program-libs/ctoken-types/Cargo.toml
📚 Learning: 2025-10-11T21:59:25.222Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-10-11T21:59:25.222Z
Learning: Applies to program-libs/account-checks/src/account_info/{solana.rs,pinocchio.rs,test_account_info.rs} : Provide SDK-specific AccountInfoTrait implementations in account_info/{solana.rs,pinocchio.rs,test_account_info.rs}

Applied to files:

  • program-libs/account-checks/src/account_info/test_account_info.rs
  • program-libs/ctoken-types/Cargo.toml
📚 Learning: 2025-10-11T21:59:25.222Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-10-11T21:59:25.222Z
Learning: Applies to program-libs/account-checks/src/account_info/{solana.rs,pinocchio.rs,test_account_info.rs} : Gate SDK-specific implementations with #[cfg(feature = "solana"|"pinocchio"|"test-only")]

Applied to files:

  • program-libs/account-checks/src/account_info/test_account_info.rs
  • program-libs/ctoken-types/Cargo.toml
📚 Learning: 2025-10-11T21:59:25.222Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-10-11T21:59:25.222Z
Learning: Applies to program-libs/account-checks/src/account_info/account_info_trait.rs : Ensure the crate compiles with no features enabled by keeping trait definitions free of SDK-specific dependencies

Applied to files:

  • program-libs/account-checks/src/account_info/test_account_info.rs
📚 Learning: 2025-10-11T21:59:25.222Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-10-11T21:59:25.222Z
Learning: Applies to program-libs/account-checks/src/account_info/test_account_info.rs : Use the mock AccountInfo implementation under the test-only feature for unit tests

Applied to files:

  • program-libs/account-checks/src/account_info/test_account_info.rs
📚 Learning: 2025-10-16T06:33:19.426Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-10-16T06:33:19.426Z
Learning: Applies to program-libs/compressible/src/config.rs : Ensure serialization compatibility across Anchor, Pinocchio, and Borsh for core account types used by dependent programs

Applied to files:

  • program-libs/account-checks/src/account_info/test_account_info.rs
  • program-tests/create-address-test-program/src/lib.rs
  • program-libs/compressed-account/src/lib.rs
  • program-libs/compressed-account/src/instruction_data/invoke_cpi.rs
📚 Learning: 2025-10-11T21:59:25.222Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-10-11T21:59:25.222Z
Learning: Applies to program-libs/account-checks/**/Cargo.toml : Define features solana, pinocchio, and test-only in Cargo.toml; default build should enable none

Applied to files:

  • program-libs/account-checks/src/account_info/test_account_info.rs
  • program-libs/ctoken-types/Cargo.toml
  • program-libs/compressed-account/Cargo.toml
  • sdk-libs/sdk/Cargo.toml
📚 Learning: 2025-10-11T21:59:25.222Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-10-11T21:59:25.222Z
Learning: Applies to program-libs/account-checks/src/error.rs : Maintain stable mapping of AccountError to ProgramError, including Pinocchio code mapping (1–11), in error.rs

Applied to files:

  • program-libs/account-checks/src/account_info/test_account_info.rs
📚 Learning: 2025-10-15T03:46:26.767Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: programs/registry/CLAUDE.md:0-0
Timestamp: 2025-10-15T03:46:26.767Z
Learning: Applies to programs/registry/src/lib.rs : Load accounts according to type before check_forester: batched via BatchedMerkleTreeAccount::type_from_account_info(); regular via ctx.accounts.account.load()?.metadata; use custom deserialization when required.

Applied to files:

  • sdk-libs/client/src/rpc/client.rs
📚 Learning: 2025-10-16T06:33:55.362Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-10-16T06:33:55.362Z
Learning: Applies to programs/compressed-token/program/program-libs/ctoken-types/** : Define all state and instruction data structures in the light-ctoken-types crate (program-libs/ctoken-types), including state/, instructions/, and state/extensions/

Applied to files:

  • program-libs/ctoken-types/Cargo.toml
📚 Learning: 2025-10-15T03:46:26.767Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: programs/registry/CLAUDE.md:0-0
Timestamp: 2025-10-15T03:46:26.767Z
Learning: Applies to programs/registry/src/account_compression_cpi/*.rs : Context structs for wrapper instructions must include standard accounts: optional registered_forester_pda (mut), authority Signer, cpi_authority with seeds/bump for CPI_AUTHORITY_PDA_SEED, registered_program_pda, target program handle, log_wrapper, and mutable target_account.

Applied to files:

  • program-tests/create-address-test-program/src/lib.rs
  • sdk-libs/sdk-pinocchio/src/cpi/v1/invoke.rs
  • program-libs/compressed-account/src/instruction_data/invoke_cpi.rs
📚 Learning: 2025-10-16T06:33:19.426Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-10-16T06:33:19.426Z
Learning: Applies to program-libs/compressible/src/config.rs : Maintain CompressibleConfig account structure with Anchor/Borsh/Pod (Pinocchio/Pod) serialization and related state validation methods (validate_active, validate_not_inactive) in src/config.rs

Applied to files:

  • program-tests/create-address-test-program/src/lib.rs
  • program-libs/compressed-account/src/instruction_data/invoke_cpi.rs
📚 Learning: 2025-10-15T03:46:26.767Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: programs/registry/CLAUDE.md:0-0
Timestamp: 2025-10-15T03:46:26.767Z
Learning: Applies to programs/registry/src/account_compression_cpi/**/*.rs : CPI processing functions must derive PDA signer seeds as [CPI_AUTHORITY_PDA_SEED, bump] and use CpiContext::new_with_signer with cpi_authority as the authority account and mapped target accounts.

Applied to files:

  • program-tests/create-address-test-program/src/lib.rs
📚 Learning: 2025-10-15T03:46:26.767Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: programs/registry/CLAUDE.md:0-0
Timestamp: 2025-10-15T03:46:26.767Z
Learning: Applies to programs/registry/src/account_compression_cpi/**/*.rs : Pass the data Vec<u8> through unchanged from the wrapper to the target program CPI; the target program performs deserialization.

Applied to files:

  • sdk-libs/sdk-pinocchio/src/cpi/v1/invoke.rs
  • program-libs/compressed-account/src/instruction_data/invoke_cpi.rs
📚 Learning: 2025-10-16T06:33:19.426Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-10-16T06:33:19.426Z
Learning: Applies to program-libs/compressible/src/error.rs : Define error types with numeric codes in the 19xxx range and propagate hasher errors in the 7xxx range; include ProgramError conversions (Anchor, Pinocchio, Solana) in src/error.rs

Applied to files:

  • program-libs/compressed-account/src/lib.rs
🧬 Code graph analysis (9)
sdk-libs/client/src/rpc/client.rs (1)
sdk-libs/event/src/parse.rs (1)
  • event_from_light_transaction (83-134)
sdk-libs/sdk-types/src/address.rs (1)
program-libs/compressed-account/src/address.rs (2)
  • hashv_to_bn254_field_size_be_const_array (10-10)
  • hashv_to_bn254_field_size_be_const_array (24-24)
program-tests/create-address-test-program/src/lib.rs (3)
sdk-libs/sdk-types/src/cpi_accounts/v1.rs (4)
  • config (205-207)
  • new_with_config (72-78)
  • fee_payer (110-112)
  • account_infos (223-225)
sdk-libs/sdk-types/src/cpi_accounts/v2.rs (4)
  • config (157-159)
  • new_with_config (79-85)
  • fee_payer (87-89)
  • account_infos (175-177)
sdk-libs/sdk-pinocchio/src/cpi/traits.rs (1)
  • to_account_metas (25-25)
program-libs/zero-copy/src/traits/mod.rs (3)
program-libs/zero-copy/src/traits/vec_u8.rs (2)
  • zero_copy_at (43-53)
  • zero_copy_at_mut (61-73)
program-libs/zero-copy/src/traits/zero_copy_at.rs (6)
  • zero_copy_at (18-18)
  • zero_copy_at (25-28)
  • zero_copy_at (34-47)
  • zero_copy_at (54-60)
  • zero_copy_at (67-73)
  • borsh_vec_u8_as_slice (182-189)
program-libs/zero-copy/src/traits/zero_copy_at_mut.rs (16)
  • zero_copy_at_mut (18-20)
  • zero_copy_at_mut (28-33)
  • zero_copy_at_mut (40-55)
  • zero_copy_at_mut (62-66)
  • zero_copy_at_mut (73-77)
  • zero_copy_at_mut (85-90)
  • zero_copy_at_mut (97-117)
  • zero_copy_at_mut (335-340)
  • zero_copy_at_mut (398-410)
  • zero_copy_at_mut (463-470)
  • zero_copy_at_mut (501-506)
  • zero_copy_at_mut (559-578)
  • zero_copy_at_mut (632-639)
  • zero_copy_at_mut (673-678)
  • zero_copy_at_mut (753-759)
  • borsh_vec_u8_as_slice_mut (150-159)
sdk-libs/sdk-pinocchio/src/cpi/v1/invoke.rs (4)
sdk-libs/sdk-pinocchio/src/cpi/traits.rs (1)
  • with_light_account (44-53)
sdk-libs/sdk-pinocchio/src/cpi/v2/invoke.rs (2)
  • with_light_account (28-76)
  • with_light_account (99-118)
program-libs/compressed-account/src/lib.rs (5)
  • from (84-108)
  • from (113-115)
  • from (120-122)
  • from (150-159)
  • from (204-212)
program-libs/compressed-account/src/instruction_data/traits.rs (3)
  • data (27-34)
  • data (87-87)
  • data (108-108)
program-libs/compressed-account/src/lib.rs (2)
program-libs/hasher/src/bigint.rs (1)
  • bigint_to_be_bytes_array (23-36)
program-libs/hasher/src/hash_to_field_size.rs (4)
  • hash_to_field_size (12-12)
  • hash_to_field_size (20-39)
  • hash_to_bn254_field_size_be (99-101)
  • hashv_to_bn254_field_size_be (43-54)
program-libs/compressed-account/src/instruction_data/invoke_cpi.rs (2)
sdk-libs/sdk-pinocchio/src/cpi/v1/invoke.rs (1)
  • data (146-151)
program-libs/compressed-account/src/instruction_data/traits.rs (3)
  • data (27-34)
  • data (87-87)
  • data (108-108)
program-libs/compressed-account/src/address.rs (3)
program-libs/compressed-account/src/compressed_account.rs (3)
  • hash (103-109)
  • hash (324-339)
  • hash (364-377)
program-libs/indexed-array/src/array.rs (1)
  • hash (86-96)
program-tests/system-cpi-test/src/create_pda.rs (1)
  • hash (542-545)
program-libs/hasher/src/hash_to_field_size.rs (2)
program-libs/hasher/src/keccak.rs (1)
  • hashv (18-48)
program-libs/hasher/src/lib.rs (1)
  • hashv (41-41)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (21)
  • GitHub Check: programs (system-cpi-test-v2-functional-account-infos, ["cargo-test-sbf -p system-cpi-v2-test -- ...
  • GitHub Check: programs (system-cpi-test-v2-functional-read-only, ["cargo-test-sbf -p system-cpi-v2-test -- func...
  • GitHub Check: programs (light-system-program-address, ["cargo-test-sbf -p system-test -- test_with_address", "c...
  • GitHub Check: programs (compressed-token-batched-tree, ["cargo-test-sbf -p compressed-token-test -- test_transf...
  • GitHub Check: programs (light-system-program-compression, ["cargo-test-sbf -p system-test -- test_with_compress...
  • GitHub Check: programs (account-compression-and-registry, ["cargo-test-sbf -p account-compression-test", "cargo...
  • GitHub Check: programs (system-cpi-test, ["cargo-test-sbf -p system-cpi-test", "cargo test -p light-system-prog...
  • GitHub Check: programs (compressed-token-and-e2e, ["cargo-test-sbf -p compressed-token-test --test v1", "cargo-...
  • GitHub Check: lint
  • GitHub Check: Forester e2e test
  • GitHub Check: stateless-js-v1
  • GitHub Check: Test program-libs-slow
  • GitHub Check: Test program-libs-fast
  • GitHub Check: cli-v1
  • GitHub Check: cli-v2
  • GitHub Check: Test batched-merkle-tree-simulate
  • GitHub Check: stateless-js-v2
  • GitHub Check: system-programs (sdk-anchor-test-program, ["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -...
  • GitHub Check: system-programs (sdk-token-test-program, ["cargo-test-sbf -p sdk-token-test"])
  • GitHub Check: system-programs (sdk-test-program, ["cargo-test-sbf -p sdk-native-test", "cargo-test-sbf -p sdk-v...
  • GitHub Check: system-programs (sdk-libs, light-sdk-macros light-sdk light-program-test light-client light-compr...
🔇 Additional comments (38)
program-libs/ctoken-types/Cargo.toml (2)

22-22: Verify light-hasher feature gating for no_std compatibility.

light-hasher is pulled in without explicit features. Given the no_std PR objective, confirm whether alloc or std should be conditionally enabled via a feature or if this dependency is intentionally no_std-compatible by default.


48-49: Clarify whether dev-dependency std/solana features should be conditional.

Lines 48–49 add "std" and "solana" to light-account-checks unconditionally in dev-dependencies. Based on learnings, this crate should define solana, pinocchio, and test-only features with default = []. Verify whether these should be gated behind a dev feature (e.g., dev-feature = ["light-account-checks/test-only", "light-account-checks/std", "light-account-checks/solana"]) or are appropriately unconditional for tests.

program-libs/compressed-account/Cargo.toml (3)

10-12: Verify whether alloc-by-default aligns with no_std refactoring goal.

Line 10 sets default = ["alloc"], meaning this crate enables heap allocation by default. While the new alloc and std feature hierarchy (lines 11–12) is well-structured for no_std builds, enabling alloc by default may allow consumers to accidentally pull in alloc when they intended a fully no_std build.

Verify whether this is intentional (e.g., if consumers are expected to always need alloc) or if default = [] would better align with the no_std refactoring objectives, leaving it to consumers to opt-in via features = ["alloc"].


27-27: Excellent feature gating for no_std support.

Lines 27 and 35 correctly disable default features on light-zero-copy and borsh respectively, with explicit opt-in for required features. This approach properly supports no_std builds while maintaining explicit control over alloc/std dependencies.

Also applies to: 35-35


49-49: Dev-dependency std feature properly gated.

Line 49 appropriately enables std on the dev-dependency light-compressed-account, allowing tests to use full std features without requiring std for the library build itself.

sdk-libs/client/src/rpc/client.rs (2)

9-13: LGTM! Import paths updated for new crate structure.

The import paths have been correctly updated to use the new light_event crate for event types, and TreeType is now imported directly from light_compressed_account. These changes align with the PR's objective to refactor event-related types into a dedicated workspace crate.


301-303: Excellent error handling improvement!

Replacing unwrap() with map_err provides much better error propagation and debugging information. The formatted error message clearly indicates the failure point and includes the underlying error details.

sdk-libs/sdk/Cargo.toml (1)

43-46: All feature definitions are correct; the original review concern about feature mismatch is unfounded.

The verification confirms that all referenced features are properly defined:

  • light-sdk-types defines std = ["alloc", "light-compressed-account/std", "solana-msg"]
  • light-compressed-account defines std = ["alloc", "borsh/std", "dep:solana-msg", "light-zero-copy/std"]
  • light-hasher defines both std = ["alloc"] and keccak = ["dep:sha3"]

Regarding the dev-dependencies concern: when Cargo processes light-hasher = { workspace = true, features = ["keccak"] } without explicitly disabling default features, the default features are automatically included. Since light-hasher specifies default = ["std"], the dev build will have both std and keccak enabled. There is no feature mismatch or compilation issue.

The code changes are correct and follow proper no_std refactoring patterns.

Likely an incorrect or invalid review comment.

program-libs/account-checks/src/account_info/test_account_info.rs (4)

5-6: LGTM: Explicit std import for Vec usage.

The explicit extern crate std; and use std::vec::Vec; are correct since this test helper module requires Vec and related macros. The pattern of using core::mem operations (lines 64-67) while importing std for collections is appropriate.


64-67: LGTM: Appropriate use of core::mem operations.

Correctly shifted from std::mem to core::mem for forget and transmute operations. These functions are available in core, reducing unnecessary std dependency. The intentional memory leak pattern (line 63 comment) is acceptable for test-only mocking.


111-112: LGTM: Explicit std imports align with feature gate.

The explicit extern crate std; and consolidated imports correctly declare the std dependency now required by the feature gate (line 109). All imported items (RefCell, Rc, vec, Vec) are used in the module implementation.


109-109: Feature gate change verified—no breaking code dependencies found.

The grep results confirm no code or tests in the account-checks crate expect the solana_program submodule (at line 109) to be available with feature = "solana" alone. The explicit std requirement is accurate since the implementation uses Rc, RefCell, and Vec, which mandate std support.

sdk-libs/sdk-types/src/address.rs (1)

65-66: ****

The const generic parameter <3> is correctly sized. The function hashv_to_bn254_field_size_be_const_array internally appends a bump_seed slice to the input (line 79 of hash_to_field_size.rs), so MAX_SLICES must account for input slices plus this additional element. For 2 input slices, <3> is correct (2 inputs + 1 bump_seed = 3 total capacity).

Likely an incorrect or invalid review comment.

program-tests/create-address-test-program/src/lib.rs (2)

31-55: LGTM! Clean separation of serializable config.

The CpiAccountsConfigLocal pattern effectively separates the serializable configuration fields from the runtime cpi_signer, which is always reconstructed as the constant LIGHT_CPI_SIGNER. The bidirectional conversions are correct and maintain consistency.


106-109: LGTM! Proper bounds check added.

The runtime guard correctly prevents out-of-bounds access to remaining_accounts[1] and remaining_accounts[2] in the subsequent code (lines 117, 121). This improves safety when write_cpi_context is enabled.

program-libs/zero-copy/src/traits/zero_copy_at_mut.rs (3)

1-2: LGTM! Correct alloc import for no_std support.

The conditional import of Vec from alloc is correctly gated and aligns with the no_std refactoring objectives.


93-118: LGTM! Vec deserialization with heap exhaustion protection.

The implementation correctly:

  • Gates Vec support behind the alloc feature
  • Includes a heap exhaustion check (Line 104-108) that ensures the buffer has at least num_slices bytes
  • Uses u32_to_usize for safe size conversion

The check assumes each element requires at least 1 byte when serialized, which is reasonable for most use cases.


187-190: LGTM! Correctly gated trait implementation.

The ZeroCopyStructInnerMut implementation for Vec<T> is properly feature-gated and consistent with other implementations in the module.

program-libs/zero-copy/src/traits/mod.rs (1)

1-13: LGTM! Consistent alloc feature gating.

All new alloc-dependent modules and re-exports are correctly gated with #[cfg(feature = "alloc")], ensuring the crate can build in no_std environments without heap allocation support.

program-libs/zero-copy/src/lib.rs (2)

2-3: LGTM! Correct extern crate declaration for no_std + alloc.

The conditional extern crate alloc declaration is properly gated and necessary for using heap-allocated types in a no_std environment.


13-22: LGTM! Feature gate migration from std to alloc.

The change from std to alloc feature gating for the traits module and related re-exports is correct, enabling the crate to work in no_std environments with allocator support while still supporting std environments.

program-libs/zero-copy/src/traits/zero_copy_at.rs (2)

1-2: LGTM! Correct alloc import for no_std support.

The conditional import of Vec from alloc is properly gated and consistent with the no_std refactoring.


117-140: LGTM! Vec deserialization with heap exhaustion protection.

The ZeroCopyAt implementation for Vec<T> correctly:

  • Gates support behind the alloc feature
  • Includes heap exhaustion protection (Lines 126-130)
  • Uses safe size conversion with u32_to_usize
program-libs/zero-copy/src/traits/zero_copy_new.rs (2)

1-2: LGTM! Correct alloc import for no_std support.

The conditional import is properly gated for the no_std refactoring.


238-274: LGTM! Vec initialization with overflow protection.

The ZeroCopyNew implementation correctly:

  • Gates Vec support behind the alloc feature
  • Uses checked_add to prevent overflow when calculating byte length (Line 248)
  • Writes a 4-byte U32 length prefix
  • Initializes each element with its configuration

The implementation is consistent with other Vec-based zero-copy traits.

program-libs/zero-copy/src/traits/vec_u8.rs (2)

1-2: LGTM! Correct alloc import for no_std support.

The conditional import is properly gated.


12-21: LGTM! VecU8 wrapper type is correctly gated.

The wrapper type and constructor are properly feature-gated and provide a clean API.

sdk-libs/sdk-pinocchio/src/cpi/v1/invoke.rs (5)

4-5: LGTM: Feature-gated import.

The conditional import of CompressedAccountInfoTrait is correctly feature-gated and aligns with the PR's no_std refactoring goals.


129-136: LGTM: BorshSerialize implementation updated for no_std.

The explicit use of borsh::maybestd::io::Write is correct for no_std compatibility, as maybestd is Borsh's compatibility layer that works in both std and no_std environments.


145-151: LGTM: Correctly feature-gated for alloc.

The data() method is appropriately gated behind the alloc feature since it returns Vec<u8>, which requires heap allocation. This aligns with the PR's no_std refactoring objectives.

Note that callers without the alloc feature will need to use alternative serialization methods.


69-79: Feature gate and signature updates look good.

The method signature changes are consistent with:

  • The trait definition in sdk-pinocchio/src/cpi/traits.rs (shown in relevant snippets)
  • The v2 implementation in sdk-pinocchio/src/cpi/v2/invoke.rs
  • The PR's goal of feature-gated no_std support

The use of crate:: prefixes for trait bounds improves scoping clarity.


84-86: Fix incorrect error conversion at line 86 that will not compile.

The conversion u64::from(e) as u32 is invalid—there is no From<ProgramError> for u64 implementation in the codebase. This pattern is inconsistent with the correct error handling at lines 92 and 109, which properly convert via LightSdkError::from() then u32::from(e).

Align line 86 with the pattern at lines 92 and 109:

.map_err(crate::error::LightSdkError::from)
.map_err(|e| ProgramError::Custom(u32::from(e)))?

Alternatively, consider whether the error should return CompressedAccountError directly (which has proper From<CompressedAccountError> for u32 implementations matching error codes 12006–12021).

⛔ Skipped due to learnings
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-10-11T21:59:25.222Z
Learning: Applies to program-libs/account-checks/src/**/*.rs : Return AccountError variants (codes 12006–12021) and rely on automatic ProgramError conversions; avoid returning raw ProgramError directly
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-10-11T21:59:25.222Z
Learning: Applies to program-libs/account-checks/src/error.rs : Maintain stable mapping of AccountError to ProgramError, including Pinocchio code mapping (1–11), in error.rs
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-10-11T21:59:25.222Z
Learning: Applies to program-libs/account-checks/src/**/*.rs : On account initialization, call account_info_init to set the 8-byte discriminator
program-libs/compressed-account/src/instruction_data/invoke_cpi.rs (1)

11-11: LGTM on no_std aliasing.

Importing crate::Vec and InstructionDiscriminator here keeps the module no_std‑friendly and consistent.

program-libs/compressed-account/src/lib.rs (2)

185-193: LGTM on core-only Display/Default.

Using core::fmt and core::default::Default keeps no_std clean.

Also applies to: 196-200


41-44: LGTM on error text clarifications.

Wording updates improve diagnostics; numeric mapping below remains consistent.

program-libs/hasher/src/hash_to_field_size.rs (2)

15-40: LGTM on alloc-gated blanket impl and debug gating.

Correctly propagates Borsh errors and restricts println! to all(debug_assertions, std).


42-54: Verify workspace features ensure keccak is enabled where hash_to_bn254_field_size_be is used.

The functions hash_to_bn254_field_size_be(), hashv_to_bn254_field_size_be() and related variants use Keccak::hashv().unwrap() but are not gated on the keccak feature. On non-Solana platforms without keccak enabled, they will panic with KeccakFeatureNotEnabled.

Current usage is safe:

  • Solana programs use the syscall path (always available)
  • Tests explicitly enable keccak

However, sdk-libs/sdk-types (which exports public address derivation functions) imports light-hasher with workspace defaults (default-features = false), making keccak unavailable. These public APIs could be called from non-Solana contexts without the feature, causing panics.

Recommendation: Either gate these functions on #[cfg(feature = "keccak")] or explicitly enable keccak in packages that export them publicly (e.g., sdk-types). Alternatively, adopt fallible variants and propagate errors instead of unwrapping.

program-libs/compressed-account/src/address.rs (1)

9-11: LGTM on const-array helper usage.

No allocation; correct error propagation.

Comment on lines +24 to 26
hashv_to_bn254_field_size_be_const_array::<4>(&slices)
.expect("hashv_to_bn254_field_size_be_const_array::<4> should be infallible for Keccak")
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Eliminate expect; return Result or gate to keccak/solana.

expect will panic when Keccak::hashv returns an error (e.g., feature="keccak" disabled). Prefer fallible API.

Option A (preferred; safer API):

-pub fn derive_address(
+pub fn derive_address(
     seed: &[u8; 32],
     merkle_tree_pubkey: &[u8; 32],
     program_id_bytes: &[u8; 32],
-) -> [u8; 32] {
+) -> Result<[u8; 32], CompressedAccountError> {
@@
-    hashv_to_bn254_field_size_be_const_array::<4>(&slices)
-        .expect("hashv_to_bn254_field_size_be_const_array::<4> should be infallible for Keccak")
+    Ok(
+        hashv_to_bn254_field_size_be_const_array::<4>(&slices)
+            .map_err(CompressedAccountError::from)?
+    )
 }

Option B (non-breaking): add #[cfg(any(target_os = "solana", feature = "keccak"))] to this function as well.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In program-libs/compressed-account/src/address.rs around lines 24 to 26, remove
the expect call that can panic when Keccak is unavailable and make this function
fallible (or gate it to Keccak/Solana); specifically change the function to
return Result<_, _> and propagate the error from
hashv_to_bn254_field_size_be_const_array::<4>(&slices) using ? (or map_err to
the crate's error type), update all callers to handle the Result, and if you
prefer the non-breaking alternative instead add #[cfg(any(target_os = "solana",
feature = "keccak"))] to this function so it is only compiled when Keccak is
available.

Comment on lines +15 to +23
#[cfg_attr(
all(feature = "std", feature = "anchor"),
derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
)]
#[cfg_attr(
not(feature = "anchor"),
derive(borsh::BorshDeserialize, borsh::BorshSerialize)
)]
#[derive(Debug, PartialEq, Default, Clone, ZeroCopyMut)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Anchor derive gated on std → anchor+no_std loses derives (likely compile break).

If feature="anchor" but not "std" (typical BPF/no_std), neither Anchor nor Borsh derives apply. Drop the std requirement and derive Anchor when anchor is enabled; otherwise derive Borsh.

Apply:

 #[cfg_attr(
-    all(feature = "std", feature = "anchor"),
+    feature = "anchor",
     derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
 )]
 #[cfg_attr(
     not(feature = "anchor"),
     derive(borsh::BorshDeserialize, borsh::BorshSerialize)
 )]

Also ensure Cargo enables borsh with features = ["derive"] when feature = "anchor" is off.


I'll verify this review comment by checking the actual code and Cargo configuration.

</script>

🏁 Script executed:

#!/bin/bash
# Verify the file and feature gate configuration
echo "=== Checking invoke_cpi.rs lines 15-23 ==="
cat program-libs/compressed-account/src/instruction_data/invoke_cpi.rs | head -30 | tail -20

echo -e "\n=== Checking Cargo.toml for borsh configuration ==="
cat program-libs/compressed-account/Cargo.toml

echo -e "\n=== Searching for any conditional compilation on this struct ==="
rg -nB5 -A15 "ZeroCopyMut" program-libs/compressed-account/src/instruction_data/invoke_cpi.rs

Length of output: 4253


Feature gate requires both std and anchor, breaking no_std+anchor builds.

The condition all(feature = "std", feature = "anchor") means Anchor derives apply only when both features are enabled. In typical BPF/no_std scenarios where feature = "anchor" is set but feature = "std" is not, neither the Anchor nor Borsh derives are applied, resulting in a compile break.

Additionally, the borsh dependency in Cargo.toml lacks the "derive" feature, so Borsh macros will fail when anchor is disabled.

Fixes required:

  1. Remove the std requirement from the Anchor derive gate (lines 15–17):
 #[cfg_attr(
-    all(feature = "std", feature = "anchor"),
+    feature = "anchor",
     derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
 )]
  1. Add features = ["derive"] to borsh in Cargo.toml:
-borsh = { workspace = true, default-features = false }
+borsh = { workspace = true, default-features = false, features = ["derive"] }
🤖 Prompt for AI Agents
In program-libs/compressed-account/src/instruction_data/invoke_cpi.rs around
lines 15 to 23, the cfg_attr currently requires both feature = "std" and feature
= "anchor" which prevents Anchor derives in no_std+anchor builds; change the
cfg_attr to require only feature = "anchor" so the
AnchorDeserialize/AnchorSerialize derives apply when anchor is enabled without
std, and separately update Cargo.toml to add features = ["derive"] to the borsh
dependency so borsh derive macros are available when anchor is disabled.

Comment on lines +26 to 30
pub use light_hasher::bigint::bigint_to_be_bytes_array;
#[cfg(feature = "alloc")]
pub use light_hasher::hash_to_field_size::{
hash_to_bn254_field_size_be, hashv_to_bn254_field_size_be,
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Gate bigint re-export behind alloc to avoid no_std build breaks.

bigint_to_be_bytes_array depends on alloc (BigUint::to_bytes_be). Unconditional re-export can fail in no_std without alloc.

Apply:

- pub use light_hasher::bigint::bigint_to_be_bytes_array;
+#[cfg(feature = "alloc")]
+pub use light_hasher::bigint::bigint_to_be_bytes_array;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub use light_hasher::bigint::bigint_to_be_bytes_array;
#[cfg(feature = "alloc")]
pub use light_hasher::hash_to_field_size::{
hash_to_bn254_field_size_be, hashv_to_bn254_field_size_be,
};
#[cfg(feature = "alloc")]
pub use light_hasher::bigint::bigint_to_be_bytes_array;
#[cfg(feature = "alloc")]
pub use light_hasher::hash_to_field_size::{
hash_to_bn254_field_size_be, hashv_to_bn254_field_size_be,
};
🤖 Prompt for AI Agents
In program-libs/compressed-account/src/lib.rs around lines 26–30, the public
re-export of bigint_to_be_bytes_array is unconditional but it relies on alloc
(BigUint::to_bytes_be) and breaks no_std builds; guard that re-export with the
same #[cfg(feature = "alloc")] used for the other hash_to_field_size re-exports
so the line becomes conditional, and ensure the crate's Cargo features expose
"alloc" where appropriate.

Comment on lines +23 to 36
#[cfg(feature = "alloc")]
impl<T> Deref for VecU8<T> {
type Target = Vec<T>;
fn deref(&self) -> &Self::Target {
&self.0
}
}

#[cfg(feature = "alloc")]
impl DerefMut for VecU8<u8> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

DerefMut implementation is inconsistent with Deref.

Deref is implemented for all VecU8<T> (Line 24), but DerefMut is only implemented for VecU8<u8> (Line 32). This asymmetry means users cannot mutate VecU8<T> where T is not u8, which is unexpected and inconsistent.

Apply this fix to make DerefMut generic:

-impl DerefMut for VecU8<u8> {
+impl<T> DerefMut for VecU8<T> {
     fn deref_mut(&mut self) -> &mut Self::Target {
         &mut self.0
     }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#[cfg(feature = "alloc")]
impl<T> Deref for VecU8<T> {
type Target = Vec<T>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(feature = "alloc")]
impl DerefMut for VecU8<u8> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[cfg(feature = "alloc")]
impl<T> Deref for VecU8<T> {
type Target = Vec<T>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(feature = "alloc")]
impl<T> DerefMut for VecU8<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
🤖 Prompt for AI Agents
In program-libs/zero-copy/src/traits/vec_u8.rs around lines 23 to 36, the
DerefMut impl is only for VecU8<u8> causing asymmetry with the generic Deref
impl; change the DerefMut to be generic as well by implementing impl<T> DerefMut
for VecU8<T> (guarded with #[cfg(feature = "alloc")]) and make deref_mut return
&mut self.0 with the same Target type as Deref so all VecU8<T> types can be
mutably dereferenced.

Comment on lines +290 to +294
let extra = cpi_accounts
.account_infos()
.len()
.saturating_sub(PROGRAM_ACCOUNTS_LEN);
let mut account_metas = Vec::with_capacity(1 + extra);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Under-allocated capacity causes unnecessary reallocations.

The capacity calculation is incorrect. The function adds approximately 6 fixed AccountMeta entries (lines 296–328) plus optional/tree accounts from account_infos, totaling roughly account_infos().len() entries. However, the current formula allocates only 1 + (len - PROGRAM_ACCOUNTS_LEN) capacity, which under-allocates by about 5 slots, forcing the Vec to reallocate during growth.

Apply this diff to fix the capacity:

-    let extra = cpi_accounts
-        .account_infos()
-        .len()
-        .saturating_sub(PROGRAM_ACCOUNTS_LEN);
-    let mut account_metas = Vec::with_capacity(1 + extra);
+    let mut account_metas = Vec::with_capacity(cpi_accounts.account_infos().len());

Alternatively, if you prefer expressing it as fixed + extra:

     let extra = cpi_accounts
         .account_infos()
         .len()
         .saturating_sub(PROGRAM_ACCOUNTS_LEN);
-    let mut account_metas = Vec::with_capacity(1 + extra);
+    let mut account_metas = Vec::with_capacity(6 + extra);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let extra = cpi_accounts
.account_infos()
.len()
.saturating_sub(PROGRAM_ACCOUNTS_LEN);
let mut account_metas = Vec::with_capacity(1 + extra);
let mut account_metas = Vec::with_capacity(cpi_accounts.account_infos().len());
🤖 Prompt for AI Agents
In program-tests/create-address-test-program/src/lib.rs around lines 290 to 294,
the Vec capacity is under-allocated (currently set to 1 + extra) which causes
reallocations because there are roughly PROGRAM_ACCOUNTS_LEN fixed entries plus
the optional/tree accounts; change the allocation to reserve the full expected
size (fixed entries + extra) — e.g. use PROGRAM_ACCOUNTS_LEN + extra (or
directly account_infos().len()) as the with_capacity argument so the Vec is
sized correctly up front.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (3)
program-libs/compressed-account/src/instruction_data/traits.rs (2)

3-7: Previous conditional import issue resolved.

The conditional AnchorSerialize import now correctly uses #[cfg(all(feature = "std", feature = "anchor"))] at line 3, which addresses the gap identified in the previous review where enabling anchor without std would leave no import available. The fallback to borsh::BorshSerialize at line 7 now covers all other feature combinations.


35-44: Critical: data_array overwrites discriminator and fails to track length.

The self.serialize(&mut data.as_mut_slice()) call at line 40 writes over the discriminator bytes added at line 39, because as_mut_slice() returns a mutable slice of the ArrayVec's current contents. Additionally, serialize() writes to the slice but doesn't update the ArrayVec's internal length tracker, leaving data.len() at just the discriminator length.

Based on the pattern in program-libs/compressed-account/src/instruction_data/invoke_cpi.rs (lines 37-47), serialize to a temporary buffer first, then extend:

 fn data_array<const N: usize>(&self) -> Result<ArrayVec<[u8; N]>, CompressedAccountError> {
     let mut data = ArrayVec::new();
-    // Add discriminator
     data.extend_from_slice(self.discriminator());
-    self.serialize(&mut data.as_mut_slice())
-        .map_err(|_e| CompressedAccountError::InputTooLarge(data.len().saturating_sub(N)))?;
-
+    
+    // Serialize into a temporary buffer
+    let mut temp_buf = [0u8; N];
+    let remaining = N.saturating_sub(data.len());
+    let mut cursor = &mut temp_buf[..remaining];
+    self.serialize(&mut cursor)
+        .map_err(|_| CompressedAccountError::InvalidArgument)?;
+    let written = remaining - cursor.len();
+    data.try_extend_from_slice(&temp_buf[..written])
+        .map_err(|_| CompressedAccountError::InputTooLarge(written))?;
+    
     Ok(data)
 }
Cargo.toml (1)

171-171: Explicitly declare required features for downstream crates—multiple workspace members are missing critical feature enablement.

The script confirms the core concern: setting default-features = false for 4 of the 5 affected crates (light-hasher, light-account-checks, light-compressed-account, light-sdk-types) breaks downstream crates using { workspace = true } without explicit features, since they inherit default-features = false and won't compile if code requires std or alloc.

Spot-checking reveals the issue is widespread:

  • ✓ Some crates have proper features (e.g., sdk-libs/sdk/Cargo.toml declares features for most affected crates; forester-utils/Cargo.toml has features for light-hasher and light-compressed-account)
  • ✗ Many crates are still missing feature declarations, including:
    • program-libs/bloom-filter/Cargo.toml: light-hasher = { workspace = true } (needs features)
    • program-libs/compressible/Cargo.toml: light-hasher and light-account-checks lack feature declarations
    • anchor-programs/system/Cargo.toml: light-zero-copy = { workspace = true } (needs features)
    • forester-utils/Cargo.toml: light-account-checks = { workspace = true } (needs features)

Audit all 51+ workspace members and add explicit feature declarations (e.g., features = ["std"] or features = ["solana"] depending on context). Verify the workspace compiles with these changes before merging.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1e4cd1e and 06f00c1.

📒 Files selected for processing (11)
  • Cargo.toml (3 hunks)
  • program-libs/account-checks/Cargo.toml (2 hunks)
  • program-libs/batched-merkle-tree/src/errors.rs (2 hunks)
  • program-libs/compressed-account/Cargo.toml (3 hunks)
  • program-libs/compressed-account/src/instruction_data/traits.rs (1 hunks)
  • program-libs/compressible/Cargo.toml (1 hunks)
  • program-libs/hasher/tests/hash_to_field_size.rs (1 hunks)
  • program-libs/zero-copy/tests/borsh.rs (1 hunks)
  • scripts/lint.sh (1 hunks)
  • sdk-libs/compressed-token-sdk/Cargo.toml (2 hunks)
  • sdk-libs/sdk/Cargo.toml (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • scripts/lint.sh
  • program-libs/account-checks/Cargo.toml
  • sdk-libs/compressed-token-sdk/Cargo.toml
🧰 Additional context used
🧠 Learnings (9)
📚 Learning: 2025-10-16T06:33:19.426Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-10-16T06:33:19.426Z
Learning: Applies to program-libs/compressible/src/config.rs : Ensure serialization compatibility across Anchor, Pinocchio, and Borsh for core account types used by dependent programs

Applied to files:

  • program-libs/compressible/Cargo.toml
  • program-libs/compressed-account/Cargo.toml
  • program-libs/compressed-account/src/instruction_data/traits.rs
  • Cargo.toml
📚 Learning: 2025-10-11T21:59:25.222Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-10-11T21:59:25.222Z
Learning: Applies to program-libs/account-checks/**/Cargo.toml : Define features solana, pinocchio, and test-only in Cargo.toml; default build should enable none

Applied to files:

  • program-libs/compressible/Cargo.toml
  • program-libs/compressed-account/Cargo.toml
  • sdk-libs/sdk/Cargo.toml
📚 Learning: 2025-10-16T06:33:19.426Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-10-16T06:33:19.426Z
Learning: Applies to program-libs/compressible/src/error.rs : Define error types with numeric codes in the 19xxx range and propagate hasher errors in the 7xxx range; include ProgramError conversions (Anchor, Pinocchio, Solana) in src/error.rs

Applied to files:

  • program-libs/batched-merkle-tree/src/errors.rs
📚 Learning: 2025-10-11T21:59:25.222Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-10-11T21:59:25.222Z
Learning: Applies to program-libs/account-checks/src/error.rs : Maintain stable mapping of AccountError to ProgramError, including Pinocchio code mapping (1–11), in error.rs

Applied to files:

  • program-libs/batched-merkle-tree/src/errors.rs
📚 Learning: 2025-10-11T21:59:25.222Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-10-11T21:59:25.222Z
Learning: Applies to program-libs/account-checks/src/account_info/{solana.rs,pinocchio.rs,test_account_info.rs} : Gate SDK-specific implementations with #[cfg(feature = "solana"|"pinocchio"|"test-only")]

Applied to files:

  • program-libs/batched-merkle-tree/src/errors.rs
  • sdk-libs/sdk/Cargo.toml
📚 Learning: 2025-10-16T06:33:55.362Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-10-16T06:33:55.362Z
Learning: Applies to programs/compressed-token/program/programs/compressed-token/anchor/src/lib.rs : Define all program-specific error codes in anchor_compressed_token::ErrorCode in programs/compressed-token/anchor/src/lib.rs; return errors as ProgramError::Custom(error_code as u32)

Applied to files:

  • program-libs/batched-merkle-tree/src/errors.rs
📚 Learning: 2025-10-16T06:33:19.426Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-10-16T06:33:19.426Z
Learning: Applies to program-libs/compressible/src/config.rs : Maintain CompressibleConfig account structure with Anchor/Borsh/Pod (Pinocchio/Pod) serialization and related state validation methods (validate_active, validate_not_inactive) in src/config.rs

Applied to files:

  • program-libs/compressed-account/src/instruction_data/traits.rs
📚 Learning: 2025-10-11T21:59:52.712Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/account-checks/docs/CLAUDE.md:0-0
Timestamp: 2025-10-11T21:59:52.712Z
Learning: Applies to program-libs/account-checks/docs/**/DISCRIMINATOR.md : DISCRIMINATOR.md must document 8-byte discriminators, the Discriminator trait, constant arrays for compile-time checks, and integration with account initialization

Applied to files:

  • program-libs/compressed-account/src/instruction_data/traits.rs
📚 Learning: 2025-10-11T21:59:25.222Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-10-11T21:59:25.222Z
Learning: Applies to program-libs/account-checks/src/**/*.rs : Validate account type with 8-byte discriminators using check_discriminator before deserialization

Applied to files:

  • program-libs/compressed-account/src/instruction_data/traits.rs
🧬 Code graph analysis (2)
program-libs/hasher/tests/hash_to_field_size.rs (2)
program-libs/hasher/src/hash_to_field_size.rs (5)
  • hash_to_field_size (12-12)
  • hash_to_field_size (20-39)
  • hashv_to_bn254_field_size_be (43-54)
  • is_smaller_than_bn254_field_size_be (104-109)
  • hash_to_bn254_field_size_be (99-101)
program-libs/hasher/src/bigint.rs (1)
  • bigint_to_be_bytes_array (23-36)
program-libs/compressed-account/src/instruction_data/traits.rs (3)
program-libs/compressed-account/src/instruction_data/invoke_cpi.rs (2)
  • discriminator (126-128)
  • data (38-48)
sdk-libs/sdk-pinocchio/src/cpi/v1/invoke.rs (2)
  • discriminator (139-141)
  • data (146-151)
sdk-libs/sdk-pinocchio/src/cpi/traits.rs (1)
  • data_array (112-112)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (21)
  • GitHub Check: cli-v2
  • GitHub Check: Test program-libs-slow
  • GitHub Check: Test program-libs-fast
  • GitHub Check: Test batched-merkle-tree-simulate
  • GitHub Check: programs (system-cpi-test-v2-functional-read-only, ["cargo-test-sbf -p system-cpi-v2-test -- func...
  • GitHub Check: programs (system-cpi-test-v2-functional-account-infos, ["cargo-test-sbf -p system-cpi-v2-test -- ...
  • GitHub Check: programs (system-cpi-test, ["cargo-test-sbf -p system-cpi-test", "cargo test -p light-system-prog...
  • GitHub Check: programs (account-compression-and-registry, ["cargo-test-sbf -p account-compression-test", "cargo...
  • GitHub Check: programs (light-system-program-compression, ["cargo-test-sbf -p system-test -- test_with_compress...
  • GitHub Check: programs (compressed-token-batched-tree, ["cargo-test-sbf -p compressed-token-test -- test_transf...
  • GitHub Check: stateless-js-v1
  • GitHub Check: programs (light-system-program-address, ["cargo-test-sbf -p system-test -- test_with_address", "c...
  • GitHub Check: stateless-js-v2
  • GitHub Check: programs (compressed-token-and-e2e, ["cargo-test-sbf -p compressed-token-test --test v1", "cargo-...
  • GitHub Check: lint
  • GitHub Check: system-programs (sdk-test-program, ["cargo-test-sbf -p sdk-native-test", "cargo-test-sbf -p sdk-v...
  • GitHub Check: Forester e2e test
  • GitHub Check: system-programs (sdk-token-test-program, ["cargo-test-sbf -p sdk-token-test"])
  • GitHub Check: system-programs (sdk-libs, light-sdk-macros light-sdk light-program-test light-client light-compr...
  • GitHub Check: cli-v1
  • GitHub Check: system-programs (sdk-anchor-test-program, ["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -...
🔇 Additional comments (21)
program-libs/zero-copy/tests/borsh.rs (1)

288-294: LGTM! Improves consistency across test suite.

The change from Result<(), Box<dyn std::error::Error>> to a plain unit return type with .unwrap() aligns test_array_struct with the pattern used by all other tests in this file. Using .unwrap() in tests is appropriate since test failures should panic rather than propagate errors.

program-libs/compressed-account/src/instruction_data/traits.rs (3)

1-1: LGTM: Correct no_std migration.

Switching from std::fmt::Debug to core::fmt::Debug correctly supports no_std environments while maintaining functionality.


9-9: LGTM: Appropriate no_std vector choice.

The tinyvec::ArrayVec import supports stack-allocated, fixed-capacity vectors suitable for no_std environments, aligning with the new data_array method.


24-33: LGTM: Correct allocation-aware implementation.

The data() method is properly gated with #[cfg(feature = "alloc")] since try_to_vec requires allocation. The use of crate::Vec and the serialization pattern (discriminator followed by serialized inputs) align with established patterns in the codebase.

program-libs/hasher/tests/hash_to_field_size.rs (1)

31-44: LGTM!

The test correctly validates that hash_to_field_size() produces the same result as manually serializing with Borsh and calling hash_to_bn254_field_size_be().

program-libs/compressible/Cargo.toml (1)

9-9: LGTM! Anchor feature correctly enables std.

Adding light-compressed-account/std to the anchor feature is appropriate, as Anchor programs operate in a std environment. This ensures proper feature propagation given that the workspace now sets default-features = false for light-compressed-account.

sdk-libs/sdk/Cargo.toml (3)

14-14: Breaking change: borsh is now always included.

The removal of borsh from the default feature set (line 14) combined with making it a required dependency (line 38) means borsh is now unconditionally compiled into all builds. Previously, users could opt out via default-features = false.

Is this intentional? If certain build configurations should exclude borsh, consider keeping it optional and adding it to appropriate feature sets.

Also applies to: 38-38


42-42: Good—explicit std feature enablement.

These changes correctly address the workspace-level default-features = false by explicitly enabling the std feature for light-sdk-types, light-compressed-account, and light-hasher. This prevents compilation failures when default features are disabled.

Also applies to: 44-45


52-52: LGTM! Keccak feature added for testing.

Adding the keccak feature to light-hasher in dev-dependencies is appropriate for testing hash functionality.

Cargo.toml (3)

27-27: New workspace member added.

The addition of sdk-libs/event introduces a new crate to the workspace, likely for event handling functionality mentioned in the PR summary.


170-170: New dependency for event handling.

The light-event crate is added to support the new workspace member at sdk-libs/event.


129-129: Borsh default features disabled workspace-wide.

Setting default-features = false for borsh aligns with the no_std refactoring goals. Ensure all crates that require borsh features (like derive) explicitly enable them.

Verify that all workspace members using borsh have explicit feature declarations where needed (e.g., features = ["derive"]).

program-libs/batched-merkle-tree/src/errors.rs (2)

39-41: Good—prevents duplicate ProgramError variants.

Refining the feature gate to #[cfg(all(feature = "solana", not(feature = "pinocchio")))] ensures that only one ProgramError variant exists when both features are enabled. Line 36 already defines a ProgramError(u64) variant for pinocchio, so this change prevents a conflict.


79-81: Correct—conversion arm supports both harnesses.

Updating the feature gate to #[cfg(any(feature = "pinocchio", feature = "solana"))] ensures the ProgramError conversion arm is available for both Pinocchio and Solana builds, aligning with the variant definitions above.

program-libs/compressed-account/Cargo.toml (7)

10-12: Well-structured no_std support with alloc tier.

The new feature hierarchy (default → alloc → std) enables flexible compilation:

  • default = ["alloc"]: reasonable baseline supporting heap allocations without full std
  • alloc: enables allocator-dependent code via light-hasher/alloc
  • std: adds standard library support including borsh/std and solana-msg

This aligns with the PR's no_std refactoring goals and addresses the workspace-level default-features = false by explicitly defining feature tiers. Based on learnings.


14-14: Correct—anchor requires std.

Adding std to the anchor feature is appropriate since Anchor programs run in a std environment and require standard library functionality.


27-27: Good—explicit feature control for light-zero-copy.

Setting default-features = false with explicit features = ["alloc", "mut", "derive"] gives precise control over light-zero-copy's capabilities, aligning with the workspace-wide default-features strategy.


31-31: Making solana-msg optional supports no_std builds.

Marking solana-msg as optional and gating it behind the std feature (line 12) enables no_std compilation paths where logging may not be needed.


35-35: Borsh default-features = false aligns with no_std.

Disabling borsh's default features is correct for no_std support. The std feature on line 12 explicitly enables borsh/std when needed. Based on learnings: borsh 1.5.7 (current workspace version per line 129 of Cargo.toml at 0.10.4) supports no_std with optional std feature.


49-49: Self-dependency in dev-dependencies is appropriate.

Adding light-compressed-account as a dev-dependency with features = ["new-unique", "std"] is a common pattern for testing feature combinations and ensuring feature-gated code paths work correctly.


39-39: tinyvec usage is correct—no alloc feature needed.

Verification confirms that only ArrayVec (stack-only) is used in the codebase:

  • program-libs/compressed-account/src/compressed_account.rs line 259: Fixed-capacity stack vector
  • program-libs/compressed-account/src/instruction_data/traits.rs line 36-37: Generic ArrayVec return type

TinyVec (which requires the alloc feature) is not used. The current dependency configuration without alloc feature is appropriate for the stack-only usage pattern.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (7)
program-libs/compressed-account/src/instruction_data/invoke_cpi.rs (1)

15-22: Critical issue from previous review remains unfixed.

The feature gate all(feature = "std", feature = "anchor") on line 16 still breaks no_std+anchor builds. When anchor is enabled without std (typical for BPF programs), the struct derives neither Anchor nor Borsh traits, causing compilation failure.

This is especially problematic because lines 39-42 in the data() method assume feature = "anchor" enables Anchor traits, but the derives won't apply without std.

The fix from the previous review is still required: remove the std requirement from the Anchor derive gate.

program-libs/macros/src/lib.rs (1)

88-88: Fix: "at runtime" should be "at compile time".

The macro performs PDA derivation during macro expansion (compile time), not at runtime. This was previously flagged but remains unaddressed.

Apply this diff:

-/// Derives a complete Light Protocol CPI configuration at runtime
+/// Derives a complete Light Protocol CPI configuration at compile time
program-libs/hasher/tests/hash_to_field_size.rs (4)

1-11: Add missing ark_bn254 import and review unnecessary import.

Line 14 references ark_bn254::Fr::MODULUS, but ark_bn254 is not explicitly imported. Additionally, ark_ff::PrimeField (line 1) is imported but not directly used in this test file—it's only used within is_smaller_than_bn254_field_size_be.

Apply this diff:

-use ark_ff::PrimeField;
+use ark_bn254;
 use borsh::{BorshDeserialize, BorshSerialize};

12-25: Add required feature gate to prevent compilation failure.

This test uses ark_bn254::Fr::MODULUS and calls is_smaller_than_bn254_field_size_be, which are only available when the poseidon feature is enabled and not targeting Solana. Without the proper feature gate, this test will fail to compile on incompatible targets.

Apply this diff:

+#[cfg(all(not(target_os = "solana"), feature = "poseidon"))]
 #[test]
 fn test_is_smaller_than_bn254_field_size_be() {

42-60: Add required feature gate for test compatibility.

This test calls is_smaller_than_bn254_field_size_be (lines 49, 57), which is only available under cfg(all(not(target_os = "solana"), feature = "poseidon")). The test also uses solana_pubkey::Pubkey, which should be available on non-Solana targets for testing. Gate the test to match the function's availability.

Apply this diff:

+#[cfg(all(not(target_os = "solana"), feature = "poseidon"))]
 #[test]
 fn test_hash_to_bn254_field_size_be() {

Based on learnings: gate SDK-specific implementations appropriately as done in program-libs/account-checks.


62-82: Add required feature gate for test compatibility.

Same issue as test_hash_to_bn254_field_size_be: this test calls is_smaller_than_bn254_field_size_be (lines 70, 79), which requires cfg(all(not(target_os = "solana"), feature = "poseidon")). Gate the test appropriately.

Apply this diff:

+#[cfg(all(not(target_os = "solana"), feature = "poseidon"))]
 #[test]
 fn test_hashv_to_bn254_field_size_be() {
program-libs/hasher/tests/data_hasher.rs (1)

62-139: Sizes 5–12 are never asserted; cover all sizes without duplication.

This repeats an earlier warning; the current code silently skips 5–12. Replace the match with a const‑generic helper + macro to test 1..=12 uniformly.

 #[test]
 fn test_data_hasher_array_multiple_sizes() {
-    // Test arrays of each implemented size
-    for size in 1..=12 {
-        let mut array = Vec::with_capacity(size);
-        for i in 0..size {
-            array.push(TestHashable::new(i as u8));
-        }
-
-        // Convert the Vec to an array of the appropriate size
-        let array_slice = array.as_slice();
-
-        // Create expected inputs (hashes of each TestHashable)
-        let mut expected_inputs = Vec::with_capacity(size);
-        for i in 0..size {
-            let mut hash = [0u8; 32];
-            hash[31] = i as u8;
-            expected_inputs.push(hash);
-        }
-
-        // Dynamically test each array size
-        match size {
-            1 => {
-                let arr: [TestHashable; 1] = [array_slice[0].clone()];
-                let hash_result = arr.hash::<Poseidon>().unwrap();
-
-                let expected_slices: Vec<&[u8]> =
-                    expected_inputs.iter().map(|h| h.as_slice()).collect();
-                let expected_hash = Poseidon::hashv(&expected_slices).unwrap();
-
-                assert_eq!(hash_result, expected_hash);
-            }
-            2 => {
-                let arr: [TestHashable; 2] = [array_slice[0].clone(), array_slice[1].clone()];
-                let hash_result = arr.hash::<Poseidon>().unwrap();
-
-                let expected_slices: Vec<&[u8]> =
-                    expected_inputs.iter().map(|h| h.as_slice()).collect();
-                let expected_hash = Poseidon::hashv(&expected_slices).unwrap();
-
-                assert_eq!(hash_result, expected_hash);
-            }
-            3 => {
-                let arr: [TestHashable; 3] = [
-                    array_slice[0].clone(),
-                    array_slice[1].clone(),
-                    array_slice[2].clone(),
-                ];
-                let hash_result = arr.hash::<Poseidon>().unwrap();
-
-                let expected_slices: Vec<&[u8]> =
-                    expected_inputs.iter().map(|h| h.as_slice()).collect();
-                let expected_hash = Poseidon::hashv(&expected_slices).unwrap();
-
-                assert_eq!(hash_result, expected_hash);
-            }
-            // We test one more size (4) to confirm the pattern works
-            4 => {
-                let arr: [TestHashable; 4] = [
-                    array_slice[0].clone(),
-                    array_slice[1].clone(),
-                    array_slice[2].clone(),
-                    array_slice[3].clone(),
-                ];
-                let hash_result = arr.hash::<Poseidon>().unwrap();
-
-                let expected_slices: Vec<&[u8]> =
-                    expected_inputs.iter().map(|h| h.as_slice()).collect();
-                let expected_hash = Poseidon::hashv(&expected_slices).unwrap();
-
-                assert_eq!(hash_result, expected_hash);
-            }
-            _ => {
-                // For sizes 5-12, we've verified the pattern with tests for sizes 1-4
-                // We could add more tests here if needed
-            }
-        }
-    }
+    // Helper that checks an exact array length using const generics.
+    fn check<const N: usize>() {
+        let arr: [TestHashable; N] = core::array::from_fn(|i| TestHashable::new(i as u8));
+        let hash_result = arr.hash::<Poseidon>().unwrap();
+        let expected_inputs: Vec<[u8; 32]> = (0..N)
+            .map(|i| {
+                let mut h = [0u8; 32];
+                h[31] = i as u8;
+                h
+            })
+            .collect();
+        let expected_slices: Vec<&[u8]> = expected_inputs.iter().map(|h| h.as_slice()).collect();
+        let expected_hash = Poseidon::hashv(&expected_slices).unwrap();
+        assert_eq!(hash_result, expected_hash);
+    }
+
+    macro_rules! for_sizes {
+        ($($n:expr),* $(,)?) => { $(check::<$n>();)* };
+    }
+    for_sizes!(1,2,3,4,5,6,7,8,9,10,11,12);
 }
#!/bin/bash
set -euo pipefail

echo "Checking MSRV / toolchain (array::from_fn requires Rust 1.63+):"
rg -nP '^\s*rust-version\s*=\s*".+?"' -S -g 'Cargo.toml' -C1 || true
fd -a 'rust-toolchain*' | while read -r f; do echo "== $f =="; cat "$f"; echo; done

echo
echo "Verify dev-deps enable poseidon (and std if required) for hasher tests:"
if [ -f program-libs/hasher/Cargo.toml ]; then
  echo "== program-libs/hasher/Cargo.toml =="
  rg -n -C2 '^\[dev-dependencies\]|\[dev-dependencies\..*\]|light-?hasher|features\s*=' program-libs/hasher/Cargo.toml || true
else
  echo "program-libs/hasher/Cargo.toml not found; scanning workspace for hasher Cargo.toml..."
  fd -a Cargo.toml | while read -r f; do
    if rg -q '^\s*name\s*=\s*"light-?hasher"' "$f"; then
      echo "== $f =="; rg -n -C2 '^\[dev-dependencies\]|\[dev-dependencies\..*\]|light-?hasher|features\s*=' "$f"
    fi
  done
fi
🧹 Nitpick comments (2)
program-libs/hasher/tests/data_hasher.rs (2)

31-37: Use hashv consistently (even for 1 element).

Align the single‑element expectation with the multi‑element path to avoid relying on an implementation detail where hashv([x]) == hash(x) may diverge for other hashers.

-    let expected_hash = Poseidon::hash(&expected_input).unwrap();
+    let expected_hash = Poseidon::hashv(&[&expected_input]).unwrap();

46-55: Remove variable shadowing; initialize directly.

Reduces noise and improves clarity.

-    let hash1 = [0u8; 32];
-    let hash2 = [0u8; 32];
-
-    let mut hash1 = hash1;
-    hash1[31] = 1;
-
-    let mut hash2 = hash2;
-    hash2[31] = 2;
+    let mut hash1 = [0u8; 32];
+    hash1[31] = 1;
+    let mut hash2 = [0u8; 32];
+    hash2[31] = 2;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 06f00c1 and 18b5af2.

📒 Files selected for processing (5)
  • program-libs/compressed-account/src/instruction_data/invoke_cpi.rs (2 hunks)
  • program-libs/hasher/tests/data_hasher.rs (1 hunks)
  • program-libs/hasher/tests/hash_chain.rs (1 hunks)
  • program-libs/hasher/tests/hash_to_field_size.rs (1 hunks)
  • program-libs/macros/src/lib.rs (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • program-libs/hasher/tests/hash_chain.rs
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2025-10-11T21:59:25.222Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-10-11T21:59:25.222Z
Learning: Applies to program-libs/account-checks/src/account_info/{solana.rs,pinocchio.rs,test_account_info.rs} : Gate SDK-specific implementations with #[cfg(feature = "solana"|"pinocchio"|"test-only")]

Applied to files:

  • program-libs/hasher/tests/hash_to_field_size.rs
📚 Learning: 2025-10-15T03:46:26.767Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: programs/registry/CLAUDE.md:0-0
Timestamp: 2025-10-15T03:46:26.767Z
Learning: Applies to programs/registry/src/account_compression_cpi/**/*.rs : Pass the data Vec<u8> through unchanged from the wrapper to the target program CPI; the target program performs deserialization.

Applied to files:

  • program-libs/compressed-account/src/instruction_data/invoke_cpi.rs
📚 Learning: 2025-10-16T06:33:19.426Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-10-16T06:33:19.426Z
Learning: Applies to program-libs/compressible/src/config.rs : Ensure serialization compatibility across Anchor, Pinocchio, and Borsh for core account types used by dependent programs

Applied to files:

  • program-libs/compressed-account/src/instruction_data/invoke_cpi.rs
📚 Learning: 2025-10-15T03:46:26.767Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: programs/registry/CLAUDE.md:0-0
Timestamp: 2025-10-15T03:46:26.767Z
Learning: Applies to programs/registry/src/account_compression_cpi/*.rs : Context structs for wrapper instructions must include standard accounts: optional registered_forester_pda (mut), authority Signer, cpi_authority with seeds/bump for CPI_AUTHORITY_PDA_SEED, registered_program_pda, target program handle, log_wrapper, and mutable target_account.

Applied to files:

  • program-libs/compressed-account/src/instruction_data/invoke_cpi.rs
📚 Learning: 2025-10-16T06:33:19.426Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-10-16T06:33:19.426Z
Learning: Applies to program-libs/compressible/src/config.rs : Maintain CompressibleConfig account structure with Anchor/Borsh/Pod (Pinocchio/Pod) serialization and related state validation methods (validate_active, validate_not_inactive) in src/config.rs

Applied to files:

  • program-libs/compressed-account/src/instruction_data/invoke_cpi.rs
📚 Learning: 2025-10-15T03:46:26.767Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: programs/registry/CLAUDE.md:0-0
Timestamp: 2025-10-15T03:46:26.767Z
Learning: Applies to programs/registry/src/account_compression_cpi/**/*.rs : CPI processing functions must derive PDA signer seeds as [CPI_AUTHORITY_PDA_SEED, bump] and use CpiContext::new_with_signer with cpi_authority as the authority account and mapped target accounts.

Applied to files:

  • program-libs/macros/src/lib.rs
🧬 Code graph analysis (4)
program-libs/hasher/tests/hash_to_field_size.rs (2)
program-libs/hasher/src/bigint.rs (1)
  • bigint_to_be_bytes_array (23-36)
program-libs/hasher/src/hash_to_field_size.rs (5)
  • hash_to_field_size (12-12)
  • hash_to_field_size (20-39)
  • hash_to_bn254_field_size_be (99-101)
  • hashv_to_bn254_field_size_be (43-54)
  • is_smaller_than_bn254_field_size_be (104-109)
program-libs/hasher/tests/data_hasher.rs (2)
program-libs/hasher/src/data_hasher.rs (1)
  • hash (4-4)
program-libs/hasher/src/lib.rs (2)
  • hash (40-40)
  • hashv (41-41)
program-libs/compressed-account/src/instruction_data/invoke_cpi.rs (2)
program-libs/compressed-account/src/instruction_data/traits.rs (3)
  • data (26-33)
  • data (86-86)
  • data (107-107)
sdk-libs/sdk-pinocchio/src/cpi/v1/invoke.rs (1)
  • data (146-151)
program-libs/macros/src/lib.rs (1)
program-libs/macros/src/cpi_signer.rs (2)
  • derive_light_cpi_signer_pda (23-58)
  • derive_light_cpi_signer (83-127)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (21)
  • GitHub Check: Test program-libs-fast
  • GitHub Check: Test batched-merkle-tree-simulate
  • GitHub Check: Test program-libs-slow
  • GitHub Check: programs (system-cpi-test-v2-functional-account-infos, ["cargo-test-sbf -p system-cpi-v2-test -- ...
  • GitHub Check: programs (compressed-token-batched-tree, ["cargo-test-sbf -p compressed-token-test -- test_transf...
  • GitHub Check: programs (system-cpi-test, ["cargo-test-sbf -p system-cpi-test", "cargo test -p light-system-prog...
  • GitHub Check: programs (system-cpi-test-v2-functional-read-only, ["cargo-test-sbf -p system-cpi-v2-test -- func...
  • GitHub Check: programs (compressed-token-and-e2e, ["cargo-test-sbf -p compressed-token-test --test v1", "cargo-...
  • GitHub Check: programs (account-compression-and-registry, ["cargo-test-sbf -p account-compression-test", "cargo...
  • GitHub Check: programs (light-system-program-address, ["cargo-test-sbf -p system-test -- test_with_address", "c...
  • GitHub Check: programs (light-system-program-compression, ["cargo-test-sbf -p system-test -- test_with_compress...
  • GitHub Check: system-programs (sdk-anchor-test-program, ["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -...
  • GitHub Check: system-programs (sdk-libs, light-sdk-macros light-sdk light-program-test light-client light-compr...
  • GitHub Check: cli-v2
  • GitHub Check: system-programs (sdk-token-test-program, ["cargo-test-sbf -p sdk-token-test"])
  • GitHub Check: system-programs (sdk-test-program, ["cargo-test-sbf -p sdk-native-test", "cargo-test-sbf -p sdk-v...
  • GitHub Check: Forester e2e test
  • GitHub Check: cli-v1
  • GitHub Check: lint
  • GitHub Check: stateless-js-v1
  • GitHub Check: stateless-js-v2
🔇 Additional comments (3)
program-libs/compressed-account/src/instruction_data/invoke_cpi.rs (1)

37-52: Implementation is sound once derive issue is resolved.

The data() method implementation correctly:

  • Gates allocation behind the alloc feature for no_std support
  • Uses conditional trait imports that align with the intended feature flags
  • Adds a 4-byte length prefix (line 49) beyond the standard discriminator, which is appropriate for this instruction format

The trait aliasing pattern (lines 39-42) is unusual but works because both AnchorSerialize and BorshSerialize provide try_to_vec() with identical signatures.

However, this implementation will only compile correctly once the derive feature gate issue (lines 15-22) is fixed.

program-libs/macros/src/lib.rs (1)

61-66: LGTM: Clean no-op derive implementation.

The Noop derive macro is correctly implemented as a placeholder for conditional serialization. This pattern is useful for feature-gated derives.

program-libs/hasher/tests/hash_to_field_size.rs (1)

27-40: Now let me query for the actual Cargo.toml and test file structure in the codebase:

Based on my investigation of the code and Rust feature gate mechanics:

The test will fail to compile unless the "alloc" feature is enabled.

The HashToFieldSize trait implementation at line 16 of hash_to_field_size.rs is gated with #[cfg(feature = "alloc")], meaning the predicate is true if the option is set, and false if it is unset. Since the test at lines 27-40 calls test_struct.hash_to_field_size(), which depends on this feature-gated implementation, the test code will not compile without that feature enabled.

By default, tests, docs, and other tooling such as Clippy will only run with the default set of features. If "alloc" is not in the crate's default features list, this test will be silently excluded from compilation when running cargo test without explicit feature flags.

Verify whether "alloc" is included in the default features in program-libs/hasher/Cargo.toml. If it is not, the test should either be gated with #[cfg(feature = "alloc")] or the feature should be added to defaults.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
program-libs/compressed-account/src/compressed_account.rs (1)

9-16: Feature gate condition is correct but contains a redundant clause.

The anchor feature definition explicitly includes std (anchor = ["anchor-lang", "std"]), making all(feature = "std", feature = "anchor") redundant. The condition can be simplified to just feature = "anchor" without changing behavior.

The serialization strategy is sound: Anchor derives when anchor is enabled, Borsh derives otherwise. There is no valid no_std + anchor scenario per the feature configuration.

Recommendation: Simplify the condition to feature = "anchor" across all occurrences (lines 9–16, 26–33, 53–60, 88–95, 133–140, 149–156, 164–171, 182–189, 199–206, 215–222, 233–240) for clarity, though the current code functions correctly.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 18b5af2 and 596d168.

📒 Files selected for processing (3)
  • .github/workflows/rust.yml (0 hunks)
  • program-libs/compressed-account/src/compressed_account.rs (11 hunks)
  • program-libs/macros/src/lib.rs (2 hunks)
💤 Files with no reviewable changes (1)
  • .github/workflows/rust.yml
🚧 Files skipped from review as they are similar to previous changes (1)
  • program-libs/macros/src/lib.rs
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-10-16T06:33:19.426Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-10-16T06:33:19.426Z
Learning: Applies to program-libs/compressible/src/config.rs : Ensure serialization compatibility across Anchor, Pinocchio, and Borsh for core account types used by dependent programs

Applied to files:

  • program-libs/compressed-account/src/compressed_account.rs
📚 Learning: 2025-10-16T06:33:19.426Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-10-16T06:33:19.426Z
Learning: Applies to program-libs/compressible/src/config.rs : Maintain CompressibleConfig account structure with Anchor/Borsh/Pod (Pinocchio/Pod) serialization and related state validation methods (validate_active, validate_not_inactive) in src/config.rs

Applied to files:

  • program-libs/compressed-account/src/compressed_account.rs
📚 Learning: 2025-10-11T21:59:25.222Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-10-11T21:59:25.222Z
Learning: Applies to program-libs/account-checks/src/packed_accounts.rs : For dynamic account sets, use PackedAccounts for index-based access with bounds checks instead of manual indexing

Applied to files:

  • program-libs/compressed-account/src/compressed_account.rs
📚 Learning: 2025-10-11T21:59:25.222Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-10-11T21:59:25.222Z
Learning: Applies to program-libs/account-checks/src/**/*.rs : Validate account type with 8-byte discriminators using check_discriminator before deserialization

Applied to files:

  • program-libs/compressed-account/src/compressed_account.rs
🧬 Code graph analysis (1)
program-libs/compressed-account/src/compressed_account.rs (1)
program-libs/hasher/src/hash_to_field_size.rs (3)
  • hash_to_field_size (12-12)
  • hash_to_field_size (20-39)
  • hash_to_bn254_field_size_be (99-101)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (21)
  • GitHub Check: cli-v2
  • GitHub Check: system-programs (sdk-libs, light-sdk-macros light-sdk light-program-test light-client light-compr...
  • GitHub Check: system-programs (sdk-anchor-test-program, ["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -...
  • GitHub Check: system-programs (sdk-test-program, ["cargo-test-sbf -p sdk-native-test", "cargo-test-sbf -p sdk-v...
  • GitHub Check: cli-v1
  • GitHub Check: system-programs (sdk-token-test-program, ["cargo-test-sbf -p sdk-token-test"])
  • GitHub Check: stateless-js-v2
  • GitHub Check: Test program-libs-fast
  • GitHub Check: Test program-libs-slow
  • GitHub Check: Test batched-merkle-tree-simulate
  • GitHub Check: Forester e2e test
  • GitHub Check: lint
  • GitHub Check: programs (system-cpi-test, ["cargo-test-sbf -p system-cpi-test", "cargo test -p light-system-prog...
  • GitHub Check: programs (compressed-token-batched-tree, ["cargo-test-sbf -p compressed-token-test -- test_transf...
  • GitHub Check: programs (system-cpi-test-v2-functional-account-infos, ["cargo-test-sbf -p system-cpi-v2-test -- ...
  • GitHub Check: programs (compressed-token-and-e2e, ["cargo-test-sbf -p compressed-token-test --test v1", "cargo-...
  • GitHub Check: programs (system-cpi-test-v2-functional-read-only, ["cargo-test-sbf -p system-cpi-v2-test -- func...
  • GitHub Check: programs (account-compression-and-registry, ["cargo-test-sbf -p account-compression-test", "cargo...
  • GitHub Check: stateless-js-v1
  • GitHub Check: programs (light-system-program-compression, ["cargo-test-sbf -p system-test -- test_with_compress...
  • GitHub Check: programs (light-system-program-address, ["cargo-test-sbf -p system-test -- test_with_address", "c...
🔇 Additional comments (5)
program-libs/compressed-account/src/compressed_account.rs (5)

330-330: LGTM: Scoped imports improve clarity.

The explicit function-scoped imports of hash_to_bn254_field_size_be are appropriate and improve code clarity by making the dependency explicit at the point of use.

Also applies to: 370-370, 383-383


133-146: LGTM: Read-only account variants properly structured.

The new ReadOnlyCompressedAccount and PackedReadOnlyCompressedAccount types are well-defined:

  • Feature-gated derives are consistent with other types
  • PackedReadOnlyCompressedAccount correctly uses #[repr(C)] and ZeroCopyMut for zero-copy semantics
  • Field layouts are appropriate for their use cases

Also applies to: 148-162


380-785: LGTM: Test coverage is comprehensive.

The test module properly:

  • Imports necessary serialization traits
  • Tests hash computation with various field combinations
  • Verifies equivalency between legacy and new implementations
  • Includes reference values for regression testing

5-5: No action required—Vec is properly configured for no_std.

The crate's prelude correctly handles Vec with conditional compilation: it re-exports from alloc when std is disabled (line 7-8 of lib.rs) and from std when available (line 11-12). The import from the crate root in line 5 of compressed_account.rs follows this established pattern and is correct.


257-299: Based on my verification, the review comment contains inaccurate concerns. Let me check the workspace Cargo.toml to confirm the tinyvec configuration:

Code changes are correct; no configuration issues or refactoring needed.

The ArrayVec usage with capacity 7 is appropriate for no_std support. tinyvec is no_std by default, and ArrayVec has no dependencies other than tinyvec and core, so the current workspace dependency configuration is correct.

The fixed capacity of 7 properly matches the maximum possible elements: 3 mandatory pushes (owner_hashed, leaf_index, merkle_tree) plus up to 4 conditional pushes (lamports, address, discriminator, data_hash). When ArrayVec capacity is exceeded, it causes a panic, but the code logic ensures this never occurs—all conditional pushes are properly guarded by their respective conditions (lines 280-296).

@ananas-block ananas-block force-pushed the jorrit/refactor-compressed-account-nostd branch from 596d168 to 6551559 Compare October 19, 2025 08:00
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
program-libs/compressed-account/src/compressed_account.rs (2)

112-131: into_read_only may leave prove_by_index stale when root_index is Some.

You only set prove_by_index = true when root_index is None; when root_index is Some, the existing value is preserved, which could carry over an unintended true from prior state.

Make the intent explicit:

-        let merkle_context = if root_index.is_none() {
-            let mut merkle_context = self.merkle_context;
-            merkle_context.prove_by_index = true;
-            merkle_context
-        } else {
-            self.merkle_context
-        };
+        let mut merkle_context = self.merkle_context;
+        merkle_context.prove_by_index = root_index.is_none();

This prevents stale values and documents the invariant.


248-299: Public hash_with_hashed_values accepts unsized inputs; enforce canonical lengths.

As a public API, taking address: Option<&[u8]>, data: Option<(&[u8], &[u8])> permits non-32/8/32 byte inputs, producing non-canonical hashes if misused downstream. Given this is consensus/verification-critical, prefer typed arrays (and/or explicit validation).

Options:

  • API hardening (breaking change):
-pub fn hash_with_hashed_values(
-    lamports: &u64,
-    address: Option<&[u8]>,
-    data: Option<(&[u8], &[u8])>,
+pub fn hash_with_hashed_values(
+    lamports: &u64,
+    address: Option<&[u8; 32]>,
+    data: Option<(&[u8; 8], &[u8; 32])>,
  • Backward-compatible guardrails: add debug assertions now and follow up with a typed API.
     if let Some(address) = address {
+        debug_assert_eq!(address.len(), 32, "address must be 32 bytes");
         vec.push(address);
     }
@@
     if let Some((discriminator, data_hash)) = data {
+        debug_assert_eq!(discriminator.len(), 8, "discriminator must be 8 bytes");
+        debug_assert_eq!(data_hash.len(), 32, "data_hash must be 32 bytes");

Additionally, to avoid future panics if inputs expand, prefer fallible pushes:

-    let mut vec: tinyvec::ArrayVec<[&[u8]; 7]> = tinyvec::ArrayVec::new();
+    let mut vec: tinyvec::ArrayVec<[&[u8]; 7]> = tinyvec::ArrayVec::new();
+    // Invariant: at most 7 elements; use try_push to avoid panics if modified later.

and replace push with try_push().map_err(|_| CompressedAccountError::InternalCapacityExceeded)? (or similar error variant).

These tighten invariants around canonical hashing. Based on learnings.

♻️ Duplicate comments (1)
program-libs/macros/src/lib.rs (1)

88-88: Fix: "at runtime" should be "at compile time".

The macro performs PDA derivation during macro expansion (compile time), not at runtime. The generated code contains precomputed byte arrays, as evidenced by the implementation in cpi_signer.rs and the example showing const initialization.

Apply this diff:

-/// Derives a complete Light Protocol CPI configuration at runtime
+/// Derives a complete Light Protocol CPI configuration at compile time
🧹 Nitpick comments (3)
program-libs/compressed-account/src/compressed_account.rs (3)

9-17: Feature-gating gap: Anchor derives disabled when anchor=true and std=false.

Current cfg_attr applies Anchor derives only with all(feature="std", feature="anchor"), and Borsh derives only with not(feature="anchor"). When anchor=true but std=false, neither path applies, dropping (de)serialization derives and potentially breaking no_std anchor builds or consumers.

Two options:

  • If Anchor is intentionally disallowed without std: enforce that explicitly once per crate with a compile_error! to avoid silent misconfigurations.
  • If Anchor should work in no_std: drop the std requirement or add an alternative cfg path.

Suggested guard (crate-level or once in this module):

#[cfg(all(feature = "anchor", not(feature = "std")))]
compile_error!("`feature=\"anchor\"` requires `feature=\"std\"` in this crate. Enable `std` or disable `anchor`.");

Or relax the gating:

#[cfg_attr(feature = "anchor", derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize))]
#[cfg_attr(not(feature = "anchor"), derive(borsh::BorshDeserialize, borsh::BorshSerialize))]

Please confirm the intended support matrix and apply consistently across all structs. Based on learnings.

Also applies to: 26-34, 53-61, 88-96, 133-141, 149-157, 164-172, 182-191, 199-207, 215-223, 233-241


324-339: Avoid pattern-binding on references in API; pass references directly.

&merkle_tree_pubkey: &Pubkey copies the value unnecessarily and is unconventional. Prefer a plain reference for clarity and fewer moves:

-    pub fn hash(
-        &self,
-        &merkle_tree_pubkey: &Pubkey,
+    pub fn hash(
+        &self,
+        merkle_tree_pubkey: &Pubkey,

Use merkle_tree_pubkey.as_ref() as before. Same for leaf_index if desired.


364-377: Same param-pattern simplification for ZCompressedAccount::hash.

Mirror the simplification:

-    pub fn hash(
-        &self,
-        &merkle_tree_pubkey: &[u8; 32],
+    pub fn hash(
+        &self,
+        merkle_tree_pubkey: &[u8; 32],

Then use merkle_tree_pubkey.as_slice().

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 596d168 and 6551559.

📒 Files selected for processing (4)
  • .github/workflows/rust.yml (0 hunks)
  • program-libs/batched-merkle-tree/Cargo.toml (1 hunks)
  • program-libs/compressed-account/src/compressed_account.rs (11 hunks)
  • program-libs/macros/src/lib.rs (2 hunks)
💤 Files with no reviewable changes (1)
  • .github/workflows/rust.yml
🚧 Files skipped from review as they are similar to previous changes (1)
  • program-libs/batched-merkle-tree/Cargo.toml
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-10-15T03:46:26.767Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: programs/registry/CLAUDE.md:0-0
Timestamp: 2025-10-15T03:46:26.767Z
Learning: Applies to programs/registry/src/account_compression_cpi/**/*.rs : CPI processing functions must derive PDA signer seeds as [CPI_AUTHORITY_PDA_SEED, bump] and use CpiContext::new_with_signer with cpi_authority as the authority account and mapped target accounts.

Applied to files:

  • program-libs/macros/src/lib.rs
📚 Learning: 2025-10-16T06:33:19.426Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-10-16T06:33:19.426Z
Learning: Applies to program-libs/compressible/src/config.rs : Ensure serialization compatibility across Anchor, Pinocchio, and Borsh for core account types used by dependent programs

Applied to files:

  • program-libs/compressed-account/src/compressed_account.rs
📚 Learning: 2025-10-16T06:33:19.426Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-10-16T06:33:19.426Z
Learning: Applies to program-libs/compressible/src/config.rs : Maintain CompressibleConfig account structure with Anchor/Borsh/Pod (Pinocchio/Pod) serialization and related state validation methods (validate_active, validate_not_inactive) in src/config.rs

Applied to files:

  • program-libs/compressed-account/src/compressed_account.rs
📚 Learning: 2025-10-11T21:59:25.222Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-10-11T21:59:25.222Z
Learning: Applies to program-libs/account-checks/src/packed_accounts.rs : For dynamic account sets, use PackedAccounts for index-based access with bounds checks instead of manual indexing

Applied to files:

  • program-libs/compressed-account/src/compressed_account.rs
📚 Learning: 2025-10-11T21:59:25.222Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-10-11T21:59:25.222Z
Learning: Applies to program-libs/account-checks/src/**/*.rs : Validate account type with 8-byte discriminators using check_discriminator before deserialization

Applied to files:

  • program-libs/compressed-account/src/compressed_account.rs
🧬 Code graph analysis (2)
program-libs/macros/src/lib.rs (4)
sdk-libs/sdk-types/src/cpi_accounts/config.rs (2)
  • cpi_signer (30-32)
  • new (12-19)
sdk-libs/sdk-types/src/cpi_accounts/v2.rs (1)
  • new (71-77)
sdk-libs/sdk-types/src/cpi_accounts/v1.rs (1)
  • new (53-59)
program-libs/macros/src/cpi_signer.rs (2)
  • derive_light_cpi_signer_pda (23-58)
  • derive_light_cpi_signer (83-127)
program-libs/compressed-account/src/compressed_account.rs (1)
program-libs/hasher/src/hash_to_field_size.rs (3)
  • hash_to_field_size (12-12)
  • hash_to_field_size (20-39)
  • hash_to_bn254_field_size_be (99-101)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (21)
  • GitHub Check: Forester e2e test
  • GitHub Check: programs (system-cpi-test-v2-functional-read-only, ["cargo-test-sbf -p system-cpi-v2-test -- func...
  • GitHub Check: programs (system-cpi-test, ["cargo-test-sbf -p system-cpi-test", "cargo test -p light-system-prog...
  • GitHub Check: programs (system-cpi-test-v2-functional-account-infos, ["cargo-test-sbf -p system-cpi-v2-test -- ...
  • GitHub Check: programs (account-compression-and-registry, ["cargo-test-sbf -p account-compression-test", "cargo...
  • GitHub Check: programs (compressed-token-and-e2e, ["cargo-test-sbf -p compressed-token-test --test v1", "cargo-...
  • GitHub Check: programs (compressed-token-batched-tree, ["cargo-test-sbf -p compressed-token-test -- test_transf...
  • GitHub Check: programs (light-system-program-address, ["cargo-test-sbf -p system-test -- test_with_address", "c...
  • GitHub Check: programs (light-system-program-compression, ["cargo-test-sbf -p system-test -- test_with_compress...
  • GitHub Check: Test program-libs-fast
  • GitHub Check: Test program-libs-slow
  • GitHub Check: Test batched-merkle-tree-simulate
  • GitHub Check: stateless-js-v1
  • GitHub Check: cli-v1
  • GitHub Check: system-programs (sdk-libs, light-sdk-macros light-sdk light-program-test light-client light-compr...
  • GitHub Check: system-programs (sdk-token-test-program, ["cargo-test-sbf -p sdk-token-test"])
  • GitHub Check: system-programs (sdk-anchor-test-program, ["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -...
  • GitHub Check: system-programs (sdk-test-program, ["cargo-test-sbf -p sdk-native-test", "cargo-test-sbf -p sdk-v...
  • GitHub Check: cli-v2
  • GitHub Check: lint
  • GitHub Check: stateless-js-v2
🔇 Additional comments (5)
program-libs/macros/src/lib.rs (3)

7-7: LGTM: Internal module for CPI signer macro logic.

The module declaration appropriately encapsulates the compile-time PDA derivation logic used by the new macros.


61-66: LGTM: Clean pattern for conditional serialization derives.

The no-op derive macro enables feature-gated compilation without syntax errors when serialization derives are not needed. This aligns well with the PR's no_std refactoring objectives.


68-86: LGTM: Compile-time PDA derivation macro.

The macro correctly documents that PDA computation happens at compile time, and the delegation to the internal module is appropriate. The documentation and example clearly describe the return type and usage.

program-libs/compressed-account/src/compressed_account.rs (2)

133-147: New read-only account types look consistent with mutable/packed counterparts.

Layouts and derives align; ZeroCopyMut on packed variant with repr(C) is correct. LGTM.

Also applies to: 148-162


380-784: Strong test coverage for hashing variants and legacy parity.

The tests clearly validate domain separation, endianness toggle (is_batched), ZC path, and legacy equivalence. Thanks for the thoroughness. LGTM.

@ananas-block ananas-block force-pushed the jorrit/refactor-compressed-account-nostd branch from 6551559 to 00f3ced Compare October 19, 2025 08:20
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6551559 and 00f3ced.

📒 Files selected for processing (8)
  • .github/workflows/rust.yml (0 hunks)
  • program-libs/batched-merkle-tree/Cargo.toml (1 hunks)
  • program-libs/compressed-account/src/compressed_account.rs (11 hunks)
  • program-libs/concurrent-merkle-tree/Cargo.toml (1 hunks)
  • program-libs/hash-set/Cargo.toml (1 hunks)
  • program-libs/indexed-array/Cargo.toml (1 hunks)
  • program-libs/indexed-merkle-tree/Cargo.toml (1 hunks)
  • program-libs/macros/src/lib.rs (2 hunks)
💤 Files with no reviewable changes (1)
  • .github/workflows/rust.yml
🚧 Files skipped from review as they are similar to previous changes (1)
  • program-libs/batched-merkle-tree/Cargo.toml
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-10-16T06:33:19.426Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-10-16T06:33:19.426Z
Learning: Applies to program-libs/compressible/src/config.rs : Ensure serialization compatibility across Anchor, Pinocchio, and Borsh for core account types used by dependent programs

Applied to files:

  • program-libs/compressed-account/src/compressed_account.rs
📚 Learning: 2025-10-16T06:33:19.426Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-10-16T06:33:19.426Z
Learning: Applies to program-libs/compressible/src/config.rs : Maintain CompressibleConfig account structure with Anchor/Borsh/Pod (Pinocchio/Pod) serialization and related state validation methods (validate_active, validate_not_inactive) in src/config.rs

Applied to files:

  • program-libs/compressed-account/src/compressed_account.rs
📚 Learning: 2025-10-11T21:59:25.222Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-10-11T21:59:25.222Z
Learning: Applies to program-libs/account-checks/src/packed_accounts.rs : For dynamic account sets, use PackedAccounts for index-based access with bounds checks instead of manual indexing

Applied to files:

  • program-libs/compressed-account/src/compressed_account.rs
📚 Learning: 2025-10-11T21:59:25.222Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-10-11T21:59:25.222Z
Learning: Applies to program-libs/account-checks/src/**/*.rs : Validate account type with 8-byte discriminators using check_discriminator before deserialization

Applied to files:

  • program-libs/compressed-account/src/compressed_account.rs
📚 Learning: 2025-10-15T03:46:26.767Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: programs/registry/CLAUDE.md:0-0
Timestamp: 2025-10-15T03:46:26.767Z
Learning: Applies to programs/registry/src/account_compression_cpi/**/*.rs : CPI processing functions must derive PDA signer seeds as [CPI_AUTHORITY_PDA_SEED, bump] and use CpiContext::new_with_signer with cpi_authority as the authority account and mapped target accounts.

Applied to files:

  • program-libs/macros/src/lib.rs
🧬 Code graph analysis (2)
program-libs/compressed-account/src/compressed_account.rs (1)
program-libs/hasher/src/hash_to_field_size.rs (3)
  • hash_to_field_size (12-12)
  • hash_to_field_size (20-39)
  • hash_to_bn254_field_size_be (99-101)
program-libs/macros/src/lib.rs (1)
program-libs/macros/src/cpi_signer.rs (2)
  • derive_light_cpi_signer_pda (23-58)
  • derive_light_cpi_signer (83-127)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (22)
  • GitHub Check: stateless-js-v2
  • GitHub Check: lint
  • GitHub Check: Test program-libs-slow
  • GitHub Check: Test program-libs-fast
  • GitHub Check: stateless-js-v1
  • GitHub Check: Test batched-merkle-tree-simulate
  • GitHub Check: cli-v2
  • GitHub Check: Forester e2e test
  • GitHub Check: programs (system-cpi-test, ["cargo-test-sbf -p system-cpi-test", "cargo test -p light-system-prog...
  • GitHub Check: programs (compressed-token-and-e2e, ["cargo-test-sbf -p compressed-token-test --test v1", "cargo-...
  • GitHub Check: programs (compressed-token-batched-tree, ["cargo-test-sbf -p compressed-token-test -- test_transf...
  • GitHub Check: programs (system-cpi-test-v2-functional-read-only, ["cargo-test-sbf -p system-cpi-v2-test -- func...
  • GitHub Check: system-programs (sdk-test-program, ["cargo-test-sbf -p sdk-native-test", "cargo-test-sbf -p sdk-v...
  • GitHub Check: system-programs (sdk-libs, light-sdk-macros light-sdk light-program-test light-client light-compr...
  • GitHub Check: system-programs (sdk-token-test-program, ["cargo-test-sbf -p sdk-token-test"])
  • GitHub Check: system-programs (sdk-anchor-test-program, ["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -...
  • GitHub Check: programs (system-cpi-test-v2-functional-account-infos, ["cargo-test-sbf -p system-cpi-v2-test -- ...
  • GitHub Check: programs (account-compression-and-registry, ["cargo-test-sbf -p account-compression-test", "cargo...
  • GitHub Check: programs (light-system-program-compression, ["cargo-test-sbf -p system-test -- test_with_compress...
  • GitHub Check: programs (light-system-program-address, ["cargo-test-sbf -p system-test -- test_with_address", "c...
  • GitHub Check: cli-v1
  • GitHub Check: Analyze (go)
🔇 Additional comments (10)
program-libs/indexed-merkle-tree/Cargo.toml (1)

37-37: Expand dev-dependency hashing algorithms for comprehensive testing.

Adding light-hasher as a dev-dependency with extended features ["keccak", "sha256", "poseidon"] is a solid pattern. This allows tests and examples to validate multiple hashing algorithms while the production code remains minimal (poseidon-only on line 25).

Both dependencies correctly use { workspace = true }, ensuring version consistency across the workspace.

Please verify that tests/examples in this crate actually exercise the additional keccak and sha256 features. If they're unused, consider removing them to keep dev-dependencies lean.

program-libs/hash-set/Cargo.toml (1)

17-17: Verify that main dependency light-hasher (line 17) has sufficient baseline functionality without explicit features.

The dev-dependency now explicitly enables ["keccak", "sha256", "poseidon"] features (line 22), while the main dependency relies only on workspace defaults (line 17). For no_std/alloc refactors with feature-gating, this pattern is sound: the library compiles minimally, and tests gain access to specific algorithms.

However, ensure that the code path in the library itself does not require at least one of these hash algorithms at compile time or runtime when features are absent (e.g., as a fallback). If the library does need a baseline hasher, consider conditionally enabling one feature in the main dependency, or documenting the requirement clearly.

To verify this pattern is safe, confirm:

  1. Whether any code in program-libs/hash-set unconditionally calls functions from light-hasher that depend on these features.
  2. Whether light-hasher's workspace default (now disabled per the learnings) previously provided a default algorithm that is now required by tests only.

Consider running the library's doctests and unit tests without the dev-dependency to ensure no breakage occurs when building the library in isolation.

Also applies to: 22-22

program-libs/concurrent-merkle-tree/Cargo.toml (1)

28-28: Verified: all hash function features are valid.

The dev-dependency correctly expands the hash function features for comprehensive testing. All three features—keccak, sha256, and poseidon—are properly defined in light-hasher and available for use. This follows best practice: keeping the runtime dependency minimal (poseidon only) while enabling full test coverage with multiple hash algorithms in dev-dependencies.

program-libs/indexed-array/Cargo.toml (1)

16-16: Features align with workspace pattern; verify consistency rationale with maintainers.

The expanded dev-dependency features (keccak, sha256, poseidon) match the pattern used across related crates (indexed-merkle-tree, hash-set, concurrent-merkle-tree). However, indexed-array's test suite does not directly reference these hashers. Confirm whether this is:

  1. Preparatory for future feature-gated tests
  2. Required for transitive test dependencies
  3. Maintained for consistency across similar library crates

If this is intentional consistency, no action needed. If not, consider whether dev-dependencies should be trimmed to only required features.

program-libs/macros/src/lib.rs (2)

61-66: LGTM: Clean no-op derive placeholder.

The implementation correctly returns an empty TokenStream, and the documentation clearly explains its intended use as a serialization derive placeholder.


68-86: LGTM: Documentation and delegation are correct.

The macro correctly documents compile-time PDA derivation, and the example matches the implementation's tuple return type.

program-libs/compressed-account/src/compressed_account.rs (4)

257-299: LGTM: Excellent no_std optimization with ArrayVec.

The replacement of Vec with tinyvec::ArrayVec<[&[u8]; 7]> is well-suited for no_std environments. The capacity of 7 correctly accounts for all possible elements (3 mandatory + 4 optional), avoiding heap allocation in the typical case while maintaining the same hashing logic.


330-330: LGTM: Function-scoped imports improve clarity.

The explicit use statements at function scope make dependencies clear and are consistent with no_std best practices.

Also applies to: 370-370


382-383: LGTM: Test imports align with Borsh serialization path.

The change to borsh::BorshSerialize is consistent with tests using the non-Anchor feature configuration, properly exercising the Borsh serialization path.


9-16: ****

The feature flag concern is unfounded. The Cargo.toml explicitly defines anchor = ["anchor-lang", "std"], meaning the anchor feature automatically enables std. There is no "anchor without std" scenario in this codebase—the condition all(feature = "std", feature = "anchor") will always be true when anchor is enabled.

The current conditional derives pattern is correct:

  • When anchor is enabled: std is also enabled (by feature definition) → Anchor derives applied ✓
  • When anchor is disabled: Borsh derives applied ✓

No changes to lines 9-16 or related derives are needed.

Likely an incorrect or invalid review comment.

cpi_signer::derive_light_cpi_signer_pda(input)
}

/// Derives a complete Light Protocol CPI configuration at runtime
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix: "at runtime" should be "at compile time".

The macro performs PDA derivation during macro expansion (compile time), not at runtime. The implementation precomputes all byte arrays and the bump seed, as evidenced by the const usage in the example at line 100.

Apply this diff:

-/// Derives a complete Light Protocol CPI configuration at runtime
+/// Derives a complete Light Protocol CPI configuration at compile time
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Derives a complete Light Protocol CPI configuration at runtime
/// Derives a complete Light Protocol CPI configuration at compile time
🤖 Prompt for AI Agents
In program-libs/macros/src/lib.rs around line 88, the doc comment mistakenly
says "at runtime" but the macro actually performs PDA derivation during macro
expansion (compile time); update the comment text to "at compile time" (and
adjust any adjacent wording to reflect compile-time behavior) so the
documentation correctly states that derivation and precomputation occur during
macro expansion.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
Cargo.toml (1)

172-172: Duplicate: default-features = false compilation risk already flagged.

This change was previously reviewed and flagged as a critical issue. The concern is that workspace members using { workspace = true } will inherit default-features = false, potentially causing compilation failures if they don't explicitly enable required features.

See the previous review comment on lines 172-184 that identified 5 workspace members (program-libs/bloom-filter, program-libs/compressible, program-libs/hash-set, program-tests/merkle-tree, sdk-libs/sdk-types) depending on these crates without explicit feature enablement.

The fix requires either:

  1. Removing/clearing default = [...] in the affected crates' Cargo.toml files, OR
  2. Updating all workspace dependents to explicitly enable required features

Also applies to: 180-181, 184-184, 186-186

🧹 Nitpick comments (2)
sdk-libs/sdk-pinocchio/Cargo.toml (1)

27-27: Consider disabling default features for optional light-sdk.

The light-sdk dependency is marked as optional but doesn't specify default-features = false, unlike other light-* dependencies in this file (lines 19-23). This inconsistency could inadvertently enable std features when the light-account feature is active.

Apply this diff to align with the no_std pattern:

-light-sdk = { workspace = true, optional = true }
+light-sdk = { workspace = true, default-features = false, optional = true }
program-tests/pinocchio-nostd-test/src/lib.rs (1)

28-37: panic! in no_std TryFrom implementation.

Line 34 uses panic! for invalid discriminators, which can increase program size and may not be the most efficient error handling in a no_std Solana program. Pinocchio and Solana programs typically prefer returning errors.

Consider returning an error instead of panicking:

     fn try_from(value: u8) -> Result<Self, Self::Error> {
         match value {
             0 => Ok(InstructionType::TestBasic),
-            _ => panic!("Invalid instruction discriminator."),
+            _ => Err(LightSdkError::InvalidDiscriminator),
         }
     }

Note: This requires LightSdkError to have an InvalidDiscriminator variant. Verify this variant exists or add it.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 00f3ced and af5e300.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • .github/workflows/sdk-tests.yml (1 hunks)
  • Cargo.toml (4 hunks)
  • program-tests/pinocchio-nostd-test/Cargo.toml (1 hunks)
  • program-tests/pinocchio-nostd-test/build.rs (1 hunks)
  • program-tests/pinocchio-nostd-test/src/lib.rs (1 hunks)
  • program-tests/pinocchio-nostd-test/tests/basic.rs (1 hunks)
  • sdk-libs/sdk-pinocchio/Cargo.toml (1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-10-11T21:59:25.222Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-10-11T21:59:25.222Z
Learning: Applies to program-libs/account-checks/**/Cargo.toml : Define features solana, pinocchio, and test-only in Cargo.toml; default build should enable none

Applied to files:

  • program-tests/pinocchio-nostd-test/build.rs
  • program-tests/pinocchio-nostd-test/Cargo.toml
  • sdk-libs/sdk-pinocchio/Cargo.toml
  • program-tests/pinocchio-nostd-test/src/lib.rs
📚 Learning: 2025-10-11T21:59:25.222Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-10-11T21:59:25.222Z
Learning: Applies to program-libs/account-checks/src/account_info/{solana.rs,pinocchio.rs,test_account_info.rs} : Gate SDK-specific implementations with #[cfg(feature = "solana"|"pinocchio"|"test-only")]

Applied to files:

  • program-tests/pinocchio-nostd-test/Cargo.toml
  • sdk-libs/sdk-pinocchio/Cargo.toml
  • program-tests/pinocchio-nostd-test/src/lib.rs
📚 Learning: 2025-10-15T03:46:43.242Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: sdk-tests/sdk-token-test/CLAUDE.md:0-0
Timestamp: 2025-10-15T03:46:43.242Z
Learning: Run tests with: cargo test-sbf -p sdk-token-test --test <test-file-name>

Applied to files:

  • .github/workflows/sdk-tests.yml
📚 Learning: 2025-10-16T06:33:19.426Z
Learnt from: CR
PR: Lightprotocol/light-protocol#0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-10-16T06:33:19.426Z
Learning: Applies to program-libs/compressible/src/config.rs : Ensure serialization compatibility across Anchor, Pinocchio, and Borsh for core account types used by dependent programs

Applied to files:

  • Cargo.toml
🧬 Code graph analysis (1)
program-tests/pinocchio-nostd-test/tests/basic.rs (1)
program-tests/pinocchio-nostd-test/src/lib.rs (1)
  • get_program_id (71-73)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (21)
  • GitHub Check: programs (system-cpi-test, ["cargo-test-sbf -p system-cpi-test", "cargo test -p light-system-prog...
  • GitHub Check: programs (compressed-token-batched-tree, ["cargo-test-sbf -p compressed-token-test -- test_transf...
  • GitHub Check: system-programs (token test, ["cargo-test-sbf -p sdk-token-test"])
  • GitHub Check: system-programs (sdk-libs, light-sdk-macros light-sdk light-program-test light-client light-compr...
  • GitHub Check: programs (system-cpi-test-v2-functional-read-only, ["cargo-test-sbf -p system-cpi-v2-test -- func...
  • GitHub Check: system-programs (native, ["cargo-test-sbf -p sdk-native-test", "cargo-test-sbf -p sdk-v1-native-t...
  • GitHub Check: programs (system-cpi-test-v2-functional-account-infos, ["cargo-test-sbf -p system-cpi-v2-test -- ...
  • GitHub Check: system-programs (anchor & pinocchio, ["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -p sdk...
  • GitHub Check: programs (light-system-program-compression, ["cargo-test-sbf -p system-test -- test_with_compress...
  • GitHub Check: stateless-js-v2
  • GitHub Check: cli-v2
  • GitHub Check: programs (account-compression-and-registry, ["cargo-test-sbf -p account-compression-test", "cargo...
  • GitHub Check: Test program-libs-slow
  • GitHub Check: Test program-libs-fast
  • GitHub Check: programs (compressed-token-and-e2e, ["cargo-test-sbf -p compressed-token-test --test v1", "cargo-...
  • GitHub Check: programs (light-system-program-address, ["cargo-test-sbf -p system-test -- test_with_address", "c...
  • GitHub Check: Test batched-merkle-tree-simulate
  • GitHub Check: cli-v1
  • GitHub Check: stateless-js-v1
  • GitHub Check: lint
  • GitHub Check: Forester e2e test
🔇 Additional comments (15)
.github/workflows/sdk-tests.yml (2)

50-55: Matrix reorganization aligns well with PR objectives.

The consolidation of CI matrix entries—renaming generic labels and grouping anchor & pinocchio tests—is a clean reorganization. The addition of pinocchio-nostd-test (line 53) directly supports the no_std refactoring goal outlined in the PR.


51-51: Verify command format consistency: cargo test-sbf vs cargo-test-sbf.

Line 51 contains "cargo test-sbf -p client-test" (with a space), whereas other similar entries use cargo-test-sbf (hyphenated, e.g., line 53). Based on the learnings provided ("Run tests with: cargo test-sbf -p ..."), the correct form likely uses a hyphen. However, this line is not marked as changed in the diff, so it may be pre-existing. If this is indeed an error, the shell eval on line 90 will fail when executing this command.

Please verify that the command format in line 51 is correct and will execute successfully. If cargo test-sbf should be cargo-test-sbf, apply this fix:

-            sub-tests: '["cargo-test-sbf -p sdk-native-test", "cargo-test-sbf -p sdk-v1-native-test", "cargo test-sbf -p client-test"]'
+            sub-tests: '["cargo-test-sbf -p sdk-native-test", "cargo-test-sbf -p sdk-v1-native-test", "cargo-test-sbf -p client-test"]'
program-tests/pinocchio-nostd-test/build.rs (1)

14-15: LGTM: strict_nostd cfg flag emission.

Emitting the strict_nostd cfg flag allows downstream code to conditionally compile based on this no_std enforcement mode.

Cargo.toml (3)

27-27: LGTM: New workspace members added.

The addition of sdk-libs/event and program-tests/pinocchio-nostd-test to the workspace aligns with the PR objectives to support no_std refactoring and event handling.

Also applies to: 48-48


126-127: LGTM: Anchor version normalization.

Removing the = prefix from anchor-lang and anchor-spl versions allows for compatible version updates while maintaining the 0.31.1 constraint. This is a more flexible versioning approach than exact pinning.


130-130: LGTM: Borsh no_std compatibility.

Setting default-features = false for borsh enables no_std builds across the workspace. Borsh 0.10.4 supports no_std, so this change is safe.

program-tests/pinocchio-nostd-test/src/lib.rs (3)

1-6: LGTM: Proper no_std configuration.

The crate-level attributes correctly establish a no_std environment:

  • #![no_std] disables the standard library
  • #![cfg_attr(target_os = "solana", forbid(unsafe_code))] enforces safety on Solana
  • #![cfg_attr(not(test), no_main)] removes the main function requirement for library builds

54-65: LGTM: Effective compile-time std detection.

These compile-time guards (lines 55-56 and 63-64) will cause compilation to fail if the std feature is somehow enabled for Solana targets. This is the effective defense mechanism, unlike the build script check.


67-78: LGTM: Test helpers properly gated.

The test helpers module is correctly gated with #[cfg(not(target_os = "solana"))], ensuring it's only available in test environments and not compiled into the Solana binary.

program-tests/pinocchio-nostd-test/tests/basic.rs (1)

1-33: LGTM: Well-structured no_std test.

The test properly exercises the no_std program:

  • Correctly gated with #[cfg(feature = "test-sbf")]
  • Uses test helpers to access program ID
  • Creates a minimal transaction with the TestBasic instruction (discriminator 0)
  • Verifies successful execution

The test effectively validates that the no_std program compiles and executes correctly in a Solana environment.

program-tests/pinocchio-nostd-test/Cargo.toml (4)

14-17: LGTM: Correct no_std feature configuration.

Setting default = [] (line 17) ensures no features are enabled by default, which is correct for a no_std test crate. The no-entrypoint and test-sbf features are appropriately defined for testing scenarios.


19-28: LGTM: Dependencies properly configured for no_std.

All dependencies use { workspace = true } and rely on workspace-level default-features = false configuration. The absence of poseidon in light-hasher (line 26) is intentional to avoid std dependencies, as documented in the comment.

The v2 features on light-sdk-pinocchio and light-sdk-types (lines 23-24) are correctly enabled.


30-36: LGTM: Dev dependencies can safely use std.

Dev dependencies (lines 31-36) correctly enable std-dependent features like solana and poseidon since tests run in a std environment. The devenv feature on light-program-test (line 31) is appropriate for test infrastructure.


38-43: LGTM: Lints allow necessary cfg patterns.

The unexpected_cfgs configuration correctly allows:

  • target_os = "solana" for Solana-specific conditional compilation
  • Features frozen-abi and no-entrypoint that may be checked but not always defined

This prevents spurious warnings during development.

sdk-libs/sdk-pinocchio/Cargo.toml (1)

10-15: Verified: light-compressed-account exports the alloc feature; feature configuration is correct.

The forwarding of light-compressed-account/alloc on line 11 is valid and properly enables no_std + alloc support by default. The feature hierarchy (std → alloc) and optional feature delegation (poseidon, light-account) are correctly structured.

@sergeytimoshin sergeytimoshin merged commit 4de2eca into main Oct 20, 2025
32 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants