-
Notifications
You must be signed in to change notification settings - Fork 87
refactor: zero copy decompress runtime #2216
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
📝 WalkthroughWalkthroughThis pull request refactors core decompression infrastructure from a single-pass to a two-pass zero-allocation approach. It adds Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
52c97ef to
ad7c229
Compare
There was a problem hiding this 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
🤖 Fix all issues with AI agents
In `@sdk-libs/macros/src/light_pdas/account/decompress_context.rs`:
- Around line 23-40: The match arm that writes into pda_indices in
collect_layout_and_tokens currently assumes pda_count < MAX_DECOMPRESS_ACCOUNTS
and can panic; add a bounds check before assigning pda_indices[pda_count] and
incrementing pda_count: if pda_count >= MAX_DECOMPRESS_ACCOUNTS return a
deterministic error (e.g. a new or existing LightSdkError variant indicating too
many PDAs) instead of writing out-of-bounds. Update the arm for
LightAccountVariant::`#packed_variant_name` { .. } to perform the check, and
reference pda_indices, pda_count and MAX_DECOMPRESS_ACCOUNTS when implementing
the guard so the function never panics on overflow.
In `@sdk-libs/sdk/src/interface/decompress_idempotent.rs`:
- Around line 99-143: The function
prepare_account_for_decompression_with_vec_seeds currently truncates seeds if
seeds_vec.len() > MAX_SEEDS which can break PDA derivation; instead, check the
seed count up front and return an error when seeds_vec.len() > MAX_SEEDS (do not
silently drop seeds), e.g., return a LightSdkError variant indicating too many
seeds (or add one) before building seed_refs, then proceed to populate seed_refs
and call prepare_account_for_decompression_idempotent as before; reference:
prepare_account_for_decompression_with_vec_seeds, MAX_SEEDS,
prepare_account_for_decompression_idempotent.
| // Generate match arms for collect_layout_and_tokens - count PDAs that need decompression | ||
| let collect_layout_pda_arms: Vec<_> = pda_ctx_seeds | ||
| .iter() | ||
| .map(|info| { | ||
| let variant_name = &info.variant_name; | ||
| let packed_variant_name = make_packed_variant_name(variant_name); | ||
| quote! { | ||
| LightAccountVariant::#packed_variant_name { .. } => { | ||
| // PDA variant: only count if not already initialized (idempotent check) | ||
| if solana_accounts[i].data_is_empty() { | ||
| pda_indices[pda_count] = i; | ||
| pda_count += 1; | ||
| } | ||
| } | ||
| LightAccountVariant::#variant_name { .. } => { | ||
| return std::result::Result::Err(light_sdk::error::LightSdkError::UnexpectedUnpackedVariant.into()); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add bounds check before writing to pda_indices
If more than MAX_DECOMPRESS_ACCOUNTS PDAs are encountered, the current code will panic on out‑of‑bounds write. Guard the write and return a deterministic error instead.
Proposed fix
- if solana_accounts[i].data_is_empty() {
- pda_indices[pda_count] = i;
- pda_count += 1;
- }
+ if solana_accounts[i].data_is_empty() {
+ if pda_count >= light_sdk::interface::MAX_DECOMPRESS_ACCOUNTS {
+ return std::result::Result::Err(
+ light_sdk::error::LightSdkError::ConstraintViolation.into()
+ );
+ }
+ pda_indices[pda_count] = i;
+ pda_count += 1;
+ }📝 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.
| // Generate match arms for collect_layout_and_tokens - count PDAs that need decompression | |
| let collect_layout_pda_arms: Vec<_> = pda_ctx_seeds | |
| .iter() | |
| .map(|info| { | |
| let variant_name = &info.variant_name; | |
| let packed_variant_name = make_packed_variant_name(variant_name); | |
| quote! { | |
| LightAccountVariant::#packed_variant_name { .. } => { | |
| // PDA variant: only count if not already initialized (idempotent check) | |
| if solana_accounts[i].data_is_empty() { | |
| pda_indices[pda_count] = i; | |
| pda_count += 1; | |
| } | |
| } | |
| LightAccountVariant::#variant_name { .. } => { | |
| return std::result::Result::Err(light_sdk::error::LightSdkError::UnexpectedUnpackedVariant.into()); | |
| } | |
| } | |
| // Generate match arms for collect_layout_and_tokens - count PDAs that need decompression | |
| let collect_layout_pda_arms: Vec<_> = pda_ctx_seeds | |
| .iter() | |
| .map(|info| { | |
| let variant_name = &info.variant_name; | |
| let packed_variant_name = make_packed_variant_name(variant_name); | |
| quote! { | |
| LightAccountVariant::`#packed_variant_name` { .. } => { | |
| // PDA variant: only count if not already initialized (idempotent check) | |
| if solana_accounts[i].data_is_empty() { | |
| if pda_count >= light_sdk::interface::MAX_DECOMPRESS_ACCOUNTS { | |
| return std::result::Result::Err( | |
| light_sdk::error::LightSdkError::ConstraintViolation.into() | |
| ); | |
| } | |
| pda_indices[pda_count] = i; | |
| pda_count += 1; | |
| } | |
| } | |
| LightAccountVariant::`#variant_name` { .. } => { | |
| return std::result::Result::Err(light_sdk::error::LightSdkError::UnexpectedUnpackedVariant.into()); | |
| } | |
| } |
🤖 Prompt for AI Agents
In `@sdk-libs/macros/src/light_pdas/account/decompress_context.rs` around lines 23
- 40, The match arm that writes into pda_indices in collect_layout_and_tokens
currently assumes pda_count < MAX_DECOMPRESS_ACCOUNTS and can panic; add a
bounds check before assigning pda_indices[pda_count] and incrementing pda_count:
if pda_count >= MAX_DECOMPRESS_ACCOUNTS return a deterministic error (e.g. a new
or existing LightSdkError variant indicating too many PDAs) instead of writing
out-of-bounds. Update the arm for LightAccountVariant::`#packed_variant_name` { ..
} to perform the check, and reference pda_indices, pda_count and
MAX_DECOMPRESS_ACCOUNTS when implementing the guard so the function never panics
on overflow.
| /// Maximum number of seeds for PDA derivation. | ||
| pub const MAX_SEEDS: usize = 16; | ||
|
|
||
| /// Convert Vec seeds to fixed array and call PDA creation. | ||
| /// Isolated in separate function to reduce stack usage (seed_refs array is on its own frame). | ||
| #[inline(never)] | ||
| #[cfg(feature = "v2")] | ||
| pub fn prepare_account_for_decompression_with_vec_seeds<'a, 'info, T>( | ||
| program_id: &Pubkey, | ||
| data: T, | ||
| compressed_meta: CompressedAccountMeta, | ||
| solana_account: &AccountInfo<'info>, | ||
| rent_sponsor: &AccountInfo<'info>, | ||
| cpi_accounts: &CpiAccounts<'a, 'info>, | ||
| seeds_vec: &[Vec<u8>], | ||
| ) -> Result< | ||
| Option<light_compressed_account::instruction_data::with_account_info::CompressedAccountInfo>, | ||
| LightSdkError, | ||
| > | ||
| where | ||
| T: Clone | ||
| + crate::account::Size | ||
| + LightDiscriminator | ||
| + Default | ||
| + AnchorSerialize | ||
| + AnchorDeserialize | ||
| + HasCompressionInfo | ||
| + 'info, | ||
| { | ||
| // Convert Vec seeds to fixed array on this stack frame | ||
| let mut seed_refs: [&[u8]; MAX_SEEDS] = [&[]; MAX_SEEDS]; | ||
| let len = seeds_vec.len().min(MAX_SEEDS); | ||
| for j in 0..len { | ||
| seed_refs[j] = seeds_vec[j].as_slice(); | ||
| } | ||
|
|
||
| prepare_account_for_decompression_idempotent( | ||
| program_id, | ||
| data, | ||
| compressed_meta, | ||
| solana_account, | ||
| rent_sponsor, | ||
| cpi_accounts, | ||
| &seed_refs[..len], | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid silently truncating PDA seeds
Truncating extra seeds can desynchronize signer seeds from the derived PDA and cause CPI signature failures. Return an error when the seed count exceeds MAX_SEEDS instead of dropping data.
Proposed fix
- let len = seeds_vec.len().min(MAX_SEEDS);
+ let len = seeds_vec.len();
+ if len > MAX_SEEDS {
+ return Err(LightSdkError::ConstraintViolation);
+ }
for j in 0..len {
seed_refs[j] = seeds_vec[j].as_slice();
}📝 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.
| /// Maximum number of seeds for PDA derivation. | |
| pub const MAX_SEEDS: usize = 16; | |
| /// Convert Vec seeds to fixed array and call PDA creation. | |
| /// Isolated in separate function to reduce stack usage (seed_refs array is on its own frame). | |
| #[inline(never)] | |
| #[cfg(feature = "v2")] | |
| pub fn prepare_account_for_decompression_with_vec_seeds<'a, 'info, T>( | |
| program_id: &Pubkey, | |
| data: T, | |
| compressed_meta: CompressedAccountMeta, | |
| solana_account: &AccountInfo<'info>, | |
| rent_sponsor: &AccountInfo<'info>, | |
| cpi_accounts: &CpiAccounts<'a, 'info>, | |
| seeds_vec: &[Vec<u8>], | |
| ) -> Result< | |
| Option<light_compressed_account::instruction_data::with_account_info::CompressedAccountInfo>, | |
| LightSdkError, | |
| > | |
| where | |
| T: Clone | |
| + crate::account::Size | |
| + LightDiscriminator | |
| + Default | |
| + AnchorSerialize | |
| + AnchorDeserialize | |
| + HasCompressionInfo | |
| + 'info, | |
| { | |
| // Convert Vec seeds to fixed array on this stack frame | |
| let mut seed_refs: [&[u8]; MAX_SEEDS] = [&[]; MAX_SEEDS]; | |
| let len = seeds_vec.len().min(MAX_SEEDS); | |
| for j in 0..len { | |
| seed_refs[j] = seeds_vec[j].as_slice(); | |
| } | |
| prepare_account_for_decompression_idempotent( | |
| program_id, | |
| data, | |
| compressed_meta, | |
| solana_account, | |
| rent_sponsor, | |
| cpi_accounts, | |
| &seed_refs[..len], | |
| ) | |
| /// Maximum number of seeds for PDA derivation. | |
| pub const MAX_SEEDS: usize = 16; | |
| /// Convert Vec seeds to fixed array and call PDA creation. | |
| /// Isolated in separate function to reduce stack usage (seed_refs array is on its own frame). | |
| #[inline(never)] | |
| #[cfg(feature = "v2")] | |
| pub fn prepare_account_for_decompression_with_vec_seeds<'a, 'info, T>( | |
| program_id: &Pubkey, | |
| data: T, | |
| compressed_meta: CompressedAccountMeta, | |
| solana_account: &AccountInfo<'info>, | |
| rent_sponsor: &AccountInfo<'info>, | |
| cpi_accounts: &CpiAccounts<'a, 'info>, | |
| seeds_vec: &[Vec<u8>], | |
| ) -> Result< | |
| Option<light_compressed_account::instruction_data::with_account_info::CompressedAccountInfo>, | |
| LightSdkError, | |
| > | |
| where | |
| T: Clone | |
| crate::account::Size | |
| LightDiscriminator | |
| Default | |
| AnchorSerialize | |
| AnchorDeserialize | |
| HasCompressionInfo | |
| 'info, | |
| { | |
| // Convert Vec seeds to fixed array on this stack frame | |
| let mut seed_refs: [&[u8]; MAX_SEEDS] = [&[]; MAX_SEEDS]; | |
| let len = seeds_vec.len(); | |
| if len > MAX_SEEDS { | |
| return Err(LightSdkError::ConstraintViolation); | |
| } | |
| for j in 0..len { | |
| seed_refs[j] = seeds_vec[j].as_slice(); | |
| } | |
| prepare_account_for_decompression_idempotent( | |
| program_id, | |
| data, | |
| compressed_meta, | |
| solana_account, | |
| rent_sponsor, | |
| cpi_accounts, | |
| &seed_refs[..len], | |
| ) |
🤖 Prompt for AI Agents
In `@sdk-libs/sdk/src/interface/decompress_idempotent.rs` around lines 99 - 143,
The function prepare_account_for_decompression_with_vec_seeds currently
truncates seeds if seeds_vec.len() > MAX_SEEDS which can break PDA derivation;
instead, check the seed count up front and return an error when seeds_vec.len()
> MAX_SEEDS (do not silently drop seeds), e.g., return a LightSdkError variant
indicating too many seeds (or add one) before building seed_refs, then proceed
to populate seed_refs and call prepare_account_for_decompression_idempotent as
before; reference: prepare_account_for_decompression_with_vec_seeds, MAX_SEEDS,
prepare_account_for_decompression_idempotent.
There was a problem hiding this 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
🤖 Fix all issues with AI agents
In `@sdk-libs/macros/src/light_pdas/account/decompress_context.rs`:
- Around line 23-43: The generated match arms write into pda_indices[pda_count]
without checking bounds, which can panic if more than MAX_DECOMPRESS_ACCOUNTS
PDAs are encountered; update the code in the match arms generated for
collect_layout_and_tokens (the block using pda_indices, pda_count) to first
check if pda_count >= MAX_DECOMPRESS_ACCOUNTS and return an appropriate error
(e.g., LightSdkError::TooManyDecompressAccounts or map to an existing error)
before assigning to pda_indices and incrementing pda_count; apply the same guard
to the other similar generated block around lines 187-214 so both sites perform
the capacity check and return on overflow instead of writing out of bounds.
- Around line 216-246: The code panics when slicing all_infos with
&all_infos[post_system_offset..]; change that to check bounds via
all_infos.get(post_system_offset..) and handle the None case by returning an
appropriate ProgramError instead of panicking. In the create_and_write_pda
function, replace let post_system_accounts = &all_infos[post_system_offset..];
with a match or if-let on all_infos.get(post_system_offset..) binding
post_system_accounts on Some and returning Err(...) on None (for example
Err(light_sdk::error::LightSdkError::UnexpectedAccountListLength.into()) or
another suitable ProgramError).
In `@sdk-libs/sdk/src/interface/decompress_idempotent.rs`:
- Around line 97-142: The function
prepare_account_for_decompression_with_vec_seeds currently silently truncates
seeds via seeds_vec.len().min(MAX_SEEDS), which will break PDA signer seeds;
instead, check if seeds_vec.len() > MAX_SEEDS and return an Err(LightSdkError)
(with a clear message like "too many PDA seeds") before attempting conversion;
then proceed to build seed_refs and call
prepare_account_for_decompression_idempotent as before. Reference MAX_SEEDS and
prepare_account_for_decompression_with_vec_seeds when locating the change.
- Around line 3-6: The CompressedAccountInfo import is over-gated; change the
cfg so CompressedAccountInfo and OutAccountInfo are gated together under the
same feature used by the functions that return CompressedAccountInfo (i.e., use
#[cfg(feature = "v2")] for both imports rather than all(feature = "v2", feature
= "cpi-context")). Locate the use of CompressedAccountInfo and OutAccountInfo in
the import list and replace the current cfg that only allows
CompressedAccountInfo under both features with a single #[cfg(feature = "v2")]
(or apply the same cfg to both types) so the return types compile when feature
"v2" is enabled.
| // Generate match arms for collect_layout_and_tokens - count PDAs that need decompression | ||
| let collect_layout_pda_arms: Vec<_> = pda_ctx_seeds | ||
| .iter() | ||
| .map(|info| { | ||
| let variant_name = &info.variant_name; | ||
| let packed_variant_name = make_packed_variant_name(variant_name); | ||
| quote! { | ||
| LightAccountVariant::#packed_variant_name { .. } => { | ||
| // PDA variant: only count if not already initialized (idempotent check) | ||
| if solana_accounts[i].data_is_empty() { | ||
| pda_indices[pda_count] = i; | ||
| pda_count += 1; | ||
| } | ||
| } | ||
| LightAccountVariant::#variant_name { .. } => { | ||
| return std::result::Result::Err(light_sdk::error::LightSdkError::UnexpectedUnpackedVariant.into()); | ||
| } | ||
| } | ||
| }) | ||
| .collect(); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard against >MAX_DECOMPRESS_ACCOUNTS to avoid out‑of‑bounds writes.
The generated code writes pda_indices[pda_count] without a capacity check. More than 16 PDAs will panic at runtime. Add a guard and return an error when the limit is exceeded.
🛠️ Suggested fix
- LightAccountVariant::`#packed_variant_name` { .. } => {
- // PDA variant: only count if not already initialized (idempotent check)
- if solana_accounts[i].data_is_empty() {
- pda_indices[pda_count] = i;
- pda_count += 1;
- }
- }
+ LightAccountVariant::`#packed_variant_name` { .. } => {
+ // PDA variant: only count if not already initialized (idempotent check)
+ if solana_accounts[i].data_is_empty() {
+ if pda_count >= light_sdk::interface::MAX_DECOMPRESS_ACCOUNTS {
+ return std::result::Result::Err(
+ light_sdk::error::LightSdkError::ConstraintViolation.into(),
+ );
+ }
+ pda_indices[pda_count] = i;
+ pda_count += 1;
+ }
+ }Also applies to: 187-214
🤖 Prompt for AI Agents
In `@sdk-libs/macros/src/light_pdas/account/decompress_context.rs` around lines 23
- 43, The generated match arms write into pda_indices[pda_count] without
checking bounds, which can panic if more than MAX_DECOMPRESS_ACCOUNTS PDAs are
encountered; update the code in the match arms generated for
collect_layout_and_tokens (the block using pda_indices, pda_count) to first
check if pda_count >= MAX_DECOMPRESS_ACCOUNTS and return an appropriate error
(e.g., LightSdkError::TooManyDecompressAccounts or map to an existing error)
before assigning to pda_indices and incrementing pda_count; apply the same guard
to the other similar generated block around lines 187-214 so both sites perform
the capacity check and return on overflow instead of writing out of bounds.
| #[inline(never)] | ||
| #[allow(clippy::too_many_arguments)] | ||
| fn create_and_write_pda<'b, 'c>( | ||
| &self, | ||
| cpi_accounts: &light_sdk::cpi::v2::CpiAccounts<'b, #lifetime>, | ||
| address_space: &solana_pubkey::Pubkey, | ||
| compressed_data: &Self::CompressedData, | ||
| solana_account: &solana_account_info::AccountInfo<#lifetime>, | ||
| seed_params: std::option::Option<&Self::SeedParams>, | ||
| zc_info: &mut light_sdk::interface::ZCompressedAccountInfoMut<'c>, | ||
| ) -> std::result::Result<bool, solana_program_error::ProgramError> { | ||
| let post_system_offset = cpi_accounts.system_accounts_end_offset(); | ||
| let all_infos = cpi_accounts.account_infos(); | ||
| let post_system_accounts = &all_infos[post_system_offset..]; | ||
| let program_id = crate::ID; | ||
| let compressed_meta = &compressed_data.meta; | ||
| let mut variant_seed_params = SeedParams::default(); | ||
| let _ = &variant_seed_params; // Suppress unused warning when no params-only fields | ||
|
|
||
| match &compressed_data.data { | ||
| #(#create_pda_match_arms)* | ||
| LightAccountVariant::PackedCTokenData(_) => { | ||
| // Tokens are handled separately, skip here | ||
| std::result::Result::Ok(false) | ||
| } | ||
| LightAccountVariant::CTokenData(_) => { | ||
| return std::result::Result::Err(light_sdk::error::LightSdkError::UnexpectedUnpackedVariant.into()); | ||
| } | ||
| #empty_variant_arm_create | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prevent panics when slicing post‑system accounts.
&all_infos[post_system_offset..] will panic on malformed account lists. Use get() and return a ProgramError instead.
🛠️ Suggested fix
- let post_system_accounts = &all_infos[post_system_offset..];
+ let post_system_accounts = all_infos
+ .get(post_system_offset..)
+ .ok_or(solana_program_error::ProgramError::NotEnoughAccountKeys)?;🤖 Prompt for AI Agents
In `@sdk-libs/macros/src/light_pdas/account/decompress_context.rs` around lines
216 - 246, The code panics when slicing all_infos with
&all_infos[post_system_offset..]; change that to check bounds via
all_infos.get(post_system_offset..) and handle the None case by returning an
appropriate ProgramError instead of panicking. In the create_and_write_pda
function, replace let post_system_accounts = &all_infos[post_system_offset..];
with a match or if-let on all_infos.get(post_system_offset..) binding
post_system_accounts on Some and returning Err(...) on None (for example
Err(light_sdk::error::LightSdkError::UnexpectedAccountListLength.into()) or
another suitable ProgramError).
| use light_compressed_account::{ | ||
| address::derive_address, instruction_data::with_account_info::OutAccountInfo, | ||
| address::derive_address, | ||
| instruction_data::with_account_info::{CompressedAccountInfo, OutAccountInfo}, | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, locate and examine the file in question
fd -t f "decompress_idempotent.rs" | head -20Repository: Lightprotocol/light-protocol
Length of output: 123
🏁 Script executed:
# Check the file size and structure
wc -l sdk-libs/sdk/src/interface/decompress_idempotent.rsRepository: Lightprotocol/light-protocol
Length of output: 127
🏁 Script executed:
# Read the imports and initial structure
head -50 sdk-libs/sdk/src/interface/decompress_idempotent.rs | cat -nRepository: Lightprotocol/light-protocol
Length of output: 2200
🏁 Script executed:
# Search for all uses of CompressedAccountInfo in the file
rg "CompressedAccountInfo" sdk-libs/sdk/src/interface/decompress_idempotent.rs -nRepository: Lightprotocol/light-protocol
Length of output: 702
🏁 Script executed:
# Search for all uses of OutAccountInfo in the file
rg "OutAccountInfo" sdk-libs/sdk/src/interface/decompress_idempotent.rs -nRepository: Lightprotocol/light-protocol
Length of output: 191
🏁 Script executed:
# Check the Cargo.toml to understand feature flags
cat sdk-libs/sdk/Cargo.toml | grep -A 20 "features"Repository: Lightprotocol/light-protocol
Length of output: 2364
🏁 Script executed:
# Check context around line 113
sed -n '105,125n' sdk-libs/sdk/src/interface/decompress_idempotent.rs | cat -nRepository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Check context around line 157
sed -n '150,170n' sdk-libs/sdk/src/interface/decompress_idempotent.rs | cat -nRepository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Check context around line 315
sed -n '305,330n' sdk-libs/sdk/src/interface/decompress_idempotent.rs | cat -nRepository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Check the function signatures and their feature gates
rg -B 5 "CompressedAccountInfo" sdk-libs/sdk/src/interface/decompress_idempotent.rs | head -80Repository: Lightprotocol/light-protocol
Length of output: 1528
🏁 Script executed:
# Get the full context for functions at lines 97-170
sed -n '97,170n' sdk-libs/sdk/src/interface/decompress_idempotent.rs | cat -nRepository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Also verify which features guard the functions
rg "fn prepare_account_for_decompression" -A 20 sdk-libs/sdk/src/interface/decompress_idempotent.rs | head -40Repository: Lightprotocol/light-protocol
Length of output: 1261
🏁 Script executed:
# Check all cfg attributes in the file
rg "#\[cfg" sdk-libs/sdk/src/interface/decompress_idempotent.rs -B 2 -A 2Repository: Lightprotocol/light-protocol
Length of output: 1260
🏁 Script executed:
# Verify the exact feature guards on all functions using these types
sed -n '1,360n' sdk-libs/sdk/src/interface/decompress_idempotent.rs | grep -E "(^#\[cfg|fn (prepare_account|write_to_zero|set_decompressed))" | head -20Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Double-check the return types more carefully
sed -n '97,125n' sdk-libs/sdk/src/interface/decompress_idempotent.rs | cat -nRepository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Read the entire relevant section with line numbers
cat -n sdk-libs/sdk/src/interface/decompress_idempotent.rs | sed -n '1,40p'Repository: Lightprotocol/light-protocol
Length of output: 1669
🏁 Script executed:
# Check lines around functions with CompressedAccountInfo
cat -n sdk-libs/sdk/src/interface/decompress_idempotent.rs | sed -n '97,145p'Repository: Lightprotocol/light-protocol
Length of output: 1878
🏁 Script executed:
# Check if there are any uses of CompressedAccountInfo outside cfg gates
rg "CompressedAccountInfo" sdk-libs/sdk/src/interface/decompress_idempotent.rs -B 10 | grep -E "(#\[cfg|CompressedAccountInfo)"Repository: Lightprotocol/light-protocol
Length of output: 775
🏁 Script executed:
# Let me verify the exact feature guards on the functions that use these types
sed -n '144,165n' sdk-libs/sdk/src/interface/decompress_idempotent.rs | cat -nRepository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Check if v2 is the default feature
grep -A 2 "^\[features\]" sdk-libs/sdk/Cargo.toml | head -5Repository: Lightprotocol/light-protocol
Length of output: 169
🏁 Script executed:
# Let me get the complete picture of function signatures
sed -n '144,200n' sdk-libs/sdk/src/interface/decompress_idempotent.rs | cat -nRepository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Verify when CI would actually report this as an unused import
# Check if there's a build without v2 feature
rg "no-default-features" . --type toml -iRepository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Check what features are used in tests/CI
fd -t f "Cargo.toml" | xargs grep -l "sdk" | head -5Repository: Lightprotocol/light-protocol
Length of output: 225
🏁 Script executed:
# Let me get the complete function definitions to understand feature gating better
sed -n '144,180n' sdk-libs/sdk/src/interface/decompress_idempotent.rs | cat -nRepository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Check the derive_verify_create_and_write_pda function signature
sed -n '225,265n' sdk-libs/sdk/src/interface/decompress_idempotent.rs | cat -nRepository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Use cat to view specific line ranges
cat sdk-libs/sdk/src/interface/decompress_idempotent.rs | sed -n '144,180p' | cat -nRepository: Lightprotocol/light-protocol
Length of output: 1444
🏁 Script executed:
# Check the derive_verify_create_and_write_pda function
cat sdk-libs/sdk/src/interface/decompress_idempotent.rs | sed -n '225,270p' | cat -nRepository: Lightprotocol/light-protocol
Length of output: 2064
🏁 Script executed:
# Let me try a different approach - check the entire file for feature gates on functions
grep -n "^#\[cfg\|^pub fn\|^fn" sdk-libs/sdk/src/interface/decompress_idempotent.rs | tail -30Repository: Lightprotocol/light-protocol
Length of output: 721
🏁 Script executed:
# Find all lines where CompressedAccountInfo appears in function signatures with context
grep -n "CompressedAccountInfo" sdk-libs/sdk/src/interface/decompress_idempotent.rsRepository: Lightprotocol/light-protocol
Length of output: 702
🏁 Script executed:
# For each line, check what function it belongs to and its feature gate
awk '
/^#\[cfg/ {
cfg = $0
getline
}
/^(pub )?fn / {
fn = $0
}
/CompressedAccountInfo/ {
print NR": "cfg" || "fn" || "$0
cfg = ""
}
' sdk-libs/sdk/src/interface/decompress_idempotent.rsRepository: Lightprotocol/light-protocol
Length of output: 1271
🏁 Script executed:
# Let me just manually check each CompressedAccountInfo usage with its feature guard
python3 << 'EOF'
import re
with open('sdk-libs/sdk/src/interface/decompress_idempotent.rs', 'r') as f:
lines = f.readlines()
# Track current feature guard
current_cfg = None
current_fn = None
for i, line in enumerate(lines, 1):
# Check for cfg attribute
if line.strip().startswith('#[cfg'):
current_cfg = line.strip()
# Check for function definition
elif line.strip().startswith('fn ') or line.strip().startswith('pub fn '):
current_fn = line.strip()[:50]
# Check for CompressedAccountInfo usage
if 'CompressedAccountInfo' in line:
print(f"Line {i}: cfg='{current_cfg}' fn='{current_fn}'\n {line.rstrip()}")
EOFRepository: Lightprotocol/light-protocol
Length of output: 158
🏁 Script executed:
# Let me directly examine the usage pattern more carefully without timing out
grep -B 2 "CompressedAccountInfo" sdk-libs/sdk/src/interface/decompress_idempotent.rs | grep -E "(#\[cfg|CompressedAccountInfo)"Repository: Lightprotocol/light-protocol
Length of output: 729
🏁 Script executed:
# Check how OutAccountInfo is used
grep -B 5 "OutAccountInfo" sdk-libs/sdk/src/interface/decompress_idempotent.rs | head -15Repository: Lightprotocol/light-protocol
Length of output: 464
Correct the feature gating for the CompressedAccountInfo import.
The import needs feature gating, but the proposed fix incorrectly restricts CompressedAccountInfo to all(feature = "v2", feature = "cpi-context"). However, CompressedAccountInfo appears in return types of functions guarded only by #[cfg(feature = "v2")] (lines 113, 157). Both types should be gated together:
-use light_compressed_account::{
- address::derive_address,
- instruction_data::with_account_info::{CompressedAccountInfo, OutAccountInfo},
-};
+use light_compressed_account::address::derive_address;
+#[cfg(feature = "v2")]
+use light_compressed_account::instruction_data::with_account_info::{CompressedAccountInfo, OutAccountInfo};🧰 Tools
🪛 GitHub Actions: examples-tests
[error] 5-5: unused import: CompressedAccountInfo
🪛 GitHub Actions: programs
[error] 5-5: unused import: CompressedAccountInfo
🪛 GitHub Check: programs (compressed-token-and-e2e, ["cargo test -p light-compressed-token", "cargo-test-sbf -p c...
[failure] 5-5:
unused import: CompressedAccountInfo
🪛 GitHub Check: programs (compressed-token-batched-tree, ["cargo-test-sbf -p compressed-token-test -- test_transf...
[failure] 5-5:
unused import: CompressedAccountInfo
[failure] 5-5:
unused import: CompressedAccountInfo
🪛 GitHub Check: system-programs (anchor & pinocchio, ["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -p sdk...
[failure] 5-5:
unused import: CompressedAccountInfo
🤖 Prompt for AI Agents
In `@sdk-libs/sdk/src/interface/decompress_idempotent.rs` around lines 3 - 6, The
CompressedAccountInfo import is over-gated; change the cfg so
CompressedAccountInfo and OutAccountInfo are gated together under the same
feature used by the functions that return CompressedAccountInfo (i.e., use
#[cfg(feature = "v2")] for both imports rather than all(feature = "v2", feature
= "cpi-context")). Locate the use of CompressedAccountInfo and OutAccountInfo in
the import list and replace the current cfg that only allows
CompressedAccountInfo under both features with a single #[cfg(feature = "v2")]
(or apply the same cfg to both types) so the return types compile when feature
"v2" is enabled.
| /// Maximum number of seeds for PDA derivation. | ||
| pub const MAX_SEEDS: usize = 16; | ||
|
|
||
| /// Convert Vec seeds to fixed array and call PDA creation. | ||
| /// Isolated in separate function to reduce stack usage (seed_refs array is on its own frame). | ||
| #[inline(never)] | ||
| #[cfg(feature = "v2")] | ||
| pub fn prepare_account_for_decompression_with_vec_seeds<'a, 'info, T>( | ||
| program_id: &Pubkey, | ||
| data: T, | ||
| compressed_meta: CompressedAccountMeta, | ||
| solana_account: &AccountInfo<'info>, | ||
| rent_sponsor: &AccountInfo<'info>, | ||
| cpi_accounts: &CpiAccounts<'a, 'info>, | ||
| seeds_vec: &[Vec<u8>], | ||
| ) -> Result< | ||
| Option<light_compressed_account::instruction_data::with_account_info::CompressedAccountInfo>, | ||
| LightSdkError, | ||
| > | ||
| where | ||
| T: Clone | ||
| + crate::account::Size | ||
| + LightDiscriminator | ||
| + Default | ||
| + AnchorSerialize | ||
| + AnchorDeserialize | ||
| + HasCompressionInfo | ||
| + 'info, | ||
| { | ||
| // Convert Vec seeds to fixed array on this stack frame | ||
| let mut seed_refs: [&[u8]; MAX_SEEDS] = [&[]; MAX_SEEDS]; | ||
| let len = seeds_vec.len().min(MAX_SEEDS); | ||
| for j in 0..len { | ||
| seed_refs[j] = seeds_vec[j].as_slice(); | ||
| } | ||
|
|
||
| prepare_account_for_decompression_idempotent( | ||
| program_id, | ||
| data, | ||
| compressed_meta, | ||
| solana_account, | ||
| rent_sponsor, | ||
| cpi_accounts, | ||
| &seed_refs[..len], | ||
| ) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid silently truncating PDA seeds.
Using min(MAX_SEEDS) drops extra seeds. If the PDA was derived from the full list, the signer seeds become invalid and the CPI will fail. Prefer a hard error when the limit is exceeded.
🛠️ Suggested fix
- let len = seeds_vec.len().min(MAX_SEEDS);
+ if seeds_vec.len() > MAX_SEEDS {
+ msg!("Too many PDA seeds: {}", seeds_vec.len());
+ return Err(LightSdkError::ConstraintViolation);
+ }
+ let len = seeds_vec.len();🤖 Prompt for AI Agents
In `@sdk-libs/sdk/src/interface/decompress_idempotent.rs` around lines 97 - 142,
The function prepare_account_for_decompression_with_vec_seeds currently silently
truncates seeds via seeds_vec.len().min(MAX_SEEDS), which will break PDA signer
seeds; instead, check if seeds_vec.len() > MAX_SEEDS and return an
Err(LightSdkError) (with a clear message like "too many PDA seeds") before
attempting conversion; then proceed to build seed_refs and call
prepare_account_for_decompression_idempotent as before. Reference MAX_SEEDS and
prepare_account_for_decompression_with_vec_seeds when locating the change.
Summary by CodeRabbit
New Features
Refactor
✏️ Tip: You can customize this high-level summary in your review settings.