Skip to content

refactor: sdk macros 4#2179

Merged
ananas-block merged 9 commits intomainfrom
jorrit/refactor-sdk-macros-4
Jan 19, 2026
Merged

refactor: sdk macros 4#2179
ananas-block merged 9 commits intomainfrom
jorrit/refactor-sdk-macros-4

Conversation

@ananas-block
Copy link
Contributor

@ananas-block ananas-block commented Jan 19, 2026

Changes:

  • cleanup & fix sdk-macros
  • add metadata to light mint

Summary by CodeRabbit

  • New Features

    • Token metadata support for minting (name, symbol, URI, update authority, additional metadata) exposed in mint APIs.
  • Bug Fixes

    • Replaced internal panics with propagated errors and added explicit error variants; improved diagnostics/logging for compression and validation failures.
  • Documentation

    • Added comprehensive RentFree, account-feature, SPL-feature, and comparison guides plus extended light_mint docs with examples and validation rules.
  • Refactor

    • Consolidated code-generation into builder-driven flows and simplified related public exports for generation helpers.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 19, 2026

📝 Walkthrough

Walkthrough

Refactors 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

Cohort / File(s) Summary
Pack & Packing Callsites
sdk-libs/compressible-client/src/lib.rs, sdk-libs/token-sdk/src/pack.rs, sdk-libs/token-sdk/tests/pack_test.rs, sdk-libs/token-sdk/src/compressed_token/v2/mint_action/account_metas.rs
Pack::pack now returns Result<Self::Packed, ProgramError>; implementations return Ok(...). Call sites updated to propagate errors (use ?), tests updated to .unwrap(). Minor inlining attributes added.
Compression Accessors & Size
sdk-libs/sdk/src/account.rs, sdk-libs/sdk/src/compressible/compression_info.rs, sdk-libs/sdk/src/compressible/compress_account.rs, sdk-libs/sdk/src/compressible/compress_account_on_init.rs, sdk-libs/sdk/src/compressible/decompress_idempotent.rs
Size, HasCompressionInfo methods and related APIs now return Result<..., ProgramError>; call sites updated to use ?, making size and compression-info access fallible.
Builder-based Macro Codegen
sdk-libs/macros/src/rentfree/program/compress.rs, sdk-libs/macros/src/rentfree/program/decompress.rs, sdk-libs/macros/src/rentfree/program/variant_enum.rs, sdk-libs/macros/src/rentfree/program/instructions.rs
Introduced CompressBuilder, DecompressBuilder, and variant builders; replaced many free functions with builder methods (context, processor, entrypoint, accounts, error codes), reorganizing codegen flow.
Macro Robustness & Error Propagation
sdk-libs/macros/src/rentfree/accounts/builder.rs, sdk-libs/macros/src/rentfree/accounts/derive.rs, sdk-libs/macros/src/rentfree/account/pack_unpack.rs, sdk-libs/macros/src/rentfree/account/decompress_context.rs, sdk-libs/macros/src/rentfree/account/traits.rs
Generation helpers now return Result<TokenStream, syn::Error>; added get_first_instruction_arg; generated pack/traits now produce fallible signatures and avoid unreachable!() panics by returning mapped errors.
Light Mint TokenMetadata
sdk-libs/macros/src/rentfree/accounts/light_mint.rs, sdk-libs/token-sdk/src/token/create_mints.rs, sdk-libs/token-sdk/src/token/mod.rs, sdk-libs/token-sdk/src/lib.rs
Added optional TokenMetadata fields (name, symbol, uri, update_authority, additional_metadata) with validate_metadata_fields; threaded metadata into SingleMintParams and MintInstructionData; re-exported TokenMetadata types.
Macro parsing, seed, and visitor tweaks
sdk-libs/macros/src/rentfree/program/parsing.rs, sdk-libs/macros/src/rentfree/program/seed_utils.rs, sdk-libs/macros/src/rentfree/program/visitors.rs, sdk-libs/macros/src/rentfree/account/seed_extraction.rs
Improved seed expression classification (nested/indexed/paths), added Debug derive for SeedElement, refined multi-segment constant handling, and introduced safer temp bindings for raw seed exprs.
Crate Context & Public Shape Changes
sdk-libs/macros/src/rentfree/program/crate_context.rs
Removed ParsedModule metadata fields (name, file, path) and CrateContext::module accessor; ParsedModule now only stores items.
Docs & Guides
sdk-libs/macros/docs/accounts/light_mint.md, sdk-libs/macros/docs/features/*.md
Added/expanded documentation: TokenMetadata fields and validation in light_mint; new feature docs for Anchor account features, Anchor SPL features, RentFree features, and a comparison/migration guide.
Errors & Logging
sdk-libs/sdk/src/error.rs, sdk-libs/sdk/src/compressible/compress_runtime.rs
Added new LightSdkError variants (MissingCompressionInfo, PackedVariantCompressionInfo, CTokenCompressionInfo, UnexpectedUnpackedVariant) and numeric mappings; added diagnostic solana_msg::msg logs before some guard errors.
Tests & Minor API Exports
sdk-libs/token-sdk/tests/pack_test.rs, sdk-libs/token-sdk/src/lib.rs
Tests adjusted for fallible pack(); token-sdk re-exports extended to expose TokenMetadataInstructionData and AdditionalMetadata.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

ai-review

Suggested reviewers

  • sergeytimoshin

Poem

✨ Builders craft the code anew,

Results now tell what falsehoods do,
Metadata sings on minted strings,
Macros build the things they bring,
Errors folded, tidy, true.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'refactor: sdk macros 4' is vague and generic. It lacks specificity about the actual changes and uses a numbered suffix ('4') that obscures the primary objective from commit history. Use a more descriptive title that captures the main theme. Consider: 'refactor: convert pack trait and compression methods to Result-based error handling' or 'refactor: introduce builder pattern for compress/decompress code generation and add token metadata support'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 70.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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-sdk-macros-4

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.

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: 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 to ProgramError instead of panicking in generated Size implementations.

The size() method has a fallible return type—Result<usize, ProgramError>—but the generated expressions call try_to_vec().expect(...), which panics on serialization failure and bypasses the error propagation contract. Replace .expect() with .map_err() to convert serialization errors to LightSdkError::Borsh, which implements From<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 in to_output_compressed_account_with_packed_context.

In the HASH_FLAT = true implementation, 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 - DUPLICATE

The same pattern exists in the HASH_FLAT = false implementation (lines 642-655).

🔧 Proposed fix to remove duplicate serialization

For the HASH_FLAT = true block (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 = false block.

🤖 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.

Comment on lines +26 to +47
**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)?;
```

Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 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 -->

Comment on lines +54 to +75
### 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
================================================================================
```
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 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.

Suggested change
### 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.

Comment on lines +103 to +111
### 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 |
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

🧩 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 1

Repository: 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 -30

Repository: 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:

  • MintUncheckedAccount (during init) ✓
  • TokenAccount → Custom struct with #[rentfree_token]
  • Token program → CompressedToken program ✓
  • 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.

Comment on lines +282 to +304
### 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],
}
```

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

🏁 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 -100

Repository: 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 -200

Repository: 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.

Comment on lines +415 to +443
## 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
}
```
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Execution flow comparison needs formatting fixes.

The flow diagrams clearly show the Anchor vs RentFree execution differences, but:

  1. Missing blank line before "### Anchor Standard Flow" heading (MD022)
  2. Code blocks lack language specifier (MD040)
🔧 Formatting fixes
 ## Execution Flow Comparison

+
 ### Anchor Standard Flow
+
 ```text
 try_accounts() {
     1. Extract AccountInfo

And 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.

Comment on lines +59 to +75
/// 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(())
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

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.

Suggested change
/// 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.

Comment on lines +154 to +159
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))
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 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.

Comment on lines +190 to +207
/// 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,
)
}
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

🧩 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 f

Repository: 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 3

Repository: Lightprotocol/light-protocol

Length of output: 2362


🏁 Script executed:

# Find the processor function definition
rg "fn process_compress_accounts_idempotent" -A 5

Repository: Lightprotocol/light-protocol

Length of output: 767


🏁 Script executed:

# Read the CLAUDE.md for macros to understand compress semantics
cat sdk-libs/macros/CLAUDE.md

Repository: Lightprotocol/light-protocol

Length of output: 2985


🏁 Script executed:

# Also check docs/CLAUDE.md for more details
cat sdk-libs/macros/docs/CLAUDE.md

Repository: 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 5

Repository: 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 -100

Repository: 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.rs

Repository: 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 3

Repository: 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 10

Repository: Lightprotocol/light-protocol

Length of output: 54


🏁 Script executed:

# Search for compress functions in sdk
rg "process_compress" sdk-libs/sdk/ -A 5

Repository: 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 -60

Repository: 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.rs

Repository: 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.

Suggested change
/// 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.

Comment on lines +260 to +279
// 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()?;
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 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().

Comment on lines 50 to +63
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
);
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 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.

Suggested change
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.

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

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_call now accepts .as_slice(), but extract_data_field_from_expr only 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).

Comment on lines +405 to +410
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,
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

🏁 Script executed:

# First, let's see the structure and context of create_mints.rs
wc -l sdk-libs/token-sdk/src/token/create_mints.rs

Repository: 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.rs

Repository: 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 -20

Repository: 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 -n

Repository: 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 -n

Repository: 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 -100

Repository: 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 -80

Repository: 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).

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

🤖 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).

Comment on lines +541 to +571
// 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),
))
}
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

🧩 Analysis chain

🏁 Script executed:

# Find ClassifiedSeed definition and usages
rg -n "enum ClassifiedSeed" --type=rs
rg -n "ClassifiedSeed::" --type=rs -A2 -B2

Repository: 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=rs

Repository: 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.rs

Repository: 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/ -A30

Repository: 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 -200

Repository: 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.rs

Repository: 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/ -l

Repository: 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 -B2

Repository: 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.rs

Repository: 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 -B2

Repository: 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 -50

Repository: 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/ -A10

Repository: 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 -100

Repository: 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 -A2

Repository: 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.rs

Repository: 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 -A2

Repository: 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 -10

Repository: 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/ -A3

Repository: 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.rs

Repository: 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/ --multiline

Repository: 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.rs

Repository: 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 -A5

Repository: 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.rs

Repository: 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/ -A20

Repository: 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 -A3

Repository: 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 -B5

Repository: 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.rs

Repository: 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.rs

Repository: 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 -B2

Repository: 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).

@ananas-block ananas-block merged commit f8553ac into main Jan 19, 2026
31 checks passed
@ananas-block ananas-block deleted the jorrit/refactor-sdk-macros-4 branch January 19, 2026 05:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants