Skip to content

refactor: light program macro#2200

Merged
SwenSchaeferjohann merged 3 commits intomainfrom
jorrit/fix-macros-bumps
Jan 22, 2026
Merged

refactor: light program macro#2200
SwenSchaeferjohann merged 3 commits intomainfrom
jorrit/fix-macros-bumps

Conversation

@ananas-block
Copy link
Contributor

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


  1. Macro Syntax Refactor - Anchor-Style Namespace Prefix

Converted all attribute parameters to use Anchor-style namespace::key syntax:

  • Token accounts: token::authority, token::mint, token::owner, token::bump
  • Associated tokens: associated_token::authority, associated_token::mint, associated_token::bump
  • Mints: mint::signer, mint::authority, mint::decimals, mint::seeds, mint::bump

  1. New Keyword Validation Module

Created light_account_keywords.rs module with centralized keyword definitions:

  • TOKEN_NAMESPACE_KEYS, ASSOCIATED_TOKEN_NAMESPACE_KEYS, MINT_NAMESPACE_KEYS constants
  • Helper functions: is_standalone_keyword(), is_shorthand_key(), valid_keys_for_namespace()
  • Shared error messages for invalid keywords and missing namespaces

  1. Bump Seed Separation

Refactored bump handling to separate from base seeds:

  • Token accounts: Bumps no longer included in authority_seeds array, can be provided via optional bump parameter or auto-derived
  • Mint accounts: Added mint_bump and authority_bump optional parameters, base seeds no longer include bumps
  • Auto-derivation: When bump parameter not provided, uses Pubkey::find_program_address() to derive

  1. Parser Enhancement

Updated LightAccountArgs parser to handle namespaced syntax:

  • Added NamespacedKeyValue struct replacing KeyValue (includes namespace)
  • Added support for shorthand syntax (e.g., token::mint defaults to token::mint = mint)
  • Enhanced validation with namespace-specific error messages

  1. Token Account Field Expansion

Extended TokenAccountField struct:

  • Added optional bump field for explicit bump specification
  • Updated documentation to clarify bump handling
  • Seeds now exclude bumps by default

  1. Mint Field Expansion

Extended LightMintField struct with:

  • Added optional mint_bump field for explicit mint signer bump
  • Added optional authority_bump field for explicit authority bump
  • Added output_tree field (auto-populated from CreateAccountsProof for correct queue indexing)

  1. Token Account CPI Generation Update

Modified code generation in generate_token_account_cpi():

  • Generates bump derivation code when parameter not provided
  • Appends bump as final seed in signer seeds array
  • Improved seed binding comments to clarify bump handling

  1. Mint CPI Generation Update

Modified code generation in generate_mints_invocation():

  • Handles both explicit and auto-derived bumps for mint and authority seeds
  • Uses output_tree from proof for correct output queue index instead of hardcoded 0
  • Builds seed arrays with bumps appended at code generation time

  1. Program-Test Logging Refactoring

Restructured logging system:

  • Moved instruction-decoder functionality into program-test (decoder.rs, formatter.rs, types.rs, config.rs)
  • Removed dependency on external instruction-decoder crate
  • Integrated logging directly into program-test module with inline decoder implementation

  1. Instruction-Decoder Crate Removal

Removed entire instruction-decoder and instruction-decoder-derive crates:

  • Decoding/formatting logic migrated to program-test/src/logging/
  • Eliminated dependency for simplified monorepo structure

  1. Documentation Update

Updated light_mint.md documentation:

  • Changed syntax examples from old format to new namespace prefix format
  • Updated source reference to new file location
  • Added comprehensive syntax examples for all account types

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 22, 2026

📝 Walkthrough

Walkthrough

This 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

Cohort / File(s) Summary
Docs & Examples
sdk-libs/macros/docs/accounts/light_mint.md
Updated examples to use mint::... namespaced keys and changed mint account fields from Account<'info, Mint> to UncheckedAccount<'info>.
Keyword Validation & Module Export
sdk-libs/macros/src/light_pdas/light_account_keywords.rs, sdk-libs/macros/src/light_pdas/mod.rs
New module with namespace-specific key sets, shorthand rules, validation helpers, and error constructors; exported from mod.rs.
Attribute Parsing (namespace-aware)
sdk-libs/macros/src/light_pdas/accounts/light_account.rs
Replaced flat KeyValue parsing with NamespacedKeyValue; added namespace inference/validation, mark-only vs init-mode rules, expanded error messages; TokenAccountField/AtaField now include optional bump fields; added LightAccountType::namespace().
Seed Extraction & ATA Detection
sdk-libs/macros/src/light_pdas/account/seed_extraction.rs
check_light_account_type now detects (has_pda, has_mint, has_ata); ExtractedAccountsInfo tracks has_light_ata_fields; token attr parsing now namespace-aware; unit tests added.
Validation / Proof Requirement
sdk-libs/macros/src/light_pdas/accounts/builder.rs
Added validate_create_accounts_proof to require CreateAccountsProof when init fields (PDAs/mints) exist; invoked in validate.
Mint & Token Seed/Bump Handling
sdk-libs/macros/src/light_pdas/accounts/mint.rs, .../token.rs
LightMintField gains output_tree, mint_bump, authority_bump; codegen derives bumps via find_program_address when absent and appends bumps to seed arrays; token account CPI generation likewise derives/uses bump and constructs __bump_slice for signer seeds.
Codegen & Instruction Flow
sdk-libs/macros/src/light_pdas/accounts/derive.rs, .../program/instructions.rs, .../program/parsing.rs
Tests/derive examples switched to namespaced keys; instruction codegen accepts has_ata_fields and InstructionVariant added AtaOnly; error messaging updated to reference namespaced keys.

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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • SwenSchaeferjohann

Poem

🌱 Namespaces whisper: "mint::, token::, ata",
Seeds find bumps where signatures play,
Parser learns the namespace song,
Validator sings errors strong,
Codegen stitches bumps and builds the day.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Title check ⚠️ Warning The title 'refactor: light program macro' is too vague and generic. It fails to convey the specific and substantive changes made to bump handling, keyword validation, proof validation, and account type requirements. Use a more descriptive title that captures the main intent, such as: 'refactor: separate bump handling and add CreateAccountsProof validation in light program macro' or 'refactor: enhance light program macro with namespace-aware keywords and bump management'
✅ 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/fix-macros-bumps

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

❤️ Share

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

@ananas-block
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 22, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

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_context injects __output_tree_index, but __output_queue_index always uses mints[0].output_tree, leaving __output_tree_index unused and potentially ignoring the caller’s tree expression. This can trip #![deny(warnings)] in generated code and breaks the intent of with_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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

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

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.

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

Comment on lines +500 to 513
} 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
),
));
}
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

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.

@SwenSchaeferjohann SwenSchaeferjohann merged commit ef9390b into main Jan 22, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants