refactor: light program macro#2200
Conversation
📝 WalkthroughWalkthroughThis PR adds namespace-aware parsing for #[light_account(...)] attributes (mint::, token::, associated_token::), centralizes keyword validation, detects ATA fields, and implements automatic bump derivation and bump-appending for mint and token seed generation across the macro pipeline. Changes
Sequence Diagram(s)sequenceDiagram
participant Parser as Macro Parser
participant Infer as Type Inference
participant Validator as Namespace Validator
participant CodeGen as Code Generator
Parser->>Parser: Parse #[light_account(...)] attributes into NamespacedKeyValue
Parser->>Infer: Infer account type from namespace (pda / mint / token / associated_token)
Infer-->>Parser: Inferred type
Parser->>Validator: Validate keys against valid_keys_for_namespace(namespace)
Validator-->>Parser: Validation result (ok / error)
alt valid
Parser->>CodeGen: Emit structured fields (PdaField, LightMintField, TokenAccountField, AtaField)
CodeGen->>CodeGen: For each mint/token without explicit bump -> derive bump via find_program_address
CodeGen->>CodeGen: Append bump to seed arrays and generate invoke_signed bindings
CodeGen-->>Parser: Generated code for pre_init / create instructions
else invalid
Validator-->>Parser: Emit namespace-aware error (missing/unknown key)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ 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 |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 4
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/light_pdas/accounts/mint.rs (1)
185-195: Use the PDA output-tree binding or drop it.
with_pda_contextinjects__output_tree_index, but__output_queue_indexalways usesmints[0].output_tree, leaving__output_tree_indexunused and potentially ignoring the caller’s tree expression. This can trip#![deny(warnings)]in generated code and breaks the intent ofwith_pda_context.Proposed fix (wire the selected tree index)
- // Determine CPI context offset based on PDA context - let (cpi_context_offset, output_tree_setup) = match &builder.pda_context { + // Get shared params from first mint (all mints share same params for now) + let output_tree = &mints[0].output_tree; + + // Determine CPI context offset based on PDA context + let (cpi_context_offset, output_tree_setup, output_tree_expr) = match &builder.pda_context { Some((pda_count, tree_expr)) => { let offset = *pda_count as u8; ( quote! { `#offset` }, - quote! { let __output_tree_index = `#tree_expr`; }, + quote! { let __output_tree_index: u8 = `#tree_expr`; }, + quote! { __output_tree_index }, ) } - None => (quote! { 0u8 }, quote! {}), + None => ( + quote! { 0u8 }, + quote! { let __output_tree_index: u8 = `#output_tree`; }, + quote! { __output_tree_index }, + ), }; @@ - // Get shared params from first mint (all mints share same params for now) - let rent_payment = quote_option_or(&mints[0].rent_payment, quote! { 16u8 }); - let write_top_up = quote_option_or(&mints[0].write_top_up, quote! { 766u32 }); - let output_tree = &mints[0].output_tree; + let rent_payment = quote_option_or(&mints[0].rent_payment, quote! { 16u8 }); + let write_top_up = quote_option_or(&mints[0].write_top_up, quote! { 766u32 }); @@ - let __output_queue_index: u8 = `#output_tree`; + let __output_queue_index: u8 = `#output_tree_expr`;Also applies to: 366-369, 411-417
🤖 Fix all issues with AI agents
In `@sdk-libs/macros/docs/accounts/light_mint.md`:
- Around line 11-20: The docs show a bump included inside mint::seeds which is
now wrong because the implementation appends mint::bump automatically; update
all examples and the required-attributes table (e.g., the light_account
attribute on the UncheckedAccount example using mint::signer, mint::authority,
mint::decimals, mint::seeds) to remove the explicit bump from mint::seeds so
seeds contain only base seeds, and either include a separate mint::bump entry
when explicitly setting the bump or omit mint::bump entirely to allow
auto-derivation; apply this change consistently to the other occurrences noted
(lines around 60-66 and 143-150).
- Around line 69-76: Docs list wrong default values for mint::rent_payment and
mint::write_top_up; update them to match the implementation in mint.rs by
changing the table entries so `mint::rent_payment` shows `16u8` and
`mint::write_top_up` shows `766u32`. Locate the two rows in light_mint.md (the
`mint::rent_payment` and `mint::write_top_up` attribute lines) and replace the
current defaults (`2u8` and `0u32`) with the implemented defaults (`16u8` and
`766u32`); alternatively, if you prefer changing code instead, update the
default constants in mint.rs to match the documented values and ensure unit
tests reflect the new defaults.
In `@sdk-libs/macros/src/light_pdas/account/seed_extraction.rs`:
- Around line 475-487: The loop over elems silently ignores errors from
classify_seed_expr leading to missing authority_seeds; change it to fail-fast by
propagating errors instead of dropping them—e.g., replace the if let Ok(...)
block with a match or use let seed = classify_seed_expr(elem,
&instruction_args)? to return the error (or map it to a clearer error with
context) and push the seed into seeds; ensure authority_seeds = Some(seeds) only
runs after all seeds are successfully parsed.
In `@sdk-libs/macros/src/light_pdas/accounts/light_account.rs`:
- Around line 490-516: The mark-only branch that currently only validates
LightAccountType::Token must also validate mark-only associated_token cases:
when args.is_token && !args.has_init and args.account_type ==
LightAccountType::AssociatedToken, require that args.key_values contains both
"authority" and "mint" and return an Error::new_spanned if either is missing,
and also forbid/raise an error if "owner" (or any keys only valid for init) is
present; update the same validation block in light_account.rs (the branch using
args.is_token, args.has_init, and iterating args.key_values) to include this
AssociatedToken logic analogous to the Token case, ensuring associated_token
PDAs remain derivable at runtime.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@sdk-libs/macros/docs/accounts/light_mint.md`:
- Line 5: Update the doc text to match the actual examples and implementation:
replace the phrase "a `Mint` account field" with "an `UncheckedAccount<'info>`
field" (or explicitly state that the field should be `UncheckedAccount<'info>`
when using the `#[light_account(init, mint::...)]` attribute), and ensure any
surrounding sentences/examples reference `UncheckedAccount<'info>` consistently
so the documentation matches the code paths that generate compressed mint
creation and automatic decompression support.
In `@sdk-libs/macros/src/light_pdas/account/seed_extraction.rs`:
- Around line 500-513: Update the error text produced in the branch where
is_standalone_keyword(&ident_str) is false (the Err created with
syn::Error::new_spanned for ident/ident_str) to give clear, separate examples
for keywords that require a value (e.g., `authority` requires a bracketed array
like `MyAccount::authority = [...]`) versus keywords that are standalone flags
(e.g., `mint` or `init` used as `MyAccount::mint`), referencing
account_type_owned in both examples so users can see the correct namespace
syntax; adjust the message constructed around ident_str/account_type_owned in
that error creation site to explicitly state which keywords expect `[...]` vs
which are standalone.
| ## Overview | ||
|
|
||
| The `#[light_account(init, mint,...)]` attribute marks a field in an Anchor Accounts struct for compressed mint creation. When applied to a `Mint` account field, it generates code to create a compressed mint with automatic decompression support. | ||
| The `#[light_account(init, mint::...)]` attribute marks a field in an Anchor Accounts struct for compressed mint creation. When applied to a `Mint` account field, it generates code to create a compressed mint with automatic decompression support. |
There was a problem hiding this comment.
Account type reference is outdated.
Line 5 refers to "a Mint account field," but all examples throughout the documentation use UncheckedAccount<'info>, not Account<'info, Mint>. This inconsistency could mislead users about the correct field type to use.
📝 Suggested fix
-The `#[light_account(init, mint::...)]` attribute marks a field in an Anchor Accounts struct for compressed mint creation. When applied to a `Mint` account field, it generates code to create a compressed mint with automatic decompression support.
+The `#[light_account(init, mint::...)]` attribute marks a field in an Anchor Accounts struct for compressed mint creation. When applied to an `UncheckedAccount<'info>` field with the `mint::` namespace, it generates code to create a compressed mint with automatic decompression support.As per coding guidelines, documentation must accurately match the implementation and examples.
📝 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.
| The `#[light_account(init, mint::...)]` attribute marks a field in an Anchor Accounts struct for compressed mint creation. When applied to a `Mint` account field, it generates code to create a compressed mint with automatic decompression support. | |
| The `#[light_account(init, mint::...)]` attribute marks a field in an Anchor Accounts struct for compressed mint creation. When applied to an `UncheckedAccount<'info>` field with the `mint::` namespace, it generates code to create a compressed mint with automatic decompression support. |
🤖 Prompt for AI Agents
In `@sdk-libs/macros/docs/accounts/light_mint.md` at line 5, Update the doc text
to match the actual examples and implementation: replace the phrase "a `Mint`
account field" with "an `UncheckedAccount<'info>` field" (or explicitly state
that the field should be `UncheckedAccount<'info>` when using the
`#[light_account(init, mint::...)]` attribute), and ensure any surrounding
sentences/examples reference `UncheckedAccount<'info>` consistently so the
documentation matches the code paths that generate compressed mint creation and
automatic decompression support.
| } else if is_standalone_keyword(&ident_str) { | ||
| // Standalone keywords (init, token, associated_token, mint) | ||
| // Just continue - these don't require values | ||
| } else { | ||
| // Unknown standalone identifier (not a keyword, not namespace::key) | ||
| return Err(syn::Error::new_spanned( | ||
| &ident, | ||
| format!( | ||
| "Unknown keyword `{}` in #[light_account(...)]. \ | ||
| Use namespaced syntax: `{}::authority = [...]`, `{}::mint`, etc.", | ||
| ident_str, account_type_owned, account_type_owned | ||
| ), | ||
| )); | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Unknown standalone identifier error message could be more actionable.
The error message suggests {account_type}::authority = [...], {account_type}::mint as examples. However, for tokens, authority requires a bracketed array while mint doesn't. The message conflates these patterns which could confuse users.
Suggested improvement
- return Err(syn::Error::new_spanned(
- &ident,
- format!(
- "Unknown keyword `{}` in #[light_account(...)]. \
- Use namespaced syntax: `{}::authority = [...]`, `{}::mint`, etc.",
- ident_str, account_type_owned, account_type_owned
- ),
- ));
+ return Err(syn::Error::new_spanned(
+ &ident,
+ format!(
+ "Unknown keyword `{}` in #[light_account(...)]. \
+ Use namespaced syntax like `{}::key = value`. \
+ Valid keys: {}",
+ ident_str, account_type_owned, valid_keys.join(", ")
+ ),
+ ));🤖 Prompt for AI Agents
In `@sdk-libs/macros/src/light_pdas/account/seed_extraction.rs` around lines 500 -
513, Update the error text produced in the branch where
is_standalone_keyword(&ident_str) is false (the Err created with
syn::Error::new_spanned for ident/ident_str) to give clear, separate examples
for keywords that require a value (e.g., `authority` requires a bracketed array
like `MyAccount::authority = [...]`) versus keywords that are standalone flags
(e.g., `mint` or `init` used as `MyAccount::mint`), referencing
account_type_owned in both examples so users can see the correct namespace
syntax; adjust the message constructed around ident_str/account_type_owned in
that error creation site to explicitly state which keywords expect `[...]` vs
which are standalone.
Converted all attribute parameters to use Anchor-style namespace::key syntax:
Created light_account_keywords.rs module with centralized keyword definitions:
Refactored bump handling to separate from base seeds:
Updated LightAccountArgs parser to handle namespaced syntax:
Extended TokenAccountField struct:
Extended LightMintField struct with:
Modified code generation in generate_token_account_cpi():
Modified code generation in generate_mints_invocation():
Restructured logging system:
Removed entire instruction-decoder and instruction-decoder-derive crates:
Updated light_mint.md documentation: