Conversation
📝 WalkthroughWalkthroughRefactors packing/size/compression accessors to return Result-based errors, propagates those errors across SDK and macros, introduces builder-based code generation for compress/decompress/variant generation, adds TokenMetadata support to light_mint with validation, and expands documentation for account and RentFree features. Changes
Sequence Diagram(s)(omitted) Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (2 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 |
There was a problem hiding this comment.
Actionable comments posted: 11
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
sdk-libs/macros/src/rentfree/account/traits.rs (1)
178-180: Convert serialization errors toProgramErrorinstead of panicking in generatedSizeimplementations.The
size()method has a fallible return type—Result<usize, ProgramError>—but the generated expressions calltry_to_vec().expect(...), which panics on serialization failure and bypasses the error propagation contract. Replace.expect()with.map_err()to convert serialization errors toLightSdkError::Borsh, which implementsFrom<LightSdkError> for ProgramError.Proposed fix
size_fields.push(quote! { - + self.#field_name.try_to_vec().expect("Failed to serialize").len() + + { + let __lp_bytes = self.#field_name + .try_to_vec() + .map_err(|_| light_sdk::error::LightSdkError::Borsh.into())?; + __lp_bytes.len() + } });This applies to lines 178–180 and 190–195.
sdk-libs/sdk/src/account.rs (1)
641-656: Duplicate serialization into_output_compressed_account_with_packed_context.In the
HASH_FLAT = trueimplementation,try_to_vec()is called twice (lines 1042-1045 and 1051-1054), which is redundant and wasteful:output.data = self.account.try_to_vec()... // Line 1042 output.data_hash = H::hash(output.data.as_slice())... // Line 1047 output.data = self.account.try_to_vec()... // Line 1051 - DUPLICATEThe same pattern exists in the
HASH_FLAT = falseimplementation (lines 642-655).🔧 Proposed fix to remove duplicate serialization
For the
HASH_FLAT = trueblock (around line 1041-1055):} else { output.data = self .account .try_to_vec() .map_err(|e| ProgramError::BorshIoError(e.to_string()))?; // For HASH_FLAT = true, use direct serialization output.data_hash = H::hash(output.data.as_slice()) .map_err(LightSdkError::from) .map_err(ProgramError::from)?; output.data_hash[0] = 0; - output.data = self - .account - .try_to_vec() - .map_err(|e| ProgramError::BorshIoError(e.to_string()))?; }Apply the same fix to the
HASH_FLAT = falseblock.
🤖 Fix all issues with AI agents
In `@sdk-libs/macros/docs/features/anchor-account-features.md`:
- Around line 26-47: Add blank lines before and after each fenced code block in
the document to satisfy MD031; specifically insert an empty line above and below
the Rust example starting with "#[account(init, payer = user, space = 8 + 64)]
pub my_account: Account<'info, MyData>," and the following "Generated code
(simplified):" block that contains the system_program::CreateAccount /
system_program::create_account snippet so each code fence is separated from
surrounding text.
In `@sdk-libs/macros/docs/features/comparison.md`:
- Around line 54-75: The code fences containing the ASCII diagrams (the blocks
starting with "ANCHOR:" and "LIGHT RENTFREE:") lack a language hint which can
cause inconsistent rendering; update both code fences to include a language
specifier such as ```text (or leave the language empty as ```) so the diagrams
render consistently across markdown renderers—locate the blocks containing the
"ANCHOR:" and "LIGHT RENTFREE:" headers in comparison.md and prepend the opening
backticks with text (e.g., ```text) for each diagram block.
- Around line 103-111: Update the Type Mapping table entry for InterfaceAccount
to reflect that RentFree supports InterfaceAccount patterns; replace the "Not
yet supported" cell with a description like "`InterfaceAccount<'info, T>` —
supported (handled alongside `Account` and `AccountLoader` in
sdk-libs/macros/src/rentfree/account/seed_extraction.rs)`" so readers know it is
recognized by the seed extraction logic; ensure the wording references
`InterfaceAccount<'info, T>` and the file
`sdk-libs/macros/src/rentfree/account/seed_extraction.rs` for traceability.
In `@sdk-libs/macros/docs/features/rentfree-features.md`:
- Around line 415-443: Add a blank line before the "### Anchor Standard Flow"
heading and before the "### Light RentFree Flow" heading, and add a language
specifier (e.g., "```text" or "```bash") to each fenced code block that contains
the try_accounts(), light_pre_init(), and light_finalize() flows so the markdown
linter rules MD022 and MD040 are satisfied; update the three code fences around
the Anchor Standard Flow and the three code fences around the Light RentFree
Flow (the blocks containing try_accounts(), light_pre_init(), and
light_finalize()) to include the language tag and ensure there is a blank line
immediately above each "### Anchor Standard Flow" and "### Light RentFree Flow"
heading.
- Around line 282-304: The docs incorrectly show field-level
`#[compress_as(...)]` type attributes; update the text to remove the "Supported
compress_as types" list and replace the example to use the struct-level override
syntax used by the macro: show deriving `CompressAs` on a struct (e.g.,
`MixedData`) and a struct attribute `#[compress_as(owner = <expr>, amount =
<expr>)]` that supplies override expressions, include `compression_info:
Option<CompressionInfo>` and optional `#[skip]` on fields you want omitted
(e.g., owner), and keep the real fields `owner`, `amount`, `metadata` in the
struct body; mention that arbitrary field = expression pairs are accepted by the
macro rather than predefined type handlers.
- Around line 447-498: The example references a non-existent UserProfile::SIZE
constant causing a compile error; update the struct derives to include InitSpace
(i.e., derive InitSpace alongside RentFree, Compressible, HasCompressionInfo) so
the correct constant UserProfile::INIT_SPACE is available, and change the
account constraint in CreateProfile from space = 8 + UserProfile::SIZE to space
= 8 + UserProfile::INIT_SPACE; alternatively, if the intent is for compressed
accounts, document and use CompressedInitSpace::COMPRESSED_INIT_SPACE instead
and ensure the corresponding derive/imports are present.
In `@sdk-libs/macros/src/rentfree/program/compress.rs`:
- Around line 59-75: The doc for CompressBuilder::validate mentions enforcing an
800-byte per-account size limit but the function only checks PDA presence;
update the implementation in validate to also iterate over the relevant account
size metadata (e.g., inspect self.account_types or the struct/enum that holds
size info) and return Err(syn::Error::new(...)) when any account size exceeds
800 bytes, or alternatively remove the size-check sentences from the doc if you
prefer not to enforce it here; specifically modify the validate method
(referencing validate, has_pdas, and account_types) to either add the size-check
logic against the 800-byte constant or adjust the docstring to match the current
behavior.
- Around line 154-159: The slice data[0..8] can panic when account data is
shorter than 8 bytes; update the logic in compress.rs (where
account_info.try_borrow_data() is assigned to data and discriminator is derived)
to guard the length first — e.g., check data.len() >= 8 or use
data.get(..8).ok_or(...) and map that to
__anchor_to_program_error(anchor_lang::error::ErrorCode::AccountDiscriminatorMismatch)
before matching against #(`#compress_arms`)* so malformed or uninitialized account
data returns the proper error instead of panicking.
- Around line 190-207: The parameter name proof in the generated function
compress_accounts_idempotent is intentionally unused and should be renamed to
_proof to follow Rust convention and silence unused-variable warnings; update
the function signature in generate_entrypoint so the parameter reads _proof:
light_sdk::instruction::ValidityProof (leaving its type and position unchanged)
and ensure no other code in this generated block or the call to
__processor_functions::process_compress_accounts_idempotent references proof by
name.
In `@sdk-libs/macros/src/rentfree/program/instructions.rs`:
- Around line 260-279: The DecompressBuilder is instantiated without calling
validate(), creating an asymmetric API vs CompressBuilder; add a
DecompressBuilder::validate() method (or make an existing validate
conditional/no-op) and call it after constructing the builder to unify behavior.
Update DecompressBuilder::new/impl to provide validate() that checks pda_seeds
presence (no-op for TokenOnly) and then call decompress_builder.validate()?
immediately after construction before calling generate_accounts_struct() and
generate_seed_provider_impls().
In `@sdk-libs/sdk/src/compressible/compress_runtime.rs`:
- Around line 50-63: The log message for the second guard is mislabeled as "rent
sponsor" though it checks ctx.compression_authority() against
compression_config.compression_authority; update the msg() call inside that
if-block to accurately describe the mismatch (e.g., "invalid compression
authority {:?} != {:?}, expected") so logs reference
ctx.compression_authority().key and compression_config.compression_authority;
ensure the surrounding return Err(ProgramError::Custom(0)) remains unchanged.
| **Behavior**: | ||
| - Allocates space on-chain | ||
| - Assigns the account to the program | ||
| - Calls `System::create_account` CPI | ||
|
|
||
| **Required companions**: `payer`, `space` | ||
|
|
||
| **Example**: | ||
| ```rust | ||
| #[account(init, payer = user, space = 8 + 64)] | ||
| pub my_account: Account<'info, MyData>, | ||
| ``` | ||
|
|
||
| **Generated code** (simplified): | ||
| ```rust | ||
| let cpi_accounts = system_program::CreateAccount { | ||
| from: user.to_account_info(), | ||
| to: my_account.to_account_info(), | ||
| }; | ||
| system_program::create_account(cpi_ctx, lamports, space, program_id)?; | ||
| ``` | ||
|
|
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Minor: Add blank lines around code blocks for Markdown compliance.
The static analysis flagged missing blank lines before/after fenced code blocks (MD031). While functional, fixing these improves consistency with markdown best practices.
🔧 Example fix for one section
**Example**:
+
```rust
#[account(init, payer = user, space = 8 + 64)]
pub my_account: Account<'info, MyData>,Generated code (simplified):
</details>
This pattern applies throughout the document where code blocks immediately follow or precede other content.
<details>
<summary>🧰 Tools</summary>
<details>
<summary>🪛 markdownlint-cli2 (0.18.1)</summary>
27-27: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
---
35-35: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
</details>
</details>
<details>
<summary>🤖 Prompt for AI Agents</summary>
In @sdk-libs/macros/docs/features/anchor-account-features.md around lines 26 -
47, Add blank lines before and after each fenced code block in the document to
satisfy MD031; specifically insert an empty line above and below the Rust
example starting with "#[account(init, payer = user, space = 8 + 64)] pub
my_account: Account<'info, MyData>," and the following "Generated code
(simplified):" block that contains the system_program::CreateAccount /
system_program::create_account snippet so each code fence is separated from
surrounding text.
</details>
<!-- fingerprinting:phantom:poseidon:ocelot -->
<!-- This is an auto-generated comment by CodeRabbit -->
| ### Visual Flow | ||
|
|
||
| ``` | ||
| ANCHOR: | ||
| ================================================================================ | ||
| try_accounts() ─────────────────────────────────────────────> handler() -> exit() | ||
| │ | ||
| ├─ Extract AccountInfo | ||
| ├─ System CPI (init) | ||
| ├─ Token CPI (mint::*, token::*) | ||
| └─ Deserialize | ||
| ================================================================================ | ||
|
|
||
| LIGHT RENTFREE: | ||
| ================================================================================ | ||
| try_accounts() ───> light_pre_init() ───> handler() ───> light_finalize() -> exit() | ||
| │ │ │ | ||
| ├─ Extract ├─ Register address ├─ Serialize state | ||
| ├─ System CPI ├─ Compressed mint CPI ├─ Create Merkle leaf | ||
| └─ Deserialize └─ Compression setup └─ Update tree | ||
| ================================================================================ | ||
| ``` |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Clear visual flow diagrams - consider adding language hint for better rendering.
The ASCII diagrams effectively illustrate the execution flow difference. For the code blocks containing diagrams, you could use a language hint like text or leave empty (which some renderers handle better).
🔧 Optional: Add language specifier for consistency
-```
+```text
ANCHOR:
================================================================================📝 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.
| ### Visual Flow | |
| ``` | |
| ANCHOR: | |
| ================================================================================ | |
| try_accounts() ─────────────────────────────────────────────> handler() -> exit() | |
| │ | |
| ├─ Extract AccountInfo | |
| ├─ System CPI (init) | |
| ├─ Token CPI (mint::*, token::*) | |
| └─ Deserialize | |
| ================================================================================ | |
| LIGHT RENTFREE: | |
| ================================================================================ | |
| try_accounts() ───> light_pre_init() ───> handler() ───> light_finalize() -> exit() | |
| │ │ │ | |
| ├─ Extract ├─ Register address ├─ Serialize state | |
| ├─ System CPI ├─ Compressed mint CPI ├─ Create Merkle leaf | |
| └─ Deserialize └─ Compression setup └─ Update tree | |
| ================================================================================ | |
| ``` | |
| ### Visual Flow | |
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
55-55: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
🤖 Prompt for AI Agents
In `@sdk-libs/macros/docs/features/comparison.md` around lines 54 - 75, The code
fences containing the ASCII diagrams (the blocks starting with "ANCHOR:" and
"LIGHT RENTFREE:") lack a language hint which can cause inconsistent rendering;
update both code fences to include a language specifier such as ```text (or
leave the language empty as ```) so the diagrams render consistently across
markdown renderers—locate the blocks containing the "ANCHOR:" and "LIGHT
RENTFREE:" headers in comparison.md and prepend the opening backticks with text
(e.g., ```text) for each diagram block.
| ### Type Mapping | ||
|
|
||
| | Anchor SPL Type | Light RentFree Type | | ||
| |-----------------|---------------------| | ||
| | `Mint` | `UncheckedAccount` (during init) | | ||
| | `TokenAccount` | Custom struct with `#[rentfree_token]` | | ||
| | `Token` program | `CompressedToken` program | | ||
| | `TokenInterface` | Not yet supported | | ||
| | `InterfaceAccount` | Not yet supported | |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Check for TokenInterface or InterfaceAccount in RentFree macros
rg -n "TokenInterface|InterfaceAccount" --type rust sdk-libs/macros/Repository: Lightprotocol/light-protocol
Length of output: 320
🏁 Script executed:
# Check CLAUDE.md files in macros for feature descriptions
fd "CLAUDE.md" sdk-libs/macros/ -x cat {}Repository: Lightprotocol/light-protocol
Length of output: 6500
🏁 Script executed:
# Look for rentfree_token usage and token-related macro implementation
rg -n "rentfree_token" --type rust sdk-libs/macros/ -A 3 -B 1Repository: Lightprotocol/light-protocol
Length of output: 7487
🏁 Script executed:
# Check the comparison.md file to understand context
cat sdk-libs/macros/docs/features/comparison.md | head -120 | tail -30Repository: Lightprotocol/light-protocol
Length of output: 1210
InterfaceAccount is already supported—update the mapping table.
The type mapping for InterfaceAccount is inaccurate. The RentFree implementation actively handles InterfaceAccount<'info, T> as a recognized account type pattern (alongside Account and AccountLoader) in sdk-libs/macros/src/rentfree/account/seed_extraction.rs. Update the table to reflect that InterfaceAccount is supported, not "Not yet supported."
The remaining mappings are correct:
Mint→UncheckedAccount(during init) ✓TokenAccount→ Custom struct with#[rentfree_token]✓Tokenprogram →CompressedTokenprogram ✓TokenInterface→ "Not yet supported" ✓ (no implementation found)
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
105-105: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
🤖 Prompt for AI Agents
In `@sdk-libs/macros/docs/features/comparison.md` around lines 103 - 111, Update
the Type Mapping table entry for InterfaceAccount to reflect that RentFree
supports InterfaceAccount patterns; replace the "Not yet supported" cell with a
description like "`InterfaceAccount<'info, T>` — supported (handled alongside
`Account` and `AccountLoader` in
sdk-libs/macros/src/rentfree/account/seed_extraction.rs)`" so readers know it is
recognized by the seed extraction logic; ensure the wording references
`InterfaceAccount<'info, T>` and the file
`sdk-libs/macros/src/rentfree/account/seed_extraction.rs` for traceability.
| ### 12. `#[derive(CompressAs)]` | ||
|
|
||
| **Purpose**: Derives hashing behavior based on `#[compress_as(...)]` field attributes. | ||
|
|
||
| **Supported compress_as types**: | ||
| - `pubkey` - Hash as 32-byte pubkey | ||
| - `u64` / `u128` - Hash as integer | ||
| - `bytes` - Hash as raw bytes | ||
| - `array` - Hash array elements | ||
|
|
||
| **Example**: | ||
| ```rust | ||
| #[derive(CompressAs)] | ||
| pub struct MixedData { | ||
| #[compress_as(pubkey)] | ||
| pub owner: Pubkey, | ||
| #[compress_as(u64)] | ||
| pub amount: u64, | ||
| #[compress_as(bytes)] | ||
| pub metadata: [u8; 32], | ||
| } | ||
| ``` | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find CompressAs derive macro implementation
find sdk-libs/macros/src -type f -name "*.rs" | head -20
echo "---"
# Search for CompressAs trait and macro implementation
rg -n "CompressAs|compress_as" sdk-libs/macros/src --type rust -A 3 -B 1 | head -100Repository: Lightprotocol/light-protocol
Length of output: 7764
🏁 Script executed:
#!/bin/bash
# Get the full traits.rs file to understand compress_as implementation
cat -n sdk-libs/macros/src/rentfree/account/traits.rs | head -200Repository: Lightprotocol/light-protocol
Length of output: 8484
Remove the incorrect compress_as type attributes and replace with correct field override syntax.
The documented compress_as types (pubkey, u64, u128, bytes, array) as field-level attributes don't exist in the implementation. The actual compress_as macro uses struct-level field override syntax: #[compress_as(field_name = value, ...)].
Replace the example with the correct pattern:
Corrected example
#[derive(CompressAs)]
#[compress_as(owner = Pubkey::default(), amount = 0)]
pub struct MixedData {
pub compression_info: Option<CompressionInfo>,
#[skip] // Optional: skip specific fields from being cloned
pub owner: Pubkey,
pub amount: u64,
pub metadata: [u8; 32],
}The macro accepts arbitrary field name and expression pairs to override values during compression. Remove discussion of "supported compress_as types"—there are no predefined type handlers.
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
293-293: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
🤖 Prompt for AI Agents
In `@sdk-libs/macros/docs/features/rentfree-features.md` around lines 282 - 304,
The docs incorrectly show field-level `#[compress_as(...)]` type attributes;
update the text to remove the "Supported compress_as types" list and replace the
example to use the struct-level override syntax used by the macro: show deriving
`CompressAs` on a struct (e.g., `MixedData`) and a struct attribute
`#[compress_as(owner = <expr>, amount = <expr>)]` that supplies override
expressions, include `compression_info: Option<CompressionInfo>` and optional
`#[skip]` on fields you want omitted (e.g., owner), and keep the real fields
`owner`, `amount`, `metadata` in the struct body; mention that arbitrary field =
expression pairs are accepted by the macro rather than predefined type handlers.
| ## Execution Flow Comparison | ||
|
|
||
| ### Anchor Standard Flow | ||
| ``` | ||
| try_accounts() { | ||
| 1. Extract AccountInfo | ||
| 2. Create via system CPI (init) | ||
| 3. Init token/mint CPI | ||
| 4. Deserialize | ||
| } | ||
| // instruction handler | ||
| ``` | ||
|
|
||
| ### Light RentFree Flow | ||
| ``` | ||
| try_accounts() { | ||
| 1. Extract AccountInfo | ||
| 2. Create PDA via system CPI (if init) | ||
| 3. Deserialize | ||
| } | ||
| light_pre_init() { | ||
| 4. Register compressed address | ||
| 5. Create compressed mint CPI (if #[light_mint]) | ||
| } | ||
| // instruction handler | ||
| light_finalize() { | ||
| 6. Complete compression | ||
| } | ||
| ``` |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Execution flow comparison needs formatting fixes.
The flow diagrams clearly show the Anchor vs RentFree execution differences, but:
- Missing blank line before "### Anchor Standard Flow" heading (MD022)
- Code blocks lack language specifier (MD040)
🔧 Formatting fixes
## Execution Flow Comparison
+
### Anchor Standard Flow
+
```text
try_accounts() {
1. Extract AccountInfoAnd similarly for the RentFree flow section.
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
417-417: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
418-418: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
418-418: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
428-428: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
429-429: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
429-429: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
In `@sdk-libs/macros/docs/features/rentfree-features.md` around lines 415 - 443,
Add a blank line before the "### Anchor Standard Flow" heading and before the
"### Light RentFree Flow" heading, and add a language specifier (e.g., "```text"
or "```bash") to each fenced code block that contains the try_accounts(),
light_pre_init(), and light_finalize() flows so the markdown linter rules MD022
and MD040 are satisfied; update the three code fences around the Anchor Standard
Flow and the three code fences around the Light RentFree Flow (the blocks
containing try_accounts(), light_pre_init(), and light_finalize()) to include
the language tag and ensure there is a blank line immediately above each "###
Anchor Standard Flow" and "### Light RentFree Flow" heading.
| /// Validate the builder configuration. | ||
| /// | ||
| /// Checks that: | ||
| /// - At least one account type is provided (for PDA variants) | ||
| /// - All account sizes are within the 800-byte limit | ||
| /// | ||
| /// # Returns | ||
| /// `Ok(())` if validation passes, or a `syn::Error` describing the issue. | ||
| pub fn validate(&self) -> Result<()> { | ||
| // For variants that include PDAs, require at least one account type | ||
| if self.has_pdas() && self.account_types.is_empty() { | ||
| return Err(syn::Error::new( | ||
| proc_macro2::Span::call_site(), | ||
| "CompressBuilder requires at least one account type for PDA compression", | ||
| )); | ||
| } | ||
| Ok(()) |
There was a problem hiding this comment.
Doc/behavior mismatch in validate
The doc claims size checks (Line 61–64), but the implementation only checks account_types presence. Please either implement the size check here or update the doc to avoid misleading users.
✏️ Suggested doc fix
- /// - All account sizes are within the 800-byte limit
+ /// - (Compile-time) size validation is generated separately📝 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.
| /// Validate the builder configuration. | |
| /// | |
| /// Checks that: | |
| /// - At least one account type is provided (for PDA variants) | |
| /// - All account sizes are within the 800-byte limit | |
| /// | |
| /// # Returns | |
| /// `Ok(())` if validation passes, or a `syn::Error` describing the issue. | |
| pub fn validate(&self) -> Result<()> { | |
| // For variants that include PDAs, require at least one account type | |
| if self.has_pdas() && self.account_types.is_empty() { | |
| return Err(syn::Error::new( | |
| proc_macro2::Span::call_site(), | |
| "CompressBuilder requires at least one account type for PDA compression", | |
| )); | |
| } | |
| Ok(()) | |
| /// Validate the builder configuration. | |
| /// | |
| /// Checks that: | |
| /// - At least one account type is provided (for PDA variants) | |
| /// - (Compile-time) size validation is generated separately | |
| /// | |
| /// # Returns | |
| /// `Ok(())` if validation passes, or a `syn::Error` describing the issue. | |
| pub fn validate(&self) -> Result<()> { | |
| // For variants that include PDAs, require at least one account type | |
| if self.has_pdas() && self.account_types.is_empty() { | |
| return Err(syn::Error::new( | |
| proc_macro2::Span::call_site(), | |
| "CompressBuilder requires at least one account type for PDA compression", | |
| )); | |
| } | |
| Ok(()) |
🤖 Prompt for AI Agents
In `@sdk-libs/macros/src/rentfree/program/compress.rs` around lines 59 - 75, The
doc for CompressBuilder::validate mentions enforcing an 800-byte per-account
size limit but the function only checks PDA presence; update the implementation
in validate to also iterate over the relevant account size metadata (e.g.,
inspect self.account_types or the struct/enum that holds size info) and return
Err(syn::Error::new(...)) when any account size exceeds 800 bytes, or
alternatively remove the size-check sentences from the doc if you prefer not to
enforce it here; specifically modify the validate method (referencing validate,
has_pdas, and account_types) to either add the size-check logic against the
800-byte constant or adjust the docstring to match the current behavior.
| let data = account_info.try_borrow_data().map_err(__anchor_to_program_error)?; | ||
| let discriminator = &data[0..8]; | ||
|
|
||
| match discriminator { | ||
| #(#compress_arms)* | ||
| _ => Err(__anchor_to_program_error(anchor_lang::error::ErrorCode::AccountDiscriminatorMismatch)) |
There was a problem hiding this comment.
Guard against short account data before slicing discriminator
let discriminator = &data[0..8]; will panic if the account data is shorter than 8 bytes (e.g., uninitialized or malformed). This can hard-fail the program. Add a length check or use get(..8) and return a proper error.
🛡️ Proposed fix
- let discriminator = &data[0..8];
+ let discriminator = data
+ .get(0..8)
+ .ok_or_else(|| __anchor_to_program_error(anchor_lang::error::ErrorCode::AccountDiscriminatorMismatch))?;🤖 Prompt for AI Agents
In `@sdk-libs/macros/src/rentfree/program/compress.rs` around lines 154 - 159, The
slice data[0..8] can panic when account data is shorter than 8 bytes; update the
logic in compress.rs (where account_info.try_borrow_data() is assigned to data
and discriminator is derived) to guard the length first — e.g., check data.len()
>= 8 or use data.get(..8).ok_or(...) and map that to
__anchor_to_program_error(anchor_lang::error::ErrorCode::AccountDiscriminatorMismatch)
before matching against #(`#compress_arms`)* so malformed or uninitialized account
data returns the proper error instead of panicking.
| /// Generate the compress instruction entrypoint function. | ||
| pub fn generate_entrypoint(&self) -> Result<syn::ItemFn> { | ||
| Ok(syn::parse_quote! { | ||
| #[inline(never)] | ||
| #[allow(clippy::too_many_arguments)] | ||
| pub fn compress_accounts_idempotent<'info>( | ||
| ctx: Context<'_, '_, '_, 'info, CompressAccountsIdempotent<'info>>, | ||
| proof: light_sdk::instruction::ValidityProof, | ||
| compressed_accounts: Vec<light_sdk::instruction::account_meta::CompressedAccountMetaNoLamportsNoAddress>, | ||
| system_accounts_offset: u8, | ||
| ) -> Result<()> { | ||
| __processor_functions::process_compress_accounts_idempotent( | ||
| &ctx.accounts, | ||
| &ctx.remaining_accounts, | ||
| compressed_accounts, | ||
| system_accounts_offset, | ||
| ) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's check if the file exists and read the relevant section
cat -n sdk-libs/macros/src/rentfree/program/compress.rs | sed -n '180,220p'Repository: Lightprotocol/light-protocol
Length of output: 2195
🏁 Script executed:
# Let's also examine the processor function being called
fd "process_compress_accounts" --type f -x grep -l "process_compress_accounts_idempotent"Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Check for CLAUDE.md documentation
find . -name "CLAUDE.md" -type fRepository: Lightprotocol/light-protocol
Length of output: 645
🏁 Script executed:
# Look for ValidityProof usage patterns in compress operations
rg "ValidityProof" sdk-libs/macros/src/rentfree/program/ -A 3 -B 3Repository: Lightprotocol/light-protocol
Length of output: 2362
🏁 Script executed:
# Find the processor function definition
rg "fn process_compress_accounts_idempotent" -A 5Repository: Lightprotocol/light-protocol
Length of output: 767
🏁 Script executed:
# Read the CLAUDE.md for macros to understand compress semantics
cat sdk-libs/macros/CLAUDE.mdRepository: Lightprotocol/light-protocol
Length of output: 2985
🏁 Script executed:
# Also check docs/CLAUDE.md for more details
cat sdk-libs/macros/docs/CLAUDE.mdRepository: Lightprotocol/light-protocol
Length of output: 3588
🏁 Script executed:
# Let's look at decompress processor to understand the pattern
rg "fn process_decompress_accounts_idempotent" -A 5Repository: Lightprotocol/light-protocol
Length of output: 1321
🏁 Script executed:
# Check the entire compress.rs file to see if there's context about proof handling
cat -n sdk-libs/macros/src/rentfree/program/compress.rs | head -100Repository: Lightprotocol/light-protocol
Length of output: 4768
🏁 Script executed:
# Let's look at the decompress processor to see how it uses proof
rg "fn process_decompress_accounts_idempotent" -A 30 sdk-libs/macros/src/rentfree/program/decompress.rsRepository: Lightprotocol/light-protocol
Length of output: 1498
🏁 Script executed:
# Check if there are any comments or documentation about why compress doesn't use proof
rg "compress" sdk-libs/macros/docs/rentfree_program/ -i -A 3 -B 3Repository: Lightprotocol/light-protocol
Length of output: 17560
🏁 Script executed:
# Let's look at the full generate_entrypoint method in decompress to compare patterns
cat -n sdk-libs/macros/src/rentfree/program/decompress.rs | sed -n '140,180p'Repository: Lightprotocol/light-protocol
Length of output: 2257
🏁 Script executed:
# Check if there's any documentation or comments about why compress doesn't validate proof
rg "compress_accounts_idempotent" -B 5 -A 15 sdk-libs/macros/docs/Repository: Lightprotocol/light-protocol
Length of output: 6236
🏁 Script executed:
# Let's see if there's a pattern - check what the runtime compress function expects
rg "process_compress_accounts" sdk-libs/sdk -A 10Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Search for compress functions in sdk
rg "process_compress" sdk-libs/sdk/ -A 5Repository: Lightprotocol/light-protocol
Length of output: 1116
🏁 Script executed:
# Let's look at the context impl for compress to see if proof is validated there
cat -n sdk-libs/macros/src/rentfree/program/compress.rs | sed -n '80,150p'Repository: Lightprotocol/light-protocol
Length of output: 3989
🏁 Script executed:
# Final check: verify compress_accounts_idempotent is the only place and no logic actually uses the proof
rg "compress_accounts_idempotent" sdk-libs/ -B 2 -A 8 | head -60Repository: Lightprotocol/light-protocol
Length of output: 4951
🏁 Script executed:
# Check if proof is used anywhere in the generated compress entrypoint or processor
rg "proof" sdk-libs/macros/src/rentfree/program/compress.rsRepository: Lightprotocol/light-protocol
Length of output: 133
Rename proof to _proof to clarify it's intentionally unused
Compress operations don't validate proofs since they compress already on-chain PDAs directly to the Merkle tree, unlike decompress which reconstructs accounts from compressed state and requires proof validation. Prefixing with underscore follows Rust convention and silences compiler warnings.
Fix
- proof: light_sdk::instruction::ValidityProof,
+ _proof: light_sdk::instruction::ValidityProof,📝 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 the compress instruction entrypoint function. | |
| pub fn generate_entrypoint(&self) -> Result<syn::ItemFn> { | |
| Ok(syn::parse_quote! { | |
| #[inline(never)] | |
| #[allow(clippy::too_many_arguments)] | |
| pub fn compress_accounts_idempotent<'info>( | |
| ctx: Context<'_, '_, '_, 'info, CompressAccountsIdempotent<'info>>, | |
| proof: light_sdk::instruction::ValidityProof, | |
| compressed_accounts: Vec<light_sdk::instruction::account_meta::CompressedAccountMetaNoLamportsNoAddress>, | |
| system_accounts_offset: u8, | |
| ) -> Result<()> { | |
| __processor_functions::process_compress_accounts_idempotent( | |
| &ctx.accounts, | |
| &ctx.remaining_accounts, | |
| compressed_accounts, | |
| system_accounts_offset, | |
| ) | |
| } | |
| /// Generate the compress instruction entrypoint function. | |
| pub fn generate_entrypoint(&self) -> Result<syn::ItemFn> { | |
| Ok(syn::parse_quote! { | |
| #[inline(never)] | |
| #[allow(clippy::too_many_arguments)] | |
| pub fn compress_accounts_idempotent<'info>( | |
| ctx: Context<'_, '_, '_, 'info, CompressAccountsIdempotent<'info>>, | |
| _proof: light_sdk::instruction::ValidityProof, | |
| compressed_accounts: Vec<light_sdk::instruction::account_meta::CompressedAccountMetaNoLamportsNoAddress>, | |
| system_accounts_offset: u8, | |
| ) -> Result<()> { | |
| __processor_functions::process_compress_accounts_idempotent( | |
| &ctx.accounts, | |
| &ctx.remaining_accounts, | |
| compressed_accounts, | |
| system_accounts_offset, | |
| ) | |
| } |
🤖 Prompt for AI Agents
In `@sdk-libs/macros/src/rentfree/program/compress.rs` around lines 190 - 207, The
parameter name proof in the generated function compress_accounts_idempotent is
intentionally unused and should be renamed to _proof to follow Rust convention
and silence unused-variable warnings; update the function signature in
generate_entrypoint so the parameter reads _proof:
light_sdk::instruction::ValidityProof (leaving its type and position unchanged)
and ensure no other code in this generated block or the call to
__processor_functions::process_compress_accounts_idempotent references proof by
name.
| // Create CompressBuilder to generate all compress-related code | ||
| let compress_builder = CompressBuilder::new(account_types.clone(), instruction_variant); | ||
| compress_builder.validate()?; | ||
|
|
||
| let size_validation_checks = compress_builder.generate_size_validation()?; | ||
| let error_codes = compress_builder.generate_error_codes()?; | ||
|
|
||
| let pda_seed_provider_impls = | ||
| generate_pda_seed_provider_impls(&account_types, &pda_ctx_seeds, &pda_seeds)?; | ||
| let token_variant_name = format_ident!("TokenAccountVariant"); | ||
|
|
||
| // Create DecompressBuilder to generate all decompress-related code | ||
| let decompress_builder = DecompressBuilder::new( | ||
| pda_ctx_seeds.clone(), | ||
| token_variant_name, | ||
| account_types.clone(), | ||
| pda_seeds.clone(), | ||
| ); | ||
| // Note: DecompressBuilder validation is optional for now since pda_seeds may be empty for TokenOnly | ||
|
|
||
| let decompress_accounts = decompress_builder.generate_accounts_struct()?; | ||
| let pda_seed_provider_impls = decompress_builder.generate_seed_provider_impls()?; |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Builder instantiation looks correct; consider documenting DecompressBuilder validation strategy.
CompressBuilder has validate() called explicitly, while DecompressBuilder does not (per the comment on line 276). The asymmetry is intentional for TokenOnly cases, but it might be worth adding a validate() method to DecompressBuilder that's a no-op or conditionally validates based on pda_seeds presence—this would make the API more uniform.
🤖 Prompt for AI Agents
In `@sdk-libs/macros/src/rentfree/program/instructions.rs` around lines 260 - 279,
The DecompressBuilder is instantiated without calling validate(), creating an
asymmetric API vs CompressBuilder; add a DecompressBuilder::validate() method
(or make an existing validate conditional/no-op) and call it after constructing
the builder to unify behavior. Update DecompressBuilder::new/impl to provide
validate() that checks pda_seeds presence (no-op for TokenOnly) and then call
decompress_builder.validate()? immediately after construction before calling
generate_accounts_struct() and generate_seed_provider_impls().
| if *ctx.rent_sponsor().key != compression_config.rent_sponsor { | ||
| msg!( | ||
| "invalid rent sponsor {:?} != {:?}, expected", | ||
| *ctx.rent_sponsor().key, | ||
| compression_config.rent_sponsor | ||
| ); | ||
| return Err(ProgramError::Custom(0)); | ||
| } | ||
| if *ctx.compression_authority().key != compression_config.compression_authority { | ||
| msg!( | ||
| "invalid rent sponsor {:?} != {:?}, expected", | ||
| *ctx.compression_authority().key, | ||
| compression_config.compression_authority | ||
| ); |
There was a problem hiding this comment.
Fix mislabeled compression-authority log.
The second guard checks compression_authority, but the message still says “rent sponsor,” which is misleading during debugging.
🛠️ Proposed fix
- "invalid rent sponsor {:?} != {:?}, expected",
+ "invalid compression authority {:?} != {:?}, expected",📝 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.
| if *ctx.rent_sponsor().key != compression_config.rent_sponsor { | |
| msg!( | |
| "invalid rent sponsor {:?} != {:?}, expected", | |
| *ctx.rent_sponsor().key, | |
| compression_config.rent_sponsor | |
| ); | |
| return Err(ProgramError::Custom(0)); | |
| } | |
| if *ctx.compression_authority().key != compression_config.compression_authority { | |
| msg!( | |
| "invalid rent sponsor {:?} != {:?}, expected", | |
| *ctx.compression_authority().key, | |
| compression_config.compression_authority | |
| ); | |
| if *ctx.rent_sponsor().key != compression_config.rent_sponsor { | |
| msg!( | |
| "invalid rent sponsor {:?} != {:?}, expected", | |
| *ctx.rent_sponsor().key, | |
| compression_config.rent_sponsor | |
| ); | |
| return Err(ProgramError::Custom(0)); | |
| } | |
| if *ctx.compression_authority().key != compression_config.compression_authority { | |
| msg!( | |
| "invalid compression authority {:?} != {:?}, expected", | |
| *ctx.compression_authority().key, | |
| compression_config.compression_authority | |
| ); |
🤖 Prompt for AI Agents
In `@sdk-libs/sdk/src/compressible/compress_runtime.rs` around lines 50 - 63, The
log message for the second guard is mislabeled as "rent sponsor" though it
checks ctx.compression_authority() against
compression_config.compression_authority; update the msg() call inside that
if-block to accurately describe the mismatch (e.g., "invalid compression
authority {:?} != {:?}, expected") so logs reference
ctx.compression_authority().key and compression_config.compression_authority;
ensure the surrounding return Err(ProgramError::Custom(0)) remains unchanged.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
sdk-libs/macros/src/rentfree/account/seed_extraction.rs (1)
582-623: Keep data-field extraction in sync with new.as_slice()handling.
classify_method_callnow accepts.as_slice(), butextract_data_field_from_expronly unwraps.as_ref()/.as_bytes(). This creates inconsistent behavior for data-seed extraction. Consider adding.as_slice()there as well.♻️ Proposed fix
- if method_call.method == "as_ref" || method_call.method == "as_bytes" { + if method_call.method == "as_ref" + || method_call.method == "as_bytes" + || method_call.method == "as_slice" + { return extract_data_field_from_expr(&method_call.receiver); }
🤖 Fix all issues with AI agents
In `@sdk-libs/token-sdk/src/token/create_mints.rs`:
- Around line 405-410: In the invoke_execute path update the CpiContext to use
the queue index rather than the tree index: locate the CpiContext construction
in invoke_execute and change the in_queue_index assignment from
self.params.address_tree_index to self.params.output_queue_index so that both
in_queue_index and out_queue_index reference the same queue (mirror the
invoke_cpi_write behavior).
| cpi_context: Some(CpiContext { | ||
| set_context: false, | ||
| first_set_context: false, | ||
| in_tree_index: self.params.address_tree_index, | ||
| in_queue_index: self.params.address_tree_index, | ||
| out_queue_index: self.params.output_queue_index, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's see the structure and context of create_mints.rs
wc -l sdk-libs/token-sdk/src/token/create_mints.rsRepository: Lightprotocol/light-protocol
Length of output: 200
🏁 Script executed:
# Get the full context around both lines mentioned
sed -n '310,325p' sdk-libs/token-sdk/src/token/create_mints.rs
sed -n '400,420p' sdk-libs/token-sdk/src/token/create_mints.rsRepository: Lightprotocol/light-protocol
Length of output: 200
🏁 Script executed:
# Search for all in_queue_index assignments to understand the pattern
rg "in_queue_index" --type rust -B 2 -A 2 sdk-libs/token-sdk/Repository: Lightprotocol/light-protocol
Length of output: 2320
🏁 Script executed:
# Look for documentation files that explain queue indexing
fd -e md -e txt "CLAUDE.md|queue|INSERT|UPDATE" --type f | head -20Repository: Lightprotocol/light-protocol
Length of output: 929
🏁 Script executed:
# Read the full context of both functions in create_mints.rs
sed -n '300,330p' sdk-libs/token-sdk/src/token/create_mints.rs | cat -n
echo "---"
sed -n '395,420p' sdk-libs/token-sdk/src/token/create_mints.rs | cat -nRepository: Lightprotocol/light-protocol
Length of output: 3147
🏁 Script executed:
# Check the function signatures to understand what each does
sed -n '280,310p' sdk-libs/token-sdk/src/token/create_mints.rs | cat -n
echo "---"
sed -n '375,400p' sdk-libs/token-sdk/src/token/create_mints.rs | cat -nRepository: Lightprotocol/light-protocol
Length of output: 2571
🏁 Script executed:
# Check the CLAUDE.md file for token-sdk guidance
cat sdk-libs/token-sdk/CLAUDE.md 2>/dev/null || echo "No CLAUDE.md found in token-sdk"Repository: Lightprotocol/light-protocol
Length of output: 103
🏁 Script executed:
# Check parent CLAUDE.md
cat CLAUDE.md 2>/dev/null | head -100Repository: Lightprotocol/light-protocol
Length of output: 5665
🏁 Script executed:
# Check the CPI context structure definition
rg "struct.*CpiContext|in_queue_index" --type rust -B 3 -A 3 | head -80Repository: Lightprotocol/light-protocol
Length of output: 5899
Fix in_queue_index assignment in the execute path — this should use output_queue_index, not address_tree_index.
In invoke_cpi_write (line 318), you correctly use:
in_queue_index: self.params.output_queue_index,But in invoke_execute (line 409), you have:
in_queue_index: self.params.address_tree_index,This is inconsistent and incorrect. The field in_queue_index should reference a queue, not a tree index. The pattern in decompress_mint.rs confirms that in_queue_index and out_queue_index should use the same queue reference. Change line 409 to use self.params.output_queue_index to match the CPI write path and fix the queue indexing in the execute instruction.
🤖 Prompt for AI Agents
In `@sdk-libs/token-sdk/src/token/create_mints.rs` around lines 405 - 410, In the
invoke_execute path update the CpiContext to use the queue index rather than the
tree index: locate the CpiContext construction in invoke_execute and change the
in_queue_index assignment from self.params.address_tree_index to
self.params.output_queue_index so that both in_queue_index and out_queue_index
reference the same queue (mirror the invoke_cpi_write behavior).
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@sdk-libs/macros/src/rentfree/account/seed_extraction.rs`:
- Around line 541-571: The index on array-index expressions (Expr::Index) is
being dropped when classifying seeds—so expressions like params.arrays[2] become
ClassifiedSeed::DataField { field_name: arrays } and lose the index; fix this by
extending ClassifiedSeed::DataField to carry the index (e.g., add index:
Option<syn::Expr> or Box<syn::Expr>), then in the Expr::Index match (the branch
using is_params_rooted(&idx.expr) and extract_terminal_field(&idx.expr))
populate that new index field with a clone of idx.index (or None when it’s a
full-range case you still handle separately), and finally update parsing.rs
where DataField is rendered/serialized so the generated seed includes the
indexed element (or, if you prefer to reject indexed expressions, replace the
return with Err(syn::Error::new_spanned(...)) to explicitly forbid params[...]
seeds instead of silently dropping the index).
| // Index expression - handles two cases: | ||
| // 1. b"literal"[..] - converts [u8; N] to &[u8] | ||
| // 2. params.arrays[2] - array indexing on params field | ||
| Expr::Index(idx) => { | ||
| // Case 1: Check if the index is a full range (..) on byte literal | ||
| if let Expr::Range(range) = &*idx.index { | ||
| if range.start.is_none() && range.end.is_none() { | ||
| // This is a full range [..], now check if expr is a byte string literal | ||
| if let Expr::Lit(lit) = &*idx.expr { | ||
| if let syn::Lit::ByteStr(bs) = &lit.lit { | ||
| return Ok(ClassifiedSeed::Literal(bs.value())); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Case 2: Array indexing on params field like params.arrays[2] | ||
| if is_params_rooted(&idx.expr) { | ||
| if let Some(field_name) = extract_terminal_field(&idx.expr) { | ||
| return Ok(ClassifiedSeed::DataField { | ||
| field_name, | ||
| conversion: None, | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| Err(syn::Error::new_spanned( | ||
| expr, | ||
| format!("Unsupported index expression in seeds: {:?}", expr), | ||
| )) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Find ClassifiedSeed definition and usages
rg -n "enum ClassifiedSeed" --type=rs
rg -n "ClassifiedSeed::" --type=rs -A2 -B2Repository: Lightprotocol/light-protocol
Length of output: 125
🏁 Script executed:
# Also search for how DataField is handled in rendering/codegen
rg -n "DataField\s*\{" --type=rs -A5 -B2
rg -n "to_token_stream\|render\|generate" sdk-libs/macros/src/rentfree/account/ --type=rsRepository: Lightprotocol/light-protocol
Length of output: 125
🏁 Script executed:
# Look at the broader context of seed_extraction.rs
wc -l sdk-libs/macros/src/rentfree/account/seed_extraction.rs
head -100 sdk-libs/macros/src/rentfree/account/seed_extraction.rsRepository: Lightprotocol/light-protocol
Length of output: 3636
🏁 Script executed:
# Use rg without type flag (newer rg may need different syntax)
rg "enum ClassifiedSeed" sdk-libs/macros/src/ -A30Repository: Lightprotocol/light-protocol
Length of output: 3038
🏁 Script executed:
# Search for usage patterns - how DataField is consumed in rendering
rg "ClassifiedSeed::" sdk-libs/macros/src/ -A3 -B1 | head -200Repository: Lightprotocol/light-protocol
Length of output: 9194
🏁 Script executed:
# Look at the specific function that processes seeds
sed -n '541,571p' sdk-libs/macros/src/rentfree/account/seed_extraction.rsRepository: Lightprotocol/light-protocol
Length of output: 1401
🏁 Script executed:
# Find how seeds are converted to code - look for token stream generation
rg "to_tokens\|quote\|TokenStream" sdk-libs/macros/src/rentfree/account/ -lRepository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Check what calls classify_seed_expr
rg "classify_seed_expr\|ClassifiedSeed" sdk-libs/macros/src/rentfree/account/ -A2 -B2Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Look at the parsing.rs file to see how DataField is converted to code
sed -n '1,100p' sdk-libs/macros/src/rentfree/program/parsing.rsRepository: Lightprotocol/light-protocol
Length of output: 3237
🏁 Script executed:
# Get the full context around ClassifiedSeed::DataField conversion in parsing.rs
rg "ClassifiedSeed::DataField" sdk-libs/macros/src/rentfree/program/parsing.rs -A8 -B2Repository: Lightprotocol/light-protocol
Length of output: 793
🏁 Script executed:
# Look for where the seeds are used - trace the full flow
grep -n "SeedElement\|seed_to_expr\|params\." sdk-libs/macros/src/rentfree/program/parsing.rs | head -50Repository: Lightprotocol/light-protocol
Length of output: 1559
🏁 Script executed:
# Check what extract_terminal_field does - does it preserve index info?
rg "extract_terminal_field" sdk-libs/macros/src/rentfree/account/ -A10Repository: Lightprotocol/light-protocol
Length of output: 5907
🏁 Script executed:
# Look for actual usage of indexed seeds in tests or examples
rg "seeds\s*=\s*\[" sdk-libs/macros/ -A5 | head -100Repository: Lightprotocol/light-protocol
Length of output: 7361
🏁 Script executed:
# Check if there are any tests for the index case
rg "\[2\]\|arrays\[" sdk-libs/macros/src/ -B5 -A2Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Look at the full extract_terminal_field implementation to see if it handles index recursion
sed -n '700,750p' sdk-libs/macros/src/rentfree/account/seed_extraction.rsRepository: Lightprotocol/light-protocol
Length of output: 1830
🏁 Script executed:
# Look for any documentation or comments about index support
rg "index\|Index" sdk-libs/macros/src/rentfree/account/seed_extraction.rs -B2 -A2Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Look for actual test cases or examples using indexed seeds
find sdk-libs/macros -name "*.rs" -type f -exec grep -l "seeds.*\[.*\].*\[" {} \;Repository: Lightprotocol/light-protocol
Length of output: 202
🏁 Script executed:
# Check the programs directory for any use of indexed seeds
find . -path "*/programs/*" -name "*.rs" -exec grep -l "seeds.*\[" {} \; | head -10Repository: Lightprotocol/light-protocol
Length of output: 2080
🏁 Script executed:
# Look at examples in the docs for seed patterns
rg "seeds\s*=\s*\[" sdk-libs/macros/docs/ -A3Repository: Lightprotocol/light-protocol
Length of output: 5334
🏁 Script executed:
# Check if there are any integration tests that would catch this issue
find . -path "*/tests/*" -name "*.rs" -exec grep -l "rentfree\|ClassifiedSeed" {} \;Repository: Lightprotocol/light-protocol
Length of output: 228
🏁 Script executed:
# Look at the full rendering logic in parsing.rs around line 310-340
sed -n '280,340p' sdk-libs/macros/src/rentfree/program/parsing.rsRepository: Lightprotocol/light-protocol
Length of output: 2602
🏁 Script executed:
# Check if indexed fields are actually used anywhere in real seed specifications
rg "seeds\s*=\s*\[.*\[.*\].*\]" sdk-libs/macros/ --multilineRepository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Look at the error message at the end of that Expr::Index block to understand intent
sed -n '541,580p' sdk-libs/macros/src/rentfree/account/seed_extraction.rsRepository: Lightprotocol/light-protocol
Length of output: 1613
🏁 Script executed:
# Check if there's any code that attempts to handle indexed access elsewhere
rg "Expr::Index" sdk-libs/macros/src/rentfree/account/ -B3 -A5Repository: Lightprotocol/light-protocol
Length of output: 2634
🏁 Script executed:
# Verify the actual data.field_name pattern - can you search for where seeds are rendered in the actual PDA call?
sed -n '230,280p' sdk-libs/macros/src/rentfree/program/parsing.rsRepository: Lightprotocol/light-protocol
Length of output: 1783
🏁 Script executed:
# Check if there are actual tests that would exercise indexed seed syntax
grep -r "seeds.*\[.*\]\[" sdk-tests/ --include="*.rs"Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Look for documentation that specifies what seed expressions are supported
rg "Seeds in.*account.*seeds" sdk-libs/macros/docs/ -A20Repository: Lightprotocol/light-protocol
Length of output: 1653
🏁 Script executed:
# Trace the actual seed usage - how are the rendered seeds used?
rg "find_program_address\|create_program_address" sdk-libs/macros/src/ -B3 -A3Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Check the full flow - what happens when seeds are used in PDA derivation
rg "find_program_address|create_program_address" sdk-libs/ -A5 -B5Repository: Lightprotocol/light-protocol
Length of output: 28549
🏁 Script executed:
# Look at the test that's mentioned in the search results
head -200 sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_single_pubkey_test.rsRepository: Lightprotocol/light-protocol
Length of output: 5967
🏁 Script executed:
# Check if there's documentation on the index case or any TODO/FIXME about it
rg "TODO|FIXME|XXX" sdk-libs/macros/src/rentfree/account/seed_extraction.rsRepository: Lightprotocol/light-protocol
Length of output: 234
🏁 Script executed:
# Verify: when a DataField is used, is the index ever applied anywhere?
rg "params\.\w+\[" sdk-libs/macros/src/rentfree/ -A2 -B2Repository: Lightprotocol/light-protocol
Length of output: 1572
Index expressions in seeds drop the index, causing silent PDA misdefinition.
Code like params.arrays[2] is classified as DataField { field_name: arrays }, losing the index. During code generation, only data.arrays (the full field) is rendered into the seed, not the indexed element. This silently changes what gets hashed for PDA derivation. Either extend ClassifiedSeed::DataField to preserve index information (e.g., add an index: Option<Expr> field and render it in parsing.rs), or reject indexed expressions outright with a compile error to avoid the trap.
🤖 Prompt for AI Agents
In `@sdk-libs/macros/src/rentfree/account/seed_extraction.rs` around lines 541 -
571, The index on array-index expressions (Expr::Index) is being dropped when
classifying seeds—so expressions like params.arrays[2] become
ClassifiedSeed::DataField { field_name: arrays } and lose the index; fix this by
extending ClassifiedSeed::DataField to carry the index (e.g., add index:
Option<syn::Expr> or Box<syn::Expr>), then in the Expr::Index match (the branch
using is_params_rooted(&idx.expr) and extract_terminal_field(&idx.expr))
populate that new index field with a clone of idx.index (or None when it’s a
full-range case you still handle separately), and finally update parsing.rs
where DataField is rendered/serialized so the generated seed includes the
indexed element (or, if you prefer to reject indexed expressions, replace the
return with Err(syn::Error::new_spanned(...)) to explicitly forbid params[...]
seeds instead of silently dropping the index).
Changes:
Summary by CodeRabbit
New Features
Bug Fixes
Documentation
Refactor
✏️ Tip: You can customize this high-level summary in your review settings.