Skip to content

Adding ShankAccounts Macro#76

Open
blockiosaurus wants to merge 2 commits intomasterfrom
feat/shank-accounts
Open

Adding ShankAccounts Macro#76
blockiosaurus wants to merge 2 commits intomasterfrom
feat/shank-accounts

Conversation

@blockiosaurus
Copy link
Contributor

This PR adds a ShankAccounts macro to give the option of sourcing Shank's IDL generation from a struct of AccountInfos rather than the current syntax which can make Instruction enums overly long. The account struct also has an autogenerated context() function which will turn a slice of AccountInfos into the struct.

#[derive(ShankAccounts)]
pub struct CreateVaultAccounts<'info> {
    #[account(mut, signer, desc = "The payer and authority")]
    pub payer: &'info AccountInfo<'info>,

    #[account(mut, desc = "The vault account to create")]
    pub vault: &'info AccountInfo<'info>,

    #[account(optional, desc = "Optional new authority")]
    pub optional_authority: Option<&'info AccountInfo<'info>>,

    #[account(desc = "System program")]
    pub system_program: &'info AccountInfo<'info>,
}

@coderabbitai
Copy link

coderabbitai bot commented Sep 19, 2025

Summary by CodeRabbit

Release Notes

  • New Features

    • Added ShankAccounts derive macro for defining typed account groups with support for optional accounts and account constraints (writable, signer, optional_signer)
    • Generated context() method for type-safe account access with automatic validation
    • Enhanced IDL generation to support struct-based account definitions alongside inline attributes
  • Documentation

    • Added comprehensive examples and guides demonstrating optional accounts, context usage, and Anchor-style patterns

Walkthrough

Adds ShankAccounts derive support, expands IDL generation to resolve #[accounts(StructName)] by extracting struct field metadata, generates context() constructors and a generic Context type, wires macro and idl changes across crates, and adds examples plus extensive tests and fixtures.

Changes

Cohort / File(s) Summary
Docs
EXAMPLE_USAGE.md, shank-idl/ACCOUNTS_STRUCT_IDL.md
New usage docs and IDL behavior doc describing ShankAccounts, optional accounts pattern, and current vs intended IDL expansion behavior.
IDL integration
shank-idl/src/accounts_extraction.rs, shank-idl/src/file.rs, shank-idl/src/idl_instruction.rs, shank-idl/src/lib.rs
New accounts extraction module; collect ShankAccounts into a HashMap and pass into instruction→IDL conversion; IdlInstruction/IdlInstructions gain accounts-aware conversion with struct expansion and placeholder fallback.
IDL fixtures & tests
shank-idl/tests/fixtures/instructions/single_file/*.rs, .../*.json, shank-idl/tests/instructions.rs, shank-idl/tests/generate_expected_idl.rs, shank-idl/tests/test_shank_accounts_expansion.rs
Adds Rust/JSON fixtures and tests to validate IDL outputs for simple, accounts-struct, and complex scenarios; includes ignored generators and an expansion check test.
Macro: accounts derive implementation
shank-macro-impl/src/accounts/account_field.rs, .../account_struct.rs, .../mod.rs, shank-macro-impl/src/lib.rs
New Accounts parsing: AccountField and AccountStruct types; parse #[account(...)] aliases, descriptions, optional/optional_signer; generate __shank_accounts() metadata and context() when lifetimes present; export new DERIVE_ACCOUNTS_ATTR and accounts module.
Macro: instruction attributes & variants
shank-macro-impl/src/instruction/account_attrs.rs, shank-macro-impl/src/instruction/instruction.rs, shank-macro-impl/src/instruction/idl_instruction_attrs.rs
Add AccountsSource enum (Inline
Macro crate API
shank-macro/src/accounts.rs, shank-macro/src/lib.rs
Adds derive_accounts wrapper and public proc-macro ShankAccounts; updates ShankInstruction derive to accept accounts attribute and re-exports accounts path.
Core: Context & traits
shank/src/context.rs, shank/src/lib.rs
Adds generic Context<'a, T, U=()> wrapper and AccountInfoRef trait; re-exports Context.
Examples
shank/examples/*.rs (accounts_struct_example, anchor_style_example, context_usage_example, full_solana_example)
New examples demonstrating ShankAccounts, optional accounts, generated context() usage, Anchor-style patterns, and feature-gated Solana integration with mocks.
Workspace/features
shank/Cargo.toml
Adds optional solana-program dependency behind a feature and macrotest dev-dependency; default features empty.
Shank tests (macro expansion & behavior)
shank/tests/*, shank/tests/expand/*, shank/tests/expand_tests.rs
Large set of new tests and macrotest expansions exercising __shank_accounts(), context(), optional handling, lifetimes, and many integration scenarios across many test files.
Formatting/Minor edits
shank-idl/tests/accounts.rs, shank-macro-impl/src/parsed_struct/struct_field_attr.rs
Minor formatting/refactor tweaks with no behavioral change.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Parser as Crate AST Parser
  participant Extract as extract_shank_accounts_structs
  participant Map as Accounts Map (HashMap)
  participant InstrParser as Instruction Parser
  participant IDLConv as IdlInstruction::try_from_with_accounts

  Parser->>Extract: iterate ItemStructs
  Extract->>Map: insert StructName => Vec<InstructionAccount>
  InstrParser->>IDLConv: convert variant with accounts_map
  alt Inline accounts on variant
    IDLConv->>IDLConv: map inline InstructionAccounts → IdlAccountItems
  else Struct reference on variant
    IDLConv->>Map: lookup StructName
    alt found
      Map-->>IDLConv: Vec<InstructionAccount>
      IDLConv->>IDLConv: expand to IdlAccountItems
    else not found
      IDLConv->>IDLConv: emit placeholder IdlAccountItem with docs
    end
  end
  IDLConv-->>InstrParser: IdlInstruction(s)
Loading
sequenceDiagram
  autonumber
  participant Caller as Program code
  participant Gen as Generated context() (from ShankAccounts)
  participant Ctx as Context<'a, T, U>

  Caller->>Gen: call context(&[AccountInfo])
  Gen->>Gen: validate min accounts length
  Gen->>Gen: for each optional field: if account.key == crate::ID => None else Some(&acc[i])
  Gen->>Ctx: construct Context { accounts: T, remaining_accounts }
  Ctx-->>Caller: typed ctx.accounts and remaining_accounts
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Areas needing extra attention:

  • Macro codegen: shank-macro-impl/src/accounts/* and mod.rs — correctness of attribute parsing, alias handling, and generated TokenStream (context signature, lifetimes).
  • IDL expansion wiring: shank-idl/src/accounts_extraction.rs and idl_instruction.rs — resolving Path to struct names, mapping metadata into IdlAccountItems, and placeholder fallback.
  • Interaction between proc-macro outputs and idl extractor (ABI of __shank_accounts()) — ensure tuple layout and stability matches idl parsing expectations.
  • Tests & fixtures: many new tests and generated snapshots; verify feature-gated paths (solana-program off/on) and macrotest outputs.

Suggested reviewers

  • pileks
  • danenbm

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Adding ShankAccounts Macro' directly and clearly describes the main feature being introduced - a new ShankAccounts procedural macro.
Description check ✅ Passed The description clearly explains the purpose of the ShankAccounts macro, shows a concrete example, and describes the key benefit (reducing verbosity in instruction enum definitions through an alternative account-sourcing pattern).
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ 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 feat/shank-accounts

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

Copy link

@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: 81

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
shank-idl/src/idl_instruction.rs (1)

264-273: Same off-by-one in the accounts-aware path.

Mirror the fix in try_from_with_accounts.

-        ensure!(
-            discriminant < u8::MAX as usize,
+        ensure!(
+            discriminant <= u8::MAX as usize,
             anyhow!(
                 "Instruction variant discriminants have to be <= u8::MAX ({}), \
                     but the discriminant of variant '{}' is {}",
                 u8::MAX,
                 ident,
                 discriminant
             )
         );
shank-macro-impl/src/instruction/account_attrs.rs (1)

222-249: Index-position validation is good; consider duplicate-name detection.

Multiple inline accounts with the same name should error early to avoid confusing IDL.

If desired, I can add a small pass to check for duplicate InstructionAccount.name values and return a ParseError.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5e1caff and 10aa392.

📒 Files selected for processing (61)
  • EXAMPLE_USAGE.md (1 hunks)
  • shank-idl/ACCOUNTS_STRUCT_IDL.md (1 hunks)
  • shank-idl/src/accounts_extraction.rs (1 hunks)
  • shank-idl/src/file.rs (3 hunks)
  • shank-idl/src/idl_instruction.rs (4 hunks)
  • shank-idl/src/lib.rs (1 hunks)
  • shank-idl/tests/fixtures/instructions/single_file/instruction_with_accounts_struct.json (1 hunks)
  • shank-idl/tests/fixtures/instructions/single_file/instruction_with_accounts_struct.rs (1 hunks)
  • shank-idl/tests/fixtures/instructions/single_file/instruction_with_complex_accounts_struct.json (1 hunks)
  • shank-idl/tests/fixtures/instructions/single_file/instruction_with_complex_accounts_struct.rs (1 hunks)
  • shank-idl/tests/fixtures/instructions/single_file/instruction_with_simple_accounts_struct.json (1 hunks)
  • shank-idl/tests/fixtures/instructions/single_file/instruction_with_simple_accounts_struct.rs (1 hunks)
  • shank-idl/tests/generate_expected_idl.rs (1 hunks)
  • shank-idl/tests/instructions.rs (1 hunks)
  • shank-idl/tests/test_shank_accounts_expansion.rs (1 hunks)
  • shank-macro-impl/src/accounts/account_field.rs (1 hunks)
  • shank-macro-impl/src/accounts/account_struct.rs (1 hunks)
  • shank-macro-impl/src/accounts/mod.rs (1 hunks)
  • shank-macro-impl/src/instruction/account_attrs.rs (3 hunks)
  • shank-macro-impl/src/instruction/instruction.rs (3 hunks)
  • shank-macro-impl/src/lib.rs (2 hunks)
  • shank-macro/src/accounts.rs (1 hunks)
  • shank-macro/src/lib.rs (4 hunks)
  • shank/Cargo.toml (1 hunks)
  • shank/examples/accounts_struct_example.rs (1 hunks)
  • shank/examples/anchor_style_example.rs (1 hunks)
  • shank/examples/context_usage_example.rs (1 hunks)
  • shank/examples/full_solana_example.rs (1 hunks)
  • shank/src/context.rs (1 hunks)
  • shank/src/lib.rs (1 hunks)
  • shank/tests/accounts_macro_test.rs (1 hunks)
  • shank/tests/basic_context_test.rs (1 hunks)
  • shank/tests/context_api_test.rs (1 hunks)
  • shank/tests/context_api_working_test.rs (1 hunks)
  • shank/tests/context_generation_test.rs (1 hunks)
  • shank/tests/context_issue_demo.rs (1 hunks)
  • shank/tests/context_method_demo.rs (1 hunks)
  • shank/tests/context_method_test.rs (1 hunks)
  • shank/tests/context_runtime_test.rs (1 hunks)
  • shank/tests/context_usage_test.rs (1 hunks)
  • shank/tests/enhanced_accounts_test.rs (1 hunks)
  • shank/tests/error_handling_test.rs (1 hunks)
  • shank/tests/expand/basic_accounts.expanded.rs (1 hunks)
  • shank/tests/expand/basic_accounts.rs (1 hunks)
  • shank/tests/expand/complex_constraints.expanded.rs (1 hunks)
  • shank/tests/expand/complex_constraints.rs (1 hunks)
  • shank/tests/expand/custom_lifetime.expanded.rs (1 hunks)
  • shank/tests/expand/custom_lifetime.rs (1 hunks)
  • shank/tests/expand/empty_struct.expanded.rs (1 hunks)
  • shank/tests/expand/empty_struct.rs (1 hunks)
  • shank/tests/expand/no_constraints.expanded.rs (1 hunks)
  • shank/tests/expand/no_constraints.rs (1 hunks)
  • shank/tests/expand/optional_accounts.expanded.rs (1 hunks)
  • shank/tests/expand/optional_accounts.rs (1 hunks)
  • shank/tests/expand_tests.rs (1 hunks)
  • shank/tests/final_context_test.rs (1 hunks)
  • shank/tests/idl_generation_test.rs (1 hunks)
  • shank/tests/instruction_integration_test.rs (1 hunks)
  • shank/tests/instruction_with_accounts_struct_test.rs (1 hunks)
  • shank/tests/simple_context_test.rs (1 hunks)
  • shank/tests/working_context_demo.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/tests/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place test files and fixtures under each crate’s tests/ directory

Files:

  • shank-idl/tests/fixtures/instructions/single_file/instruction_with_accounts_struct.json
  • shank/tests/expand_tests.rs
  • shank-idl/tests/fixtures/instructions/single_file/instruction_with_complex_accounts_struct.json
  • shank/tests/expand/empty_struct.rs
  • shank-idl/tests/fixtures/instructions/single_file/instruction_with_simple_accounts_struct.json
  • shank-idl/tests/generate_expected_idl.rs
  • shank/tests/expand/empty_struct.expanded.rs
  • shank/tests/expand/basic_accounts.rs
  • shank-idl/tests/test_shank_accounts_expansion.rs
  • shank/tests/working_context_demo.rs
  • shank-idl/tests/instructions.rs
  • shank/tests/expand/complex_constraints.rs
  • shank/tests/basic_context_test.rs
  • shank/tests/simple_context_test.rs
  • shank/tests/expand/custom_lifetime.rs
  • shank/tests/context_method_demo.rs
  • shank/tests/instruction_with_accounts_struct_test.rs
  • shank/tests/expand/basic_accounts.expanded.rs
  • shank/tests/final_context_test.rs
  • shank/tests/expand/optional_accounts.rs
  • shank/tests/expand/complex_constraints.expanded.rs
  • shank/tests/idl_generation_test.rs
  • shank-idl/tests/fixtures/instructions/single_file/instruction_with_accounts_struct.rs
  • shank/tests/expand/no_constraints.rs
  • shank/tests/context_runtime_test.rs
  • shank/tests/error_handling_test.rs
  • shank/tests/context_api_working_test.rs
  • shank/tests/instruction_integration_test.rs
  • shank/tests/accounts_macro_test.rs
  • shank/tests/context_method_test.rs
  • shank/tests/expand/optional_accounts.expanded.rs
  • shank/tests/expand/custom_lifetime.expanded.rs
  • shank/tests/context_generation_test.rs
  • shank/tests/context_api_test.rs
  • shank/tests/context_issue_demo.rs
  • shank/tests/context_usage_test.rs
  • shank-idl/tests/fixtures/instructions/single_file/instruction_with_complex_accounts_struct.rs
  • shank/tests/enhanced_accounts_test.rs
  • shank-idl/tests/fixtures/instructions/single_file/instruction_with_simple_accounts_struct.rs
  • shank/tests/expand/no_constraints.expanded.rs
**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.rs: Format all Rust source files according to rustfmt.toml
Use #[derive(ShankAccount)] to mark account structs; optionally annotate with #[seeds] for PDA generation
Use #[derive(ShankInstruction)] to mark instruction enums; use #[account] attributes on fields as needed
Use #[derive(ShankType)] to mark custom types for IDL inclusion
Use #[derive(ShankBuilder)] to generate instruction builders
Use #[derive(ShankContext)] to generate account context structs
Use #[padding] on fields to mark padding in the generated IDL
Use #[idl_type("TypeName")] to override a field’s IDL type
Use #[idl_name("name")] to rename a field in the IDL while keeping the Rust field name
Use #[skip] on fields to exclude them from the generated IDL

Files:

  • shank/tests/expand_tests.rs
  • shank-macro-impl/src/lib.rs
  • shank/tests/expand/empty_struct.rs
  • shank/src/context.rs
  • shank/src/lib.rs
  • shank-macro/src/accounts.rs
  • shank-idl/tests/generate_expected_idl.rs
  • shank-macro-impl/src/accounts/account_struct.rs
  • shank/tests/expand/empty_struct.expanded.rs
  • shank/tests/expand/basic_accounts.rs
  • shank-idl/src/lib.rs
  • shank-idl/tests/test_shank_accounts_expansion.rs
  • shank-idl/src/accounts_extraction.rs
  • shank/tests/working_context_demo.rs
  • shank-idl/tests/instructions.rs
  • shank/examples/accounts_struct_example.rs
  • shank/tests/expand/complex_constraints.rs
  • shank/examples/context_usage_example.rs
  • shank/tests/basic_context_test.rs
  • shank/tests/simple_context_test.rs
  • shank/tests/expand/custom_lifetime.rs
  • shank/tests/context_method_demo.rs
  • shank/tests/instruction_with_accounts_struct_test.rs
  • shank/tests/expand/basic_accounts.expanded.rs
  • shank-macro-impl/src/accounts/mod.rs
  • shank/tests/final_context_test.rs
  • shank/tests/expand/optional_accounts.rs
  • shank-macro-impl/src/instruction/instruction.rs
  • shank/tests/expand/complex_constraints.expanded.rs
  • shank/tests/idl_generation_test.rs
  • shank-idl/tests/fixtures/instructions/single_file/instruction_with_accounts_struct.rs
  • shank/tests/expand/no_constraints.rs
  • shank/tests/context_runtime_test.rs
  • shank/tests/error_handling_test.rs
  • shank-macro-impl/src/accounts/account_field.rs
  • shank/tests/context_api_working_test.rs
  • shank/tests/instruction_integration_test.rs
  • shank-idl/src/file.rs
  • shank/tests/accounts_macro_test.rs
  • shank/tests/context_method_test.rs
  • shank/tests/expand/optional_accounts.expanded.rs
  • shank/tests/expand/custom_lifetime.expanded.rs
  • shank-idl/src/idl_instruction.rs
  • shank/tests/context_generation_test.rs
  • shank/tests/context_api_test.rs
  • shank/tests/context_issue_demo.rs
  • shank/tests/context_usage_test.rs
  • shank-idl/tests/fixtures/instructions/single_file/instruction_with_complex_accounts_struct.rs
  • shank/examples/anchor_style_example.rs
  • shank/tests/enhanced_accounts_test.rs
  • shank-idl/tests/fixtures/instructions/single_file/instruction_with_simple_accounts_struct.rs
  • shank-macro-impl/src/instruction/account_attrs.rs
  • shank/examples/full_solana_example.rs
  • shank-macro/src/lib.rs
  • shank/tests/expand/no_constraints.expanded.rs
**/Cargo.toml

📄 CodeRabbit inference engine (CLAUDE.md)

Set Rust edition to 2018 in all crates (edition = "2018" in Cargo.toml)

Files:

  • shank/Cargo.toml
🧬 Code graph analysis (49)
shank-macro-impl/src/lib.rs (1)
shank-idl/src/file.rs (1)
  • accounts (99-108)
shank/src/lib.rs (1)
shank/tests/context_api_working_test.rs (1)
  • key (14-16)
shank-macro/src/accounts.rs (1)
shank-macro-impl/src/accounts/mod.rs (1)
  • try_from (21-48)
shank-idl/tests/generate_expected_idl.rs (2)
shank-idl/src/file.rs (2)
  • parse_file (64-97)
  • optional_program_address (51-56)
shank-idl/src/idl.rs (1)
  • try_into_json (64-67)
shank-macro-impl/src/accounts/account_struct.rs (1)
shank-macro-impl/src/accounts/account_field.rs (1)
  • from_field (15-76)
shank/tests/expand/empty_struct.expanded.rs (5)
shank/tests/expand/basic_accounts.expanded.rs (1)
  • __shank_accounts (17-52)
shank/tests/expand/complex_constraints.expanded.rs (1)
  • __shank_accounts (23-85)
shank/tests/expand/custom_lifetime.expanded.rs (1)
  • __shank_accounts (15-41)
shank/tests/expand/no_constraints.expanded.rs (1)
  • __shank_accounts (14-25)
shank/tests/expand/optional_accounts.expanded.rs (1)
  • __shank_accounts (17-52)
shank/tests/expand/basic_accounts.rs (2)
shank/examples/accounts_struct_example.rs (1)
  • main (103-150)
shank/examples/anchor_style_example.rs (2)
  • main (113-127)
  • main (130-132)
shank-idl/tests/test_shank_accounts_expansion.rs (2)
shank-idl/src/lib.rs (2)
  • extract_idl (44-63)
  • default (32-38)
shank-idl/src/file.rs (3)
  • default (39-47)
  • instructions (110-130)
  • accounts (99-108)
shank-idl/src/accounts_extraction.rs (3)
shank-macro-impl/src/parsers/attrs.rs (1)
  • get_derive_attr (74-79)
shank-idl/src/file.rs (1)
  • accounts (99-108)
shank-idl/src/idl_field.rs (1)
  • field (35-39)
shank/tests/working_context_demo.rs (3)
shank/examples/accounts_struct_example.rs (1)
  • main (103-150)
shank/examples/context_usage_example.rs (2)
  • main (95-113)
  • main (116-119)
shank/tests/expand/basic_accounts.expanded.rs (2)
  • main (54-54)
  • __shank_accounts (17-52)
shank-idl/tests/instructions.rs (1)
shank-idl/src/file.rs (2)
  • parse_file (64-97)
  • optional_program_address (51-56)
shank/examples/accounts_struct_example.rs (4)
shank-idl/src/file.rs (1)
  • accounts (99-108)
shank/examples/anchor_style_example.rs (2)
  • main (113-127)
  • main (130-132)
shank/examples/context_usage_example.rs (2)
  • main (95-113)
  • main (116-119)
shank/examples/full_solana_example.rs (2)
  • main (131-142)
  • main (145-159)
shank/tests/expand/complex_constraints.rs (2)
shank/examples/accounts_struct_example.rs (1)
  • main (103-150)
shank/examples/anchor_style_example.rs (2)
  • main (113-127)
  • main (130-132)
shank/examples/context_usage_example.rs (3)
shank/examples/accounts_struct_example.rs (1)
  • main (103-150)
shank/examples/anchor_style_example.rs (2)
  • main (113-127)
  • main (130-132)
shank/examples/full_solana_example.rs (2)
  • main (131-142)
  • main (145-159)
shank/tests/basic_context_test.rs (2)
shank/src/lib.rs (1)
  • key (10-10)
shank/tests/context_api_working_test.rs (1)
  • key (14-16)
shank/tests/simple_context_test.rs (1)
shank/tests/expand/basic_accounts.expanded.rs (1)
  • __shank_accounts (17-52)
shank/tests/expand/custom_lifetime.rs (3)
shank/examples/accounts_struct_example.rs (1)
  • main (103-150)
shank/examples/anchor_style_example.rs (2)
  • main (113-127)
  • main (130-132)
shank/examples/full_solana_example.rs (2)
  • main (131-142)
  • main (145-159)
shank/tests/context_method_demo.rs (1)
shank/tests/expand/basic_accounts.expanded.rs (1)
  • __shank_accounts (17-52)
shank/tests/instruction_with_accounts_struct_test.rs (1)
shank-idl/src/file.rs (1)
  • accounts (99-108)
shank/tests/expand/basic_accounts.expanded.rs (5)
shank/tests/expand/complex_constraints.expanded.rs (1)
  • __shank_accounts (23-85)
shank/tests/expand/custom_lifetime.expanded.rs (1)
  • __shank_accounts (15-41)
shank/tests/expand/empty_struct.expanded.rs (1)
  • __shank_accounts (5-9)
shank/tests/expand/no_constraints.expanded.rs (1)
  • __shank_accounts (14-25)
shank/tests/expand/optional_accounts.expanded.rs (1)
  • __shank_accounts (17-52)
shank-macro-impl/src/accounts/mod.rs (3)
shank-macro-impl/src/accounts/account_struct.rs (1)
  • from_fields (12-23)
shank/src/lib.rs (1)
  • key (10-10)
shank/tests/context_api_working_test.rs (1)
  • key (14-16)
shank/tests/final_context_test.rs (2)
shank/src/lib.rs (1)
  • key (10-10)
shank/tests/context_api_working_test.rs (1)
  • key (14-16)
shank/tests/expand/optional_accounts.rs (3)
shank/examples/accounts_struct_example.rs (1)
  • main (103-150)
shank/examples/anchor_style_example.rs (2)
  • main (113-127)
  • main (130-132)
shank/examples/full_solana_example.rs (2)
  • main (131-142)
  • main (145-159)
shank-macro-impl/src/instruction/instruction.rs (2)
shank-idl/src/idl_instruction.rs (2)
  • try_from (22-29)
  • try_from (67-165)
shank-macro-impl/src/instruction/account_attrs.rs (3)
  • try_from (193-219)
  • try_from (225-248)
  • attrs (226-230)
shank/tests/expand/complex_constraints.expanded.rs (4)
shank/tests/expand/basic_accounts.expanded.rs (1)
  • __shank_accounts (17-52)
shank/tests/expand/custom_lifetime.expanded.rs (1)
  • __shank_accounts (15-41)
shank/tests/expand/no_constraints.expanded.rs (1)
  • __shank_accounts (14-25)
shank/tests/expand/optional_accounts.expanded.rs (1)
  • __shank_accounts (17-52)
shank/tests/idl_generation_test.rs (6)
shank/tests/expand/basic_accounts.expanded.rs (1)
  • __shank_accounts (17-52)
shank/tests/expand/complex_constraints.expanded.rs (1)
  • __shank_accounts (23-85)
shank/tests/expand/custom_lifetime.expanded.rs (1)
  • __shank_accounts (15-41)
shank/tests/expand/empty_struct.expanded.rs (1)
  • __shank_accounts (5-9)
shank/tests/expand/no_constraints.expanded.rs (1)
  • __shank_accounts (14-25)
shank/tests/expand/optional_accounts.expanded.rs (1)
  • __shank_accounts (17-52)
shank-idl/tests/fixtures/instructions/single_file/instruction_with_accounts_struct.rs (1)
shank-idl/src/file.rs (1)
  • accounts (99-108)
shank/tests/expand/no_constraints.rs (2)
shank/examples/accounts_struct_example.rs (1)
  • main (103-150)
shank/examples/anchor_style_example.rs (2)
  • main (113-127)
  • main (130-132)
shank/tests/context_runtime_test.rs (6)
shank/tests/expand/basic_accounts.expanded.rs (1)
  • __shank_accounts (17-52)
shank/tests/expand/complex_constraints.expanded.rs (1)
  • __shank_accounts (23-85)
shank/tests/expand/custom_lifetime.expanded.rs (1)
  • __shank_accounts (15-41)
shank/tests/expand/empty_struct.expanded.rs (1)
  • __shank_accounts (5-9)
shank/tests/expand/no_constraints.expanded.rs (1)
  • __shank_accounts (14-25)
shank/tests/expand/optional_accounts.expanded.rs (1)
  • __shank_accounts (17-52)
shank/tests/error_handling_test.rs (7)
shank/tests/expand/basic_accounts.expanded.rs (1)
  • __shank_accounts (17-52)
shank/tests/expand/complex_constraints.expanded.rs (1)
  • __shank_accounts (23-85)
shank/tests/expand/custom_lifetime.expanded.rs (1)
  • __shank_accounts (15-41)
shank/tests/expand/empty_struct.expanded.rs (1)
  • __shank_accounts (5-9)
shank/tests/expand/no_constraints.expanded.rs (1)
  • __shank_accounts (14-25)
shank/tests/expand/optional_accounts.expanded.rs (1)
  • __shank_accounts (17-52)
shank/tests/context_api_test.rs (1)
  • test_different_lifetime_names (83-97)
shank/tests/context_api_working_test.rs (1)
shank/src/lib.rs (1)
  • key (10-10)
shank/tests/instruction_integration_test.rs (6)
shank/tests/expand/basic_accounts.expanded.rs (1)
  • __shank_accounts (17-52)
shank/tests/expand/complex_constraints.expanded.rs (1)
  • __shank_accounts (23-85)
shank/tests/expand/custom_lifetime.expanded.rs (1)
  • __shank_accounts (15-41)
shank/tests/expand/empty_struct.expanded.rs (1)
  • __shank_accounts (5-9)
shank/tests/expand/no_constraints.expanded.rs (1)
  • __shank_accounts (14-25)
shank/tests/expand/optional_accounts.expanded.rs (1)
  • __shank_accounts (17-52)
shank-idl/src/file.rs (2)
shank-idl/src/accounts_extraction.rs (1)
  • extract_shank_accounts_structs (12-26)
shank-idl/src/idl_instruction.rs (3)
  • try_into_with_accounts (33-43)
  • ix (23-27)
  • ix (37-41)
shank/tests/context_method_test.rs (3)
shank/tests/expand/basic_accounts.expanded.rs (1)
  • __shank_accounts (17-52)
shank/tests/expand/optional_accounts.expanded.rs (1)
  • __shank_accounts (17-52)
shank-macro-impl/src/parsed_struct/struct_attr.rs (1)
  • len (81-83)
shank/tests/expand/optional_accounts.expanded.rs (5)
shank/tests/expand/basic_accounts.expanded.rs (1)
  • __shank_accounts (17-52)
shank/tests/expand/complex_constraints.expanded.rs (1)
  • __shank_accounts (23-85)
shank/tests/expand/custom_lifetime.expanded.rs (1)
  • __shank_accounts (15-41)
shank/tests/expand/empty_struct.expanded.rs (1)
  • __shank_accounts (5-9)
shank/tests/expand/no_constraints.expanded.rs (1)
  • __shank_accounts (14-25)
shank/tests/expand/custom_lifetime.expanded.rs (3)
shank/tests/expand/basic_accounts.expanded.rs (1)
  • __shank_accounts (17-52)
shank/tests/expand/empty_struct.expanded.rs (1)
  • __shank_accounts (5-9)
shank/tests/expand/no_constraints.expanded.rs (1)
  • __shank_accounts (14-25)
shank-idl/src/idl_instruction.rs (3)
shank-idl/src/file.rs (2)
  • instructions (110-130)
  • accounts (99-108)
shank-macro-impl/src/instruction/account_attrs.rs (2)
  • try_from (193-219)
  • try_from (225-248)
shank-macro-impl/src/instruction/instruction.rs (3)
  • try_from (50-57)
  • try_from (63-76)
  • try_from (101-177)
shank/tests/context_generation_test.rs (6)
shank/tests/expand/basic_accounts.expanded.rs (1)
  • __shank_accounts (17-52)
shank/tests/expand/complex_constraints.expanded.rs (1)
  • __shank_accounts (23-85)
shank/tests/expand/custom_lifetime.expanded.rs (1)
  • __shank_accounts (15-41)
shank/tests/expand/empty_struct.expanded.rs (1)
  • __shank_accounts (5-9)
shank/tests/expand/no_constraints.expanded.rs (1)
  • __shank_accounts (14-25)
shank/tests/expand/optional_accounts.expanded.rs (1)
  • __shank_accounts (17-52)
shank/tests/context_api_test.rs (7)
shank/tests/expand/basic_accounts.expanded.rs (1)
  • __shank_accounts (17-52)
shank/tests/expand/complex_constraints.expanded.rs (1)
  • __shank_accounts (23-85)
shank/tests/expand/custom_lifetime.expanded.rs (1)
  • __shank_accounts (15-41)
shank/tests/expand/empty_struct.expanded.rs (1)
  • __shank_accounts (5-9)
shank/tests/expand/no_constraints.expanded.rs (1)
  • __shank_accounts (14-25)
shank/tests/expand/optional_accounts.expanded.rs (1)
  • __shank_accounts (17-52)
shank/tests/error_handling_test.rs (1)
  • test_different_lifetime_names (175-196)
shank/tests/context_issue_demo.rs (2)
shank/tests/expand/basic_accounts.expanded.rs (1)
  • __shank_accounts (17-52)
shank/tests/expand/optional_accounts.expanded.rs (1)
  • __shank_accounts (17-52)
shank/tests/context_usage_test.rs (4)
shank/src/lib.rs (1)
  • key (10-10)
shank/tests/context_api_working_test.rs (1)
  • key (14-16)
shank/tests/expand/basic_accounts.expanded.rs (1)
  • __shank_accounts (17-52)
shank/tests/expand/complex_constraints.expanded.rs (1)
  • __shank_accounts (23-85)
shank-idl/tests/fixtures/instructions/single_file/instruction_with_complex_accounts_struct.rs (1)
shank-idl/src/file.rs (1)
  • accounts (99-108)
shank/examples/anchor_style_example.rs (4)
shank-idl/src/file.rs (3)
  • accounts (99-108)
  • ctx (147-151)
  • ctx (153-157)
shank/examples/accounts_struct_example.rs (1)
  • main (103-150)
shank/examples/context_usage_example.rs (2)
  • main (95-113)
  • main (116-119)
shank/examples/full_solana_example.rs (2)
  • main (131-142)
  • main (145-159)
shank/tests/enhanced_accounts_test.rs (1)
shank-idl/src/file.rs (1)
  • accounts (99-108)
shank-idl/tests/fixtures/instructions/single_file/instruction_with_simple_accounts_struct.rs (1)
shank-idl/src/file.rs (1)
  • accounts (99-108)
shank-macro-impl/src/instruction/account_attrs.rs (2)
shank-idl/src/idl_instruction.rs (2)
  • try_from (22-29)
  • try_from (67-165)
shank-macro-impl/src/instruction/instruction.rs (3)
  • try_from (50-57)
  • try_from (63-76)
  • try_from (101-177)
shank/examples/full_solana_example.rs (6)
shank-idl/src/file.rs (3)
  • accounts (99-108)
  • ctx (147-151)
  • ctx (153-157)
shank/src/lib.rs (1)
  • key (10-10)
shank/tests/context_api_working_test.rs (2)
  • key (14-16)
  • main (116-118)
shank/examples/accounts_struct_example.rs (1)
  • main (103-150)
shank/examples/anchor_style_example.rs (2)
  • main (113-127)
  • main (130-132)
shank/examples/context_usage_example.rs (2)
  • main (95-113)
  • main (116-119)
shank-macro/src/lib.rs (2)
shank-idl/src/file.rs (1)
  • accounts (99-108)
shank-macro/src/accounts.rs (1)
  • derive_accounts (6-9)
shank/tests/expand/no_constraints.expanded.rs (3)
shank/tests/expand/basic_accounts.expanded.rs (1)
  • __shank_accounts (17-52)
shank/tests/expand/complex_constraints.expanded.rs (1)
  • __shank_accounts (23-85)
shank/tests/expand/empty_struct.expanded.rs (1)
  • __shank_accounts (5-9)
🪛 markdownlint-cli2 (0.18.1)
shank-idl/ACCOUNTS_STRUCT_IDL.md

130-130: Files should end with a single newline character

(MD047, single-trailing-newline)

🔇 Additional comments (68)
shank/Cargo.toml (2)

13-20: Feature-gated Solana dep looks correct.

Optional solana-program dependency and feature mapping via dep:solana-program are set up properly; edition = "2018" complies with guidelines.


13-20: No change required — feature name is consistently hyphenated ("solana-program").
Search results show only cfg(feature = "solana-program") usages; no "solana_program" occurrences found.

shank/tests/context_method_test.rs (1)

29-47: Confirm “optional” flag position (tuple index 5) is stable.

If the tuple shape evolves, this test will silently mis-read fields. Consider asserting the field name too to anchor expectations.

shank/tests/context_method_demo.rs (3)

50-64: Good coverage of IDL flags.

The assertions validate mut, signer, and optional semantics clearly.


93-107: Mirror the same IDL assertions in the no-feature path.

Consistent and clear; keeps behavior aligned across cfgs.


25-48: Unify context() signature in docs/examples to match the macro API

Repo contains mixed examples using:

  • context(&accounts) / context(accounts)
  • context(accounts, program_id)
  • context(program_id, accounts)

Update all comments/examples to the single canonical signature the macro actually generates. Representative locations to fix: shank-macro/src/lib.rs (~lines 345 and 434), EXAMPLE_USAGE.md:34, shank/examples/full_solana_example.rs:87/154, shank/examples/context_usage_example.rs:45, shank/tests/context_usage_test.rs:77/151/225, shank/tests/context_method_demo.rs:35.

shank-idl/tests/fixtures/instructions/single_file/instruction_with_simple_accounts_struct.json (1)

14-17: Confirm expected IDL field casing.

The account "dataAccount" (camelCase) may not match default generation from a Rust field named data_account unless #[idl_name("dataAccount")] is used. Align casing or annotate the Rust source.

shank/tests/expand/empty_struct.rs (1)

3-6: LGTM: empty struct expansion fixture.

Fits macrotest pattern; main() stub is appropriate for expansion.

shank/tests/instruction_with_accounts_struct_test.rs (1)

80-89: No change required — IDL names are camelCase by default. The fixture pair shows Rust field data_account -> IDL "dataAccount" (shank-idl/tests/fixtures/instructions/single_file/instruction_with_simple_accounts_struct.rs and .json); only add #[idl_name("dataAccount")] if you want to override the default conversion.

shank-macro-impl/src/lib.rs (2)

2-2: New accounts module export matches the added derive surface

Good addition. Ensure all internal references use this module path consistently.


17-17: Proc-macro entrypoint wired: ShankAccounts → derive_accounts

Verified — shank-macro/src/lib.rs contains #[proc_macro_derive(ShankAccounts)] and pub fn shank_accounts(...) which calls derive_accounts(...). (shank-macro/src/lib.rs:447–452)

shank-idl/src/lib.rs (1)

8-8: Private accounts_extraction module inclusion

LGTM. Keep it private unless there’s a clear external consumer to avoid API surface creep.

shank/tests/expand/empty_struct.expanded.rs (1)

1-11: Snapshot contents look sane for the empty-accounts case

No issues. Keep in mind snapshots can be toolchain‑sensitive; update fixtures if rustc output changes.

If you hit frequent snapshot churn, consider macrotest filters to strip unstable attrs like #[rustc_box].

shank/tests/expand/no_constraints.rs (1)

10-15: No-constraints derive case: OK

Good fixture for read-only defaults without attributes.

shank/src/lib.rs (1)

4-5: Inspect context module for Solana imports

Please check the output of the above script to confirm whether shank/src/context.rs imports any Solana crates. Once we verify there are no Solana dependencies, we can remove the feature‐gate suggestion and consider this re‐export safe.

shank-macro/src/accounts.rs (1)

6-9: Bridge is correct — proc‑macro maps errors to compile_error()
Confirmed: shank-macro/src/lib.rs — shank_accounts calls derive_accounts(input).unwrap_or_else(to_compile_error).into() and to_compile_error delegates to ParseError::to_compile_error(...).

shank/tests/expand/no_constraints.expanded.rs (1)

12-25: Accounts metadata looks correct for a no-constraints struct.

Indices, mut/signers/optional flags, and descriptions (None) align with expectations.

shank-idl/src/file.rs (1)

110-129: Correctly wires ShankAccounts into instruction IDL conversion.

Early extraction and passing into IdlInstructions::try_into_with_accounts is the right integration point.

Please confirm there’s no double-iteration hazard on ctx.structs() across instructions() and accounts() in large crates (should be fine since each creates a fresh iterator).

shank-idl/tests/fixtures/instructions/single_file/instruction_with_accounts_struct.json (1)

1-94: Fixture aligns with struct-based accounts expansion.

Accounts, args, and discriminants look consistent; optionality encoded via isOptional.

Double-check naming conventions (e.g., systemProgram vs. Rust system_program) match the generator’s casing rules for this fixture comparison.

shank-idl/tests/instructions.rs (3)

193-209: Good coverage: simple accounts struct round‑trip

Test mirrors existing pattern and asserts full IDL equality. Looks solid.


211-226: Good: accounts struct with optional handling

Consistent with the fixture layout and ParseIdlConfig usage.


228-243: Good: complex struct scenario exercised

Nice to see a complex coverage case added.

shank/tests/basic_context_test.rs (1)

1-17: LGTM: minimal mock and derive usage

Local AccountInfo mock and attributes look fine for exercising the macro.

shank/tests/expand/custom_lifetime.rs (1)

10-17: LGTM: lifetime variance covered

Good case ensuring lifetimes other than 'info are accepted.

shank/tests/simple_context_test.rs (2)

25-30: Strengthen assertions to validate optional flag

Since the struct includes #[account(optional)], assert the metadata reflects it.
[suggest_minor_issue]
Apply:

 let accounts = SimpleAccounts::__shank_accounts();
 assert_eq!(accounts.len(), 3);
+// payer, data, optional_account
+assert_eq!(accounts[2].1, "optional_account");
+assert_eq!(accounts[2].5, true); // isOptional

13-23: LGTM: simple struct shape for __shank_accounts()

Attributes and field order suit the metadata checks.

shank-idl/tests/fixtures/instructions/single_file/instruction_with_simple_accounts_struct.rs (1)

4-17: LGTM: fixture focuses on attributes, not types

Appropriate for AST-based IDL extraction; types being non-reference is fine here.

shank/tests/error_handling_test.rs (8)

10-21: LGTM: documents compile-time conflict clearly

Good to keep the conflicting case commented with rationale.


22-49: LGTM: signer vs optional_signer semantics validated

Index mapping matches expanded tests tuple layout.


51-71: LGTM: description capture behavior

Covers presence/absence; concise.


73-98: LGTM: field order preservation

Validates deterministic ordering.


100-172: LGTM: comprehensive flag matrix

Good breadth across combinations including desc.


174-196: LGTM: lifetime-parameter variants

Matches expectations from expanded fixtures.


198-217: LGTM: special field names persisted verbatim

Covers common edge cases.


219-245: LGTM: long description handling

Validates empty and long docs as well.

EXAMPLE_USAGE.md (1)

45-50: Remaining-accounts handling verified — no change required

Confirmed: Context exposes pub remaining_accounts: &'a [U]; the macro builds it as &accounts[#expected_accounts..]; minimum-account validation returns Err(ProgramError::NotEnoughAccountKeys) when too few are provided; tests cover both extra and no-extra cases (shank/src/context.rs, shank-macro-impl/src/accounts/mod.rs, shank/tests/context_api_working_test.rs, shank/tests/context_usage_test.rs).

shank-macro-impl/src/accounts/account_field.rs (2)

60-66: Semantics: should optional_signer imply optional?

Current metadata can mark optional_signer=true with optional=false. Consider setting optional=true when optional_signer is present, or document the intended semantics.


23-58: No action required — workspace uses syn 1.x (syn = 1.0.82)
Cargo.toml entries show syn = 1.0.82, so attr.parse_meta(), attr.path and NestedMeta are compatible; no migration to syn 2 required.

shank-idl/ACCOUNTS_STRUCT_IDL.md (1)

41-46: Incorrect: placeholder-name mismatch claim — no change required

Both the IDL and the emitter already use "_accounts_struct{StructName}"; the "accountsStruct..." variant is not present in the repo. Found at shank-idl/ACCOUNTS_STRUCT_IDL.md:42 and shank-macro-impl/src/instruction/instruction.rs:155.

Likely an incorrect or invalid review comment.

shank-macro-impl/src/instruction/instruction.rs (1)

134-167: Unify placeholder naming and robustly extract struct name

Use the last path segment for the struct name (handles qualified paths) and standardize the placeholder to "_accounts_struct{StructName}" to align snapshots.

File: shank-macro-impl/src/instruction/instruction.rs
Lines: 134-167

-            Err(_) => {
+            Err(_) => {
                 let accounts_source = AccountsSource::try_from(attrs)?;
                 let accounts = match &accounts_source {
                     AccountsSource::Inline(accs) => accs.0.clone(),
                     AccountsSource::Struct(path) => {
-                        // Create placeholder accounts based on the struct name
-                        // The actual resolution will happen during IDL generation
-                        // For now, create a single placeholder account that indicates this uses a struct
-                        vec![InstructionAccount {
-                            ident: ident.clone(),
-                            index: Some(0),
-                            name: format!("__accounts_struct_{}", path.get_ident().unwrap_or(&ident)),
-                            writable: false,
-                            signer: false,
-                            optional_signer: false,
-                            desc: Some(format!("Accounts defined by struct: {}", path.get_ident().unwrap_or(&ident))),
-                            optional: false,
-                        }]
+                        // Placeholder resolved during IDL generation
+                        let struct_name = path.segments.last()
+                            .map(|seg| seg.ident.to_string())
+                            .unwrap_or_else(|| ident.to_string());
+                        vec![InstructionAccount {
+                            ident: ident.clone(),
+                            index: Some(0),
+                            name: format!("__accounts_struct_{}", struct_name),
+                            writable: false,
+                            signer: false,
+                            optional_signer: false,
+                            desc: Some(format!("Accounts defined by struct: {}", struct_name)),
+                            optional: false,
+                        }]
                     }
                 };
                 let strategies: InstructionStrategies = attrs.into();
                 (accounts_source, accounts, strategies)
             }
shank/tests/context_api_test.rs (1)

65-79: Great coverage on optional and mut/signer combos.

These assertions give good signal on tuple ordering and flags for writable/signer/optional.

shank/tests/context_runtime_test.rs (2)

31-63: LGTM: feature-gated stubs make the tests portable.

The cfg blocks keep tests compiling without solana-program; good separation.


153-173: Nice default-constraints sweep; consider asserting desc is None.

You already assert None at index 6; good coverage.

shank-macro/src/lib.rs (2)

246-247: Approve — derive_instruction already accepts #[accounts(StructName)] and wires the extraction path.

Parser and dispatch are implemented: shank-macro-impl/src/instruction/account_attrs.rs (is_accounts_struct_attr, from_accounts_struct_attr, try_from), wiring in shank-macro-impl/src/instruction/instruction.rs (uses to_accounts / AccountsSource), and account struct conversion in shank-macro-impl/src/accounts/account_struct.rs::to_instruction_accounts.


365-453: Approve — ShankAccounts macro emits IDL metadata and context helpers.
gen_impl defines pub fn __shank_accounts() -> Vec<(u32, &'static str, bool, bool, bool, bool, Option)> and a pub fn context(...) -> ::shank::Context<...> (see shank-macro-impl/src/accounts/mod.rs, around lines 61 and 111).

shank-macro-impl/src/accounts/account_struct.rs (1)

25-35: Tuple order verified — no action required.
gen_account_metadata returns (index, name, writable, signer, optional_signer, optional, desc) in that exact order, matching gen_account_list usage.

shank-idl/tests/fixtures/instructions/single_file/instruction_with_complex_accounts_struct.json (1)

41-49: Optional-signer semantics are implemented as-is; the suggested isOptional = optional || optional_signer is incorrect.

shank-idl/src/accounts_extraction.rs:120–124 clears optional when optional_signer is set, and shank-idl/src/idl_instruction.rs:358–362 maps is_signer = signer, is_optional = optional, is_optional_signer = optional_signer.

Likely an incorrect or invalid review comment.

shank/tests/final_context_test.rs (1)

52-53: Confirm Context generic usage matches the crate API.

If Context is defined with lifetimes (e.g., Context<'info, T>), this may need to be Context<TestAccounts<'_>> or similar. Please verify and adjust.

shank-idl/src/idl_instruction.rs (1)

229-253: Incorrect — accounts_map is keyed by the short struct ident; last-segment lookup is correct.

shank-idl/src/accounts_extraction.rs inserts keys with let struct_name = struct_item.ident.to_string(); followed by accounts_map.insert(struct_name, accounts); (around lines 18–21), so resolving via struct_path.segments.last() matches the map keys. No change required.

Likely an incorrect or invalid review comment.

shank/tests/context_api_working_test.rs (2)

20-30: Good: ShankAccounts usage and field shapes look consistent.

Derive, lifetimes, and Option<&AccountInfo> are coherent with the intended API.


96-101: Confirmed: optional-account sentinel is crate::ID.

The generated context/macro explicitly maps optional fields to None when accounts[#idx].key == &crate::ID (see shank-render/src/context/render_context.rs:25 and shank-macro-impl/src/accounts/mod.rs:90-93, comment 108-111). The test's expectation that key == ID yields None is correct.

shank-idl/src/accounts_extraction.rs (1)

126-136: LGTM: InstructionAccount construction.

Fields mapped coherently (index, ident, flags, desc).

shank-idl/tests/fixtures/instructions/single_file/instruction_with_accounts_struct.rs (1)

1-44: LGTM: Struct-based accounts expansion setup.

Accounts structs and enum wiring match the intended flow.

shank/tests/expand/complex_constraints.rs (1)

10-29: LGTM: Constraint coverage looks comprehensive.

Good spread of mut, signer, optional, and combinations.

shank/tests/context_usage_test.rs (1)

25-33: API mismatch: tests call context(accounts, &program_id) and expect Result<...>.

This relies on the macro fix in accounts/mod.rs. Without it, these tests won’t compile. Re-run after applying the macro patch.

Also applies to: 76-83, 150-158, 223-231, 271-275

shank/tests/context_issue_demo.rs (2)

25-28: API mismatch: context() currently returns Context, not Result.

Once macro is fixed to return Result and take program_id, this test will be valid. Track dependency on macro change.


31-62: Ensure context() is cfg-gated off in non-spl builds.

With the proposed macro change, context() won’t be generated here. This test should compile cleanly.

shank/tests/expand/complex_constraints.expanded.rs (1)

21-85: Expanded tuple shape/flags look correct.

Writable/signer/optional_signer/optional flags and descriptions align with attributes.

Confirm expand golden files don’t expect context() to appear; if they do, update fixtures or ensure macro cfg-gates generation.

shank-idl/tests/fixtures/instructions/single_file/instruction_with_complex_accounts_struct.rs (3)

3-23: Struct fields use owned AccountInfo; OK for IDL, but not for context().

This fixture is IDL-only. Ensure tests run without solana-program feature so context() isn’t generated; otherwise owned types will mismatch the macro’s reference-based context assignment.


25-41: Mixed instruction spec looks good.

Combining #[accounts(ComplexAccounts)] with old-style inline accounts exercises the new expansion path well.


43-48: Local AccountInfo mock is fine for IDL.

No changes needed.

shank/examples/context_usage_example.rs (1)

23-36: Docs vs behavior: optional accounts must use Option.

You did this correctly for metadata. Good.

Please confirm the generated ctx.metadata type is Option<&AccountInfo<'_>> as intended across all derives.

shank-macro-impl/src/instruction/account_attrs.rs (1)

65-153: Inline account parser looks solid; nice coverage of aliases and mutual exclusivity.

No functional issues found.

shank/examples/anchor_style_example.rs (1)

112-127: Example text aligns with Option usage.

Messages correctly describe Option<&AccountInfo>. After fixing types above, this stays consistent.

shank/examples/full_solana_example.rs (1)

79-109: Processor paths look consistent with context(program_id, accounts).

Logging keys and optional handling are clear.

Please confirm Pubkey implements Display in your MSRV; if not, switch to {:?}.

Also applies to: 111-129

shank/tests/enhanced_accounts_test.rs (1)

92-99: Mixed old/new styles exercised correctly.

Good coverage ensuring backward compatibility.

Also applies to: 114-123

shank/examples/accounts_struct_example.rs (1)

11-36: LGTM: clear, idiomatic use of ShankAccounts for examples

Field attributes (mut/signer/optional/desc) look correct, and using PhantomData keeps this example buildable without Solana deps. No functional concerns.

Also applies to: 38-51, 53-66

shank/tests/instruction_integration_test.rs (1)

77-79: Note on optional fields in tests

Using #[account(optional)] with a non-Option field is fine here because you’re only asserting IDL metadata via __shank_accounts(). If you later exercise context() runtime generation, these should be Option<AccountInfo<'info>> (or similar) to reflect optionality.

Also applies to: 159-161

Comment on lines +10 to +12
// Program ID - normally created by declare_id! macro
pub const ID: [u8; 32] = [1; 32];

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Program ID type should reflect Solana’s Pubkey/declare_id!

Using [u8; 32] for ID can mislead users. Prefer declare_id! so examples align with real projects.

Apply:

-// Program ID - normally created by declare_id! macro
-pub const ID: [u8; 32] = [1; 32];
+// Program ID - typically created by `declare_id!`, which defines `crate::ID: Pubkey`
+use solana_program::{declare_id, pubkey::Pubkey};
+declare_id!("11111111111111111111111111111111");
📝 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
// Program ID - normally created by declare_id! macro
pub const ID: [u8; 32] = [1; 32];
// Program ID - typically created by `declare_id!`, which defines `crate::ID: Pubkey`
use solana_program::{declare_id, pubkey::Pubkey};
declare_id!("11111111111111111111111111111111");
🤖 Prompt for AI Agents
In EXAMPLE_USAGE.md around lines 10 to 12, replace the raw byte array pub const
ID: [u8; 32] = [1; 32]; with the standard Solana pattern using the declare_id!
macro so examples match real projects: import or reference
solana_program::declare_id! (or the appropriate crate path in the example) and
use declare_id!("ReplaceWithYourProgramIdHere"); ensuring the example shows the
macro usage rather than a [u8; 32] literal and updating any surrounding text to
mention inserting the real program id string.

Comment on lines +31 to +41
```rust
// When optional account is provided:
let accounts = [payer, vault, authority_account, system_program];
let ctx = CreateVaultAccounts::context(&accounts);
assert!(ctx.accounts.optional_authority.is_some());

// When optional account is NOT provided (pass program ID as placeholder):
let accounts = [payer, vault, program_id_placeholder, system_program];
let ctx = CreateVaultAccounts::context(&accounts);
assert!(ctx.accounts.optional_authority.is_none());
```
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick

Clarify the “program ID placeholder”

Explicitly note that the placeholder must be an AccountInfo whose key equals crate::ID; otherwise readers may try to pass a Pubkey.

Apply:

-// When optional account is NOT provided (pass program ID as placeholder):
-let accounts = [payer, vault, program_id_placeholder, system_program];
+// When optional account is NOT provided (pass an AccountInfo whose key == crate::ID as placeholder):
+// e.g., let program_id_placeholder = AccountInfo { key: &ID.to_bytes(), data: &[], owner: &ID.to_bytes() /* mock */ };
+let accounts = [payer, vault, program_id_placeholder /* AccountInfo with key == crate::ID */, system_program];

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In EXAMPLE_USAGE.md around lines 31 to 41, the comment about passing a "program
ID placeholder" is unclear; update the example text to explicitly state that the
placeholder must be an AccountInfo whose key equals crate::ID (not a Pubkey),
and instruct readers to construct or pass an AccountInfo with key == crate::ID
when they want to omit the optional account so the context check behaves as
shown.

Comment on lines +45 to +49
1. **Ergonomic API**: No need to pass program ID parameter - uses `crate::ID` automatically
2. **Type Safety**: Optional accounts use `Option<&AccountInfo>` types
3. **Runtime Detection**: Checks if `account.key == crate::ID` to determine None/Some
4. **IDL Generation**: Proper `"isOptional": true` flags in generated IDL
5. **Remaining Accounts**: Automatically handles extra accounts beyond the struct definition
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

🧩 Analysis chain

Inconsistent API description: context() with or without program_id

Elsewhere (examples/… and docs), context(program_id, accounts) appears. Here it claims no program_id is needed. Please unify and document the canonical signature and when each form applies.

Would you confirm the intended API and update all docs accordingly?


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "PWD: $(pwd)"
echo "--- repo root listing ---"
ls -la

echo
echo "---- EXAMPLE_USAGE.md (first 200 lines) ----"
if [ -f EXAMPLE_USAGE.md ]; then
  sed -n '1,200p' EXAMPLE_USAGE.md || true
else
  echo "EXAMPLE_USAGE.md not found at repo root"
fi

echo
echo "---- rg: search for 'context(' across repo ----"
rg -n -S -C2 "context\(" || true

echo
echo "---- rg: search for 'context(program_id' ----"
rg -n -S -C2 "context\(.*program_id" || true

echo
echo "---- rg: search for the exact substring 'context(program_id, accounts' ----"
rg -n -F "context(program_id, accounts" || true

echo
echo "---- rg: look for function/definition 'fn context' / 'def context' / 'function context' ----"
rg -n -S -C2 "fn context\b" || true
rg -n -S -C2 "\bdef context\b" || true
rg -n -S -C2 "\bfunction context\b" || true

Length of output: 28416


Unify the context(...) API to a single canonical signature and apply it across the repo.

Repo mixes context(&[AccountInfo]) and context(program_id, accounts) in docs, examples, tests, and generated code — this must be unified.

Files to update:

  • Implementations / generator: shank-macro-impl/src/accounts/mod.rs, shank-render/src/context/render_context.rs
  • Macro docs: shank-macro/src/lib.rs
  • Examples / docs: EXAMPLE_USAGE.md, shank/examples/full_solana_example.rs, shank/examples/context_usage_example.rs, shank/examples/anchor_style_example.rs, shank/examples/accounts_struct_example.rs
  • Tests: shank/tests/* (e.g. context_usage_test.rs, final_context_test.rs, context_issue_demo.rs)

Action required: choose the canonical signature (either accounts-only — using crate::ID as the placeholder for missing optional accounts — or explicit program_id + accounts), update codegen/macros, fix all examples/tests/docs to match, and document the chosen behavior for optional accounts in EXAMPLE_USAGE.md and macro docs.

}
```

This follows Solana's modern optional accounts pattern where missing optional accounts are represented by the program ID. No newline at end of file
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick

Scope the claim about optional-accounts convention

“Solana’s modern optional accounts pattern” is framework-convention, not a protocol rule. Rephrase to avoid overstating.

Apply:

-This follows Solana's modern optional accounts pattern where missing optional accounts are represented by the program ID.
+This follows a common framework convention where missing optional accounts are represented by the program ID.
📝 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
This follows Solana's modern optional accounts pattern where missing optional accounts are represented by the program ID.
This follows a common framework convention where missing optional accounts are represented by the program ID.
🤖 Prompt for AI Agents
In EXAMPLE_USAGE.md around line 64, the sentence asserts "Solana’s modern
optional accounts pattern" as a protocol rule; rephrase to scope it as a
framework/convention instead of a protocol requirement. Update the wording to
indicate this is a common framework or developer convention (e.g., "a common
convention in many Solana frameworks") and avoid language that implies it is
mandated by the Solana protocol.

Comment on lines +16 to +24
#[account(mut, signer, desc = "The payer and authority")]
pub payer: AccountInfo<'info>,

#[account(mut, desc = "The vault account to create")]
pub vault: AccountInfo<'info>,

#[account(desc = "System program")]
pub system_program: AccountInfo<'info>,
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick

Use &'info AccountInfo<'info> in examples for consistency

Examples elsewhere (tests/examples) use references. Update this snippet to avoid confusion.

-    pub payer: AccountInfo<'info>,
+    pub payer: &'info AccountInfo<'info>,
-    pub vault: AccountInfo<'info>,
+    pub vault: &'info AccountInfo<'info>,
-    pub system_program: AccountInfo<'info>,
+    pub system_program: &'info AccountInfo<'info>,
📝 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
#[account(mut, signer, desc = "The payer and authority")]
pub payer: AccountInfo<'info>,
#[account(mut, desc = "The vault account to create")]
pub vault: AccountInfo<'info>,
#[account(desc = "System program")]
pub system_program: AccountInfo<'info>,
}
#[account(mut, signer, desc = "The payer and authority")]
pub payer: &'info AccountInfo<'info>,
#[account(mut, desc = "The vault account to create")]
pub vault: &'info AccountInfo<'info>,
#[account(desc = "System program")]
pub system_program: &'info AccountInfo<'info>,
}
🤖 Prompt for AI Agents
In shank-idl/ACCOUNTS_STRUCT_IDL.md around lines 16 to 24, the account fields
are declared as owned AccountInfo<'info> which is inconsistent with other
examples; update the declarations to use references by changing each field to
&'info AccountInfo<'info> (preserving attributes like mut and signer where
present) so the snippet matches the rest of the codebase and avoids confusion.

pub optional_authority: Option<&'info AccountInfo<'info>>,
}

fn main() {} No newline at end of file
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick

Add trailing newline

Minor formatting polish for editors and linters.

🤖 Prompt for AI Agents
In shank/tests/expand/optional_accounts.rs around line 22, the file is missing a
trailing newline; add a newline at the end of the file so the final line ends
with a newline character to satisfy editors and linters.

Comment on lines +10 to +55
#[test]
fn test_idl_generation_basic_accounts() {
#[derive(ShankAccounts)]
pub struct BasicAccounts<'info> {
#[account(mut, signer, desc = "The payer account")]
pub payer: &'info AccountInfo<'info>,

#[account(mut, desc = "The data account")]
pub data: &'info AccountInfo<'info>,

#[account(desc = "The system program")]
pub system_program: &'info AccountInfo<'info>,
}

let idl = BasicAccounts::__shank_accounts();

// Verify we have 3 accounts
assert_eq!(idl.len(), 3);

// Check payer account
assert_eq!(idl[0].0, 0); // index
assert_eq!(idl[0].1, "payer"); // name
assert_eq!(idl[0].2, true); // mut
assert_eq!(idl[0].3, true); // signer
assert_eq!(idl[0].4, false); // optional_signer (false for regular signer)
assert_eq!(idl[0].5, false); // optional
assert_eq!(idl[0].6, Some("The payer account".to_string())); // description

// Check data account
assert_eq!(idl[1].0, 1);
assert_eq!(idl[1].1, "data");
assert_eq!(idl[1].2, true); // mut
assert_eq!(idl[1].3, false); // not signer
assert_eq!(idl[1].4, false); // optional_signer
assert_eq!(idl[1].5, false); // not optional
assert_eq!(idl[1].6, Some("The data account".to_string()));

// Check system program
assert_eq!(idl[2].0, 2);
assert_eq!(idl[2].1, "system_program");
assert_eq!(idl[2].2, false); // not mut
assert_eq!(idl[2].3, false); // not signer
assert_eq!(idl[2].4, false); // optional_signer
assert_eq!(idl[2].5, false); // not optional
assert_eq!(idl[2].6, Some("The system program".to_string()));
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick

Solid coverage of tuple semantics and flag combinations.

Tests accurately validate indices, names, mut/signers, optional flags, and descriptions across scenarios.

Consider a small helper or a type alias to avoid tuple indexing noise in assertions for readability:

type AccountMeta = (u32, &'static str, bool, bool, bool, bool, Option<String>);

Also applies to: 57-90, 92-160, 162-181, 183-203, 205-212, 214-231, 233-263, 265-290

🤖 Prompt for AI Agents
In shank/tests/idl_generation_test.rs around lines 10 to 55, the assertions use
raw tuple indexing which harms readability; add a local type alias like
AccountMeta = (u32, &'static str, bool, bool, bool, bool, Option<String>) near
the top of the test file and then replace the index-based assertions with either
pattern destructuring (let (idx, name, is_mut, is_signer, opt_signer, opt, desc)
= idl[n]; assert_eq!(idx, ...); ...) or by binding idl[n] to a variable of type
AccountMeta before asserting; apply the same alias/destructuring approach to the
other listed test ranges to reduce tuple-index noise and make assertions
clearer.

Comment on lines +32 to +34
fn main() {
println!("Simple context test");
} No newline at end of file
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove main() from integration test

Avoid defining fn main() in tests.

Apply:

-fn main() {
-    println!("Simple context test");
-}
📝 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
fn main() {
println!("Simple context test");
}
🤖 Prompt for AI Agents
In shank/tests/simple_context_test.rs around lines 32 to 34, the file currently
defines a standalone fn main() in an integration test which should be removed;
replace the top-level main function with one or more #[test] functions (or move
helper code to a module and expose test functions) so the test runner executes
them — delete fn main(), add a #[test] fn simple_context_test() { /* move test
logic here and use assertions instead of println! */ } and ensure the file
compiles as an integration test without a main.

Copy link

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

Review continued from previous batch...

Comment on lines +11 to +26
/// Extract ShankAccounts structs and their metadata
pub fn extract_shank_accounts_structs<'a>(
structs: impl Iterator<Item = &'a ItemStruct>,
) -> Result<HashMap<String, Vec<InstructionAccount>>> {
let mut accounts_map = HashMap::new();

for struct_item in structs {
if let Some(_attr) = get_derive_attr(&struct_item.attrs, DERIVE_ACCOUNTS_ATTR) {
let struct_name = struct_item.ident.to_string();
let accounts = extract_accounts_from_struct(struct_item)?;
accounts_map.insert(struct_name, accounts);
}
}

Ok(accounts_map)
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick

Naming mismatch with guidelines (ShankAccount vs ShankAccounts).

The code targets ShankAccounts; update docs/guidelines to avoid confusion or provide a compatibility note.

🤖 Prompt for AI Agents
In shank-idl/src/accounts_extraction.rs around lines 11 to 26, the doc comment
and naming refer to "ShankAccounts" but project guidelines use "ShankAccount"
(singular) causing mismatch; update the comment and any exposed identifiers to
match the canonical name: change the docstring to "Extract ShankAccount structs
and their metadata" (or add a brief compatibility note if keeping the plural),
and update project docs/guidelines to reflect the chosen singular/plural
convention so names are consistent across code and docs.

Comment on lines +70 to +76
let has_option_type = if let Type::Path(type_path) = &field.ty {
if let Some(segment) = type_path.path.segments.first() {
segment.ident == "Option"
} else {
false
}
} else {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Option detection is fragile (uses first segment).

Fully-qualified types (e.g., std::option::Option) won’t be detected. Use the last path segment.

-    let has_option_type = if let Type::Path(type_path) = &field.ty {
-        if let Some(segment) = type_path.path.segments.first() {
-            segment.ident == "Option"
-        } else {
-            false
-        }
-    } else {
-        false
-    };
+    let has_option_type = matches!(&field.ty, Type::Path(type_path)
+        if type_path.path.segments.last().map_or(false, |seg| seg.ident == "Option"));
📝 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
let has_option_type = if let Type::Path(type_path) = &field.ty {
if let Some(segment) = type_path.path.segments.first() {
segment.ident == "Option"
} else {
false
}
} else {
let has_option_type = matches!(&field.ty, Type::Path(type_path)
if type_path.path.segments.last().map_or(false, |seg| seg.ident == "Option"));
🤖 Prompt for AI Agents
In shank-idl/src/accounts_extraction.rs around lines 70 to 76, the current
Option<T> detection inspects the first path segment which misses fully-qualified
paths like std::option::Option; change the logic to inspect the last path
segment instead (e.g., use path.segments.last()) and compare that segment.ident
to "Option", keeping the existing Type::Path check; ensure you handle the Option
being present or not (map/and_then or pattern) so you don’t panic on empty
segments.

Comment on lines +80 to +107
// Parse #[account(...)] attributes
for attr in &field.attrs {
if attr.path.is_ident("account") {
// Use a simple string-based parsing approach for now
// This is a simplified version - in production we'd want more robust parsing
let tokens_str = attr.tokens.to_string();

// Simple parsing of common attributes
if tokens_str.contains("mut") || tokens_str.contains("writable") {
writable = true;
}
if tokens_str.contains("signer") {
signer = true;
}
if tokens_str.contains("optional_signer") {
optional_signer = true;
} else if tokens_str.contains("optional") {
optional = true;
}

// Extract description using simple regex-like approach
if let Some(desc_start) = tokens_str.find("desc = \"") {
let desc_content = &tokens_str[desc_start + 8..];
if let Some(desc_end) = desc_content.find('"') {
desc = Some(desc_content[..desc_end].to_string());
}
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

🧩 Analysis chain

Replace string-based parsing with syn’s nested meta; support alias attributes.

String contains() is brittle (spacing/ordering) and misses aliases (write/writ/w, sign/sig/s, opt/option). Parse tokens robustly and accept aliases to match the macro’s surface.

-    // Parse #[account(...)] attributes
-    for attr in &field.attrs {
-        if attr.path.is_ident("account") {
-            // Use a simple string-based parsing approach for now
-            // This is a simplified version - in production we'd want more robust parsing
-            let tokens_str = attr.tokens.to_string();
-            
-            // Simple parsing of common attributes
-            if tokens_str.contains("mut") || tokens_str.contains("writable") {
-                writable = true;
-            }
-            if tokens_str.contains("signer") {
-                signer = true;
-            }
-            if tokens_str.contains("optional_signer") {
-                optional_signer = true;
-            } else if tokens_str.contains("optional") {
-                optional = true;
-            }
-            
-            // Extract description using simple regex-like approach
-            if let Some(desc_start) = tokens_str.find("desc = \"") {
-                let desc_content = &tokens_str[desc_start + 8..];
-                if let Some(desc_end) = desc_content.find('"') {
-                    desc = Some(desc_content[..desc_end].to_string());
-                }
-            }
-        }
-    }
+    // Parse #[account(...)] attributes robustly and support aliases
+    for attr in &field.attrs {
+        #[allow(deprecated)]
+        if attr.path().is_ident("account") {
+            attr.parse_nested_meta(|meta| {
+                let key = meta.path.segments.last().map(|s| s.ident.to_string()).unwrap_or_default();
+                match key.as_str() {
+                    // writable variants
+                    "mut" | "writable" | "write" | "writ" | "w" => writable = true,
+                    // signer variants
+                    "signer" | "sign" | "sig" | "s" => signer = true,
+                    // optional variants
+                    "optional_signer" => optional_signer = true,
+                    "optional" | "opt" | "option" => optional = true,
+                    // desc = "..."
+                    "desc" => {
+                        let val: shank_macro_impl::syn::LitStr = meta.value()?.parse()?;
+                        desc = Some(val.value());
+                    }
+                    _ => {}
+                }
+                Ok(())
+            })?;
+        }
+    }

🏁 Script executed:

#!/bin/bash
# Ensure syn 2.x is in use (parse_nested_meta API)
rg -n --glob '**/Cargo.toml' -C2 'syn\s*='
# Grep for alias attributes in tests to confirm coverage alignment
rg -n --type=rust -C2 '#\[account\((write|writ|w|sign|sig|s|opt|option)\b'

Length of output: 4788


Use Attribute::parse_nested_meta for robust #[account(...)] parsing with alias support

  • In shank-idl/src/accounts_extraction.rs, replace the tokens_str.contains(...) logic with attr.parse_nested_meta(|meta| { … }), matching on meta.path variants:
    • writable: "mut" | "writable" | "write" | "writ" | "w"
    • signer: "signer" | "sign" | "sig" | "s"
    • optional: "optional_signer"; and "optional" | "opt" | "option"
  • Parse desc via meta.value()?.parse::<LitStr>()? instead of manual string slicing (requires full feature for parse_nested_meta) (docs.rs)
  • Remove brittle, order- and spacing-dependent string parsing entirely.
🤖 Prompt for AI Agents
In shank-idl/src/accounts_extraction.rs around lines 80 to 107, replace the
brittle tokens_str.contains(...) string parsing with syn's
Attribute::parse_nested_meta to robustly parse #[account(...)] entries: call
attr.parse_nested_meta(|meta| { ... }) (requires syn "full" feature), match on
meta.path to set writable when the path is any of
"mut","writable","write","writ","w", set signer when path is
"signer","sign","sig","s", set optional_signer for "optional_signer" and
optional for "optional","opt","option"; for desc extract the literal via
meta.value()?.parse::<LitStr>()? instead of manual slicing; remove the old
string-based checks and ensure error propagation or sensible defaults on parse
failures.

Comment on lines +147 to +156
ensure!(
discriminant < u8::MAX as usize,
anyhow!(
"Instruction variant discriminants have to be <= u8::MAX ({}), \
but the discriminant of variant '{}' is {}",
u8::MAX,
ident,
discriminant
)
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Off-by-one on discriminant bound (excludes 255).

The check uses < u8::MAX but the error text says <=. Use <= to accept 255.

-        ensure!(
-            discriminant < u8::MAX as usize,
+        ensure!(
+            discriminant <= u8::MAX as usize,
             anyhow!(
                 "Instruction variant discriminants have to be <= u8::MAX ({}), \
                     but the discriminant of variant '{}' is {}",
                 u8::MAX,
                 ident,
                 discriminant
             )
         );
📝 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
ensure!(
discriminant < u8::MAX as usize,
anyhow!(
"Instruction variant discriminants have to be <= u8::MAX ({}), \
but the discriminant of variant '{}' is {}",
u8::MAX,
ident,
discriminant
)
);
ensure!(
discriminant <= u8::MAX as usize,
anyhow!(
"Instruction variant discriminants have to be <= u8::MAX ({}), \
but the discriminant of variant '{}' is {}",
u8::MAX,
ident,
discriminant
)
);
🤖 Prompt for AI Agents
In shank-idl/src/idl_instruction.rs around lines 147 to 156, the discriminant
bound check is off-by-one: it currently uses `< u8::MAX as usize` which rejects
the valid value 255 while the message states "<=". Change the comparison to `<=
u8::MAX as usize` so discriminant 255 is accepted, and keep the error message
as-is; ensure the types remain consistent (usize vs u8::MAX) when making the
comparison.

@@ -0,0 +1,43 @@
use shank::ShankAccounts;
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick

Unused import.

Same here; remove unused import.

-use shank::ShankAccounts;
📝 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
use shank::ShankAccounts;
🤖 Prompt for AI Agents
In shank/tests/expand/custom_lifetime.expanded.rs around line 1, the statement
"use shank::ShankAccounts;" is an unused import; remove that import line from
the file (or replace it with a used import if intended) and run the build/test
to ensure no other references rely on it.

Comment on lines +4 to +8
pub struct AccountInfo<'info> {
pub key: &'info [u8; 32],
pub data: &'info [u8],
pub owner: &'info [u8; 32],
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick

Provide key() like other tests so optional detection can work.

Other tests implement key() on AccountInfo; add the same here for consistency.

 pub struct AccountInfo<'info> {
     pub key: &'info [u8; 32],
     pub data: &'info [u8],
     pub owner: &'info [u8; 32],
 }
+
+impl<'info> AccountInfo<'info> {
+    pub fn key(&self) -> &'info [u8; 32] {
+        self.key
+    }
+}
📝 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
pub struct AccountInfo<'info> {
pub key: &'info [u8; 32],
pub data: &'info [u8],
pub owner: &'info [u8; 32],
}
pub struct AccountInfo<'info> {
pub key: &'info [u8; 32],
pub data: &'info [u8],
pub owner: &'info [u8; 32],
}
impl<'info> AccountInfo<'info> {
pub fn key(&self) -> &'info [u8; 32] {
self.key
}
}
🤖 Prompt for AI Agents
In shank/tests/final_context_test.rs around lines 4 to 8, AccountInfo is missing
the key() accessor used by other tests; add an impl block that defines a
key(&self) -> &'info [u8; 32] method that returns self.key so optional account
detection works the same as the other tests.

Copy link

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

Review continued from previous batch...

Comment on lines +13 to +26
#[derive(ShankAccounts)]
pub struct CreateTokenAccounts<'info> {
#[account(mut, signer, desc = "Payer and authority")]
pub payer: AccountInfo<'info>,

#[account(mut, desc = "Token mint to create")]
pub mint: AccountInfo<'info>,

#[account(desc = "System program")]
pub system_program: AccountInfo<'info>,

#[account(desc = "Token program")]
pub token_program: AccountInfo<'info>,
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 Analysis chain

Fix compile error: visibility qualifiers on inner items (E0449)

Rust forbids pub structs/enums (and the pub methods the macro generates) inside function bodies. Hoist these type/enum definitions to module scope (top of the file) or remove pub and move them out of the test functions. Otherwise, derives will expand to pub impls inside fn and fail to compile.

Apply this pattern (example for the first test; replicate for the rest):

@@
 #[test]
 fn test_accounts_with_instruction_integration() {
-    #[derive(ShankAccounts)]
-    pub struct CreateTokenAccounts<'info> {
-        #[account(mut, signer, desc = "Payer and authority")]
-        pub payer: AccountInfo<'info>,
-        #[account(mut, desc = "Token mint to create")]
-        pub mint: AccountInfo<'info>,
-        #[account(desc = "System program")]
-        pub system_program: AccountInfo<'info>,
-        #[account(desc = "Token program")]
-        pub token_program: AccountInfo<'info>,
-    }
-
-    #[derive(ShankInstruction)]
-    pub enum TokenInstruction {
-        #[accounts(CreateTokenAccounts)]
-        CreateToken { 
-            decimals: u8,
-            supply: u64,
-        },
-    }
+    // Types moved to module scope; test body unchanged
     let accounts_metadata = CreateTokenAccounts::__shank_accounts();
     assert_eq!(accounts_metadata.len(), 4);
@@
 }

Place near the top of this file (module scope):

#[derive(ShankAccounts)]
pub struct CreateTokenAccounts<'info> {
    #[account(mut, signer, desc = "Payer and authority")]
    pub payer: AccountInfo<'info>,
    #[account(mut, desc = "Token mint to create")]
    pub mint: AccountInfo<'info>,
    #[account(desc = "System program")]
    pub system_program: AccountInfo<'info>,
    #[account(desc = "Token program")]
    pub token_program: AccountInfo<'info>,
}

#[derive(ShankInstruction)]
pub enum TokenInstruction {
    #[accounts(CreateTokenAccounts)]
    CreateToken { 
        decimals: u8,
        supply: u64,
    },
}

Repeat this hoisting for:

  • InitializeAccounts, TransferAccounts, CloseAccounts, ProgramInstruction
  • BasicAccounts, CreateAccounts, UpdateAccounts, AdminAccounts, ComplexProgram
  • ComplexDataAccounts, DataInstruction
  • SharedAccounts, SharedInstruction

Also applies to: 29-36, 56-64, 66-80, 82-91, 94-109, 133-136, 139-148, 151-161, 164-174, 178-202, 225-231, 235-251, 262-270, 273-282


🏁 Script executed:

#!/bin/bash
# Verify no inner items with visibility qualifiers remain in this test file
rg -nP '^\s*pub\s+(struct|enum)\s' shank/tests/instruction_integration_test.rs -n -C0
# Expect: only module-scope hits at top-level, none inside #[test] fns.

Length of output: 650


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="shank/tests/instruction_integration_test.rs"
echo ">>> Matches (with 6 lines of context):"
rg -n -C6 '^\s*pub\s+(struct|enum)\s' "$FILE" || true

echo
echo ">>> Context blocks around each match (±10 lines):"
rg -n --only-matching '^\s*pub\s+(struct|enum)\s' "$FILE" | cut -d: -f1 | sort -n | uniq | while read -r ln; do
  start=$((ln - 10)); if [ "$start" -lt 1 ]; then start=1; fi
  end=$((ln + 10))
  echo "----- lines $start-$end -----"
  sed -n "${start},${end}p" "$FILE" | nl -v${start} -ba -w3 -s": "
done

Length of output: 6422


Hoist pub structs/enums to module scope — fixes E0449

Confirmed: multiple pub struct / pub enum definitions are declared inside #[test] functions in shank/tests/instruction_integration_test.rs. Move these type definitions to module scope (near the top of the file) so derives expand to module-level pub impls; keep test bodies unchanged.

Hoist the following (file:line):

  • CreateTokenAccounts — line 14
  • TokenInstruction — line 30
  • InitializeAccounts — line 56
  • TransferAccounts — line 67
  • CloseAccounts — line 82
  • ProgramInstruction — line 95
  • BasicAccounts — line 133
  • CreateAccounts — line 140
  • UpdateAccounts — line 152
  • AdminAccounts — line 165
  • ComplexProgram — line 178
  • ComplexDataAccounts — line 225
  • DataInstruction — line 235
  • SharedAccounts — line 262
  • SharedInstruction — line 273
🤖 Prompt for AI Agents
In shank/tests/instruction_integration_test.rs around lines 13 to 273, several
pub struct/enum definitions are declared inside #[test] functions which causes
E0449; hoist the listed types to module scope (near the top of the file) so
their derived pub impls expand at module level: move CreateTokenAccounts (line
14), TokenInstruction (line 30), InitializeAccounts (56), TransferAccounts (67),
CloseAccounts (82), ProgramInstruction (95), BasicAccounts (133), CreateAccounts
(140), UpdateAccounts (152), AdminAccounts (165), ComplexProgram (178),
ComplexDataAccounts (225), DataInstruction (235), SharedAccounts (262), and
SharedInstruction (273) out of test functions into top-level module scope while
leaving the test bodies unchanged.

Copy link

@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: 17

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
shank-idl/src/idl_instruction.rs (1)

173-291: Struct‑based accounts resolution via accounts_map is correct but shares duplicated arg parsing

The new IdlInstruction::try_from_with_accounts correctly:

  • Reuses the same argument naming semantics as the basic try_from (named vs unnamed, single custom type vs args, arg{index} for multi‑field tuples).
  • Handles AccountsSource::Inline identically to the legacy path.
  • For AccountsSource::Struct, looks up struct_name in accounts_map and expands each InstructionAccount into an IdlAccountItem, falling back to a clearly documented placeholder when the struct can’t be resolved.

Functionally this looks good; the remaining concern is the duplicated arg‑parsing block, which should be factored as noted in the earlier comment.

♻️ Duplicate comments (41)
shank/tests/simple_context_test.rs (1)

32-34: Remove main() from integration test.

This issue was already flagged in a previous review. Integration tests should not define a main() function; test logic should be in #[test] functions instead.

shank/tests/instruction_with_accounts_struct_test.rs (1)

28-29: Normalize authority account attribute to optional, signer (legacy optional_signer).

The authority field is correctly typed as Option<&'info AccountInfo<'info>>, but the attribute still uses the legacy optional_signer form. For consistency with the rest of the API and attributes in this file, prefer the canonical optional, signer combination:

-    #[account(optional_signer, desc = "Authority on the vault")]
-    pub authority: Option<&'info AccountInfo<'info>>,
+    #[account(optional, signer, desc = "Authority on the vault")]
+    pub authority: Option<&'info AccountInfo<'info>>,

Run this to confirm there are no remaining optional_signer usages:

#!/bin/bash
rg -n '#\[account\(optional_signer' -C2
shank/tests/context_issue_demo.rs (1)

69-71: Remove main() from this integration test; rely on the test harness entry point.

Defining fn main() in a file under tests/ conflicts with Rust’s integration test harness, which provides its own main and expects only #[test] functions here. This can cause build failures.

You can just delete this function; the tests above will still run via the harness.

-fn main() {
-    println!("Testing context method availability");
-}
+// Integration tests use the harness-provided main; no standalone main needed here.
shank/tests/context_method_test.rs (1)

44-48: Avoid tuple positional access in assertions.

Indexing the generated metadata with idl[1].5 hard codes the tuple layout; any future change to the derive will silently break this test. Destructure the entry (or look it up by name) and assert on the optional flag explicitly instead.

-    assert_eq!(idl[1].5, true); // optional
+    let (_, _, _, _, _, is_optional, _) = idl[1];
+    assert!(is_optional, "optional_data account should be optional");
shank/tests/working_context_demo.rs (1)

22-35: Replace the integration-test main with a #[test].

Crates in tests/ already get a generated entry point; leaving a free fn main() here causes a duplicate symbol and the test target fails to compile. Convert this to a named #[test] (or move the demo under examples/) so the suite builds.

-fn main() {
+#[test]
+fn working_context_demo() {
shank/tests/context_method_demo.rs (1)

6-9: Drop unused solana_program imports.

ProgramError and Pubkey aren’t referenced in this module, so the test emits unused-import warnings (and will fail in warning-as-error CI). Trim the import list to keep the test clean.

-    use solana_program::{
-        account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey,
-    };
+    use solana_program::account_info::AccountInfo;
shank/examples/full_solana_example.rs (1)

3-65: Derive ShankType for argument structs so they appear in the IDL.

The custom payload structs aren’t exported to the IDL unless they derive ShankType, and our coding guidelines call this out explicitly. Please import ShankType and derive it on both CreateVaultArgs and UpdateVaultArgs so the example matches recommended usage.

-use shank::{ShankAccounts, ShankInstruction};
+use shank::{ShankAccounts, ShankInstruction, ShankType};
@@
-pub struct CreateVaultArgs {
+#[derive(ShankType)]
+pub struct CreateVaultArgs {
@@
-pub struct UpdateVaultArgs {
+#[derive(ShankType)]
+pub struct UpdateVaultArgs {
shank/tests/expand_tests.rs (1)

1-29: Duplication already noted in previous review.

The suggestion to consolidate these test functions to reduce repetition has already been raised in the past review comments.

shank/src/context.rs (1)

1-5: Suggestions already noted in previous review.

The recommendations to add a Debug derive, create a convenience alias for AccountInfo, and clarify documentation have already been raised in past review comments.

shank-idl/tests/generate_expected_idl.rs (1)

5-57: Test utilities are functional and well-structured.

These ignored tests provide a clean way to regenerate expected IDL JSON files from fixture sources. The pattern is consistent across all three test functions.

Past review comments suggest adding file path printing for debugging (e.g., eprintln!("Generating from: {}", file.display());). This remains a valid optional improvement if you want easier correlation between output and source files.

shank-idl/tests/test_shank_accounts_expansion.rs (3)

3-5: Test correctly marked as ignored with known limitation.

The TODO comment on Line 4 acknowledges that this test requires proper Cargo.toml setup. The current temp file approach will cause extract_idl to fail when discovering the manifest.


57-61: Temp file approach incompatible with extract_idl.

extract_idl discovers and reads Cargo.toml to obtain the lib name. Writing a standalone .rs file to the OS temp dir will cause the extraction to fail.

The past review comment provides a detailed solution using tempfile to create a proper Cargo project structure. This remains the correct approach.


78-93: Make assertion strict to catch expansion regressions.

The current lenient check allows the test to pass with a warning when expansion fails (accounts.len() == 1). This defeats the purpose of the test.

Apply:

-            // Check if we have the expected accounts expanded
-            if instruction.accounts.len() == 1 {
-                // If we still have only 1 account, it means our expansion didn't work yet
-                println!("WARNING: Accounts not expanded yet - still showing struct placeholder");
-            } else {
-                // Success! We have multiple accounts
-                println!(
-                    "SUCCESS: Accounts expanded to {} individual accounts",
-                    instruction.accounts.len()
-                );
-                // We expect 7 accounts from the CreateMachineV1Accounts struct
-                assert_eq!(
-                    instruction.accounts.len(),
-                    7,
-                    "Should have 7 individual accounts"
-                );
-            }
+            // We expect 7 accounts expanded from CreateMachineV1Accounts
+            println!("Accounts: {:#?}", instruction.accounts);
+            assert_eq!(
+                instruction.accounts.len(),
+                7,
+                "Accounts must be expanded from struct to 7 individual accounts"
+            );
shank-idl/tests/instructions.rs (1)

194-243: New tests follow established patterns correctly.

The three new test functions validate IDL generation for simple, standard, and complex accounts struct scenarios. Each test correctly:

  • Constructs the fixture path
  • Parses with optional_program_address() config
  • Compares against expected JSON using include_str!

The past review comment suggesting a helper function to reduce boilerplate remains a valid optional refactor for maintainability.

shank/tests/idl_generation_test.rs (1)

12-292: Comprehensive test coverage for IDL generation.

The test suite thoroughly validates ShankAccounts IDL generation across diverse scenarios:

  • Basic and complex account constraints
  • Optional accounts and signers
  • Custom lifetimes
  • Edge cases (empty struct, single account)
  • Indexing and field order preservation

All assertions are correct and the test coverage is excellent.

The past review comment suggesting a type alias (e.g., type AccountMeta = (u32, &'static str, bool, bool, bool, bool, Option<String>)) to reduce tuple-indexing noise remains a valid optional improvement for readability.

shank/tests/basic_context_test.rs (2)

38-39: Fix: Missing lifetime parameter in Context type annotation.

The type annotation is missing the required lifetime parameter for BasicTestAccounts.

Apply:

-    let ctx: Context<BasicTestAccounts, AccountInfo> =
-        BasicTestAccounts::context(&accounts);
+    let ctx: Context<BasicTestAccounts<'_>, AccountInfo<'_>> =
+        BasicTestAccounts::context(&accounts);

Or simply omit the type annotation:

-    let ctx: Context<BasicTestAccounts, AccountInfo> =
-        BasicTestAccounts::context(&accounts);
+    let ctx = BasicTestAccounts::context(&accounts);

46-48: Remove main() from integration test.

Integration tests run under Cargo's test harness; defining fn main() causes a conflict.

Apply:

-fn main() {
-    println!("Basic context test");
-}
shank/tests/expand/no_constraints.expanded.rs (1)

1-1: Remove unused import to avoid compiler warnings.

The use shank::ShankAccounts; import on Line 1 is not referenced in this expanded file and will trigger unused_imports warnings in stricter CI environments.

Apply:

-use shank::ShankAccounts;

Alternatively, if the import must remain for fixture consistency, add:

+#[allow(unused_imports)]
 use shank::ShankAccounts;
shank-macro-impl/src/accounts/account_struct.rs (1)

7-9: Derive Debug on AccountStruct for easier diagnostics

Deriving Debug on AccountStruct makes it much easier to inspect field state during tests or when debugging macro expansions.

- pub struct AccountStruct {
+ #[derive(Debug)]
+ pub struct AccountStruct {
     pub fields: Vec<AccountField>,
 }

[ suggest_optional_refactor]

shank-idl/src/idl_instruction.rs (1)

69-170: Fix discriminant bound off‑by‑one and deduplicate arg parsing

Two issues in TryFrom<InstructionVariant>:

  1. Discriminant off‑by‑one (also present in try_from_with_accounts below):
-        ensure!(
-            discriminant < u8::MAX as usize,
+        ensure!(
+            discriminant <= u8::MAX as usize,
             anyhow!(
                 "Instruction variant discriminants have to be <= u8::MAX ({}), \
                     but the discriminant of variant '{}' is {}",
                 u8::MAX,
                 ident,
                 discriminant
             )
         );

Apply the same change in the ensure! at lines 272–281 so both constructors accept 255 as valid while still guarding the cast to u8.

  1. Duplicate arg‑parsing logic:

The InstructionVariantFieldsVec<IdlField> logic here is essentially identical to the block in try_from_with_accounts. Extracting a small helper, e.g.:

fn parse_args(field_tys: InstructionVariantFields) -> Result<Vec<IdlField>, Error> { ... }

and calling it from both places will keep behavior in sync and reduce maintenance risk.

shank-macro-impl/src/accounts/account_field.rs (4)

15-21: Avoid unwrap() on field.ident; emit a proper compile error for unnamed fields

field.ident.clone().unwrap() will panic for tuple/unnamed fields. For a derive macro, this should be a syn::Error instead of a panic.

-    pub fn from_field(field: &Field) -> Result<Self> {
-        let name = field.ident.clone().unwrap();
+    pub fn from_field(field: &Field) -> Result<Self> {
+        let name = field.ident.clone().ok_or_else(|| {
+            syn::Error::new_spanned(field, "ShankAccounts requires named struct fields")
+        })?;

23-51: Validate attribute names more clearly and support future aliases safely

The core parsing looks fine, but two improvements will make diagnostics and future extension safer:

  1. Unknown attributes: printing path.get_ident() yields Option<Ident> in the message.
-use quote::quote;
+use quote::{quote, ToTokens};
@@
-                                } else {
-                                    return Err(syn::Error::new_spanned(
-                                        &path,
-                                        format!(
-                                            "Unknown account attribute: {:?}",
-                                            path.get_ident()
-                                        ),
-                                    ));
-                                }
+                                } else {
+                                    return Err(syn::Error::new_spanned(
+                                        &path,
+                                        format!(
+                                            "Unknown account attribute: {}",
+                                            path.to_token_stream()
+                                        ),
+                                    ));
+                                }
  1. Consider explicitly documenting here (or in the ShankAccounts docs) which aliases are supported (mut, writ, w, etc.) so users know what’s intentional vs. typo.

61-72: Reject non‑string desc / description / docs literals instead of silently ignoring

Currently, non‑string literals for desc are ignored, which hides mistakes:

#[account(desc = 123)] // silently dropped

Treat this as a hard error so users get immediate feedback:

-                            )) => {
-                                if path.is_ident("desc")
-                                    || path.is_ident("description")
-                                    || path.is_ident("docs")
-                                {
-                                    if let Lit::Str(s) = lit {
-                                        desc = Some(s.value());
-                                    }
-                                }
-                            }
+                            )) => {
+                                if path.is_ident("desc")
+                                    || path.is_ident("description")
+                                    || path.is_ident("docs")
+                                {
+                                    match lit {
+                                        Lit::Str(s) => desc = Some(s.value()),
+                                        other => {
+                                            return Err(syn::Error::new_spanned(
+                                                other,
+                                                "desc/docs/description must be a string literal",
+                                            ));
+                                        }
+                                    }
+                                }
+                            }

15-21: Enforce #[account(optional)] / #[account(optional_signer)] only on Option<...> fields

Right now optional and optional_signer are just booleans; they don’t validate the field type. That allows:

#[account(optional)]
pub bad: &'info AccountInfo<'info>;

which will be surprising at runtime when the account is actually missing. Add a type check so these attributes only apply to Option<...> fields:

-use syn::{Field, Ident, Lit, Meta, MetaNameValue, NestedMeta, Result};
+use syn::{Field, Ident, Lit, Meta, MetaNameValue, NestedMeta, Result, Type, PathArguments};
@@
     pub fn from_field(field: &Field) -> Result<Self> {
-        let name = field.ident.clone().unwrap();
+        let name = field.ident.clone().ok_or_else(|| {
+            syn::Error::new_spanned(field, "ShankAccounts requires named struct fields")
+        })?;
@@
-        // Validate mutually exclusive attributes
-        if signer && optional_signer {
+        // Validate mutually exclusive attributes
+        if signer && optional_signer {
             return Err(syn::Error::new_spanned(
                 &name,
                 "Account cannot be both 'signer' and 'optional_signer'",
             ));
         }
+
+        // Validate that optional flags match Option<T> field types
+        if optional || optional_signer {
+            let is_option = matches!(
+                &field.ty,
+                Type::Path(tp)
+                    if tp.path.segments.last().map(|s| s.ident == "Option").unwrap_or(false)
+                        && matches!(
+                            tp.path.segments.last().unwrap().arguments,
+                            PathArguments::AngleBracketed(_)
+                        )
+            );
+
+            if !is_option {
+                return Err(syn::Error::new_spanned(
+                    &field.ty,
+                    "#[account(optional)] and #[account(optional_signer)] require the field type to be Option<...>",
+                ));
+            }
+        }

This matches the attribute’s intent and prevents subtle runtime issues.

shank/tests/context_runtime_test.rs (1)

93-126: Add assertions for optional_signer flags in ComplexAccounts metadata

ComplexAccounts<'info> includes an #[account(optional_signer, ...)] field, but the test only checks lengths and first-field names. Prior review recommended asserting the optional-signer semantics to guard against regressions in __shank_accounts() generation.
[ suggest_recommended_refactor ]

You could extend the test like this:

     let simple_metadata = SimpleAccounts::__shank_accounts();
     let complex_metadata = ComplexAccounts::__shank_accounts();
@@
     assert_eq!(simple_metadata.len(), 1);
     assert_eq!(complex_metadata.len(), 4);
@@
     assert_eq!(simple_metadata[0].1, "user");
     assert_eq!(complex_metadata[0].1, "authority");
+
+    // Verify optional_signer semantics
+    assert_eq!(complex_metadata[2].1, "optional_signer");
+    assert_eq!(complex_metadata[2].3, true); // signer
+    assert_eq!(complex_metadata[2].4, true); // optional_signer flag
+    assert_eq!(complex_metadata[2].5, true); // optional implied

Adjust indices if the field order changes.

shank-macro-impl/src/accounts/mod.rs (1)

51-71: Consider matching __shank_accounts Vec construction with existing expand tests

__shank_accounts() currently builds its Vec with vec![ #account_list ], while existing .expanded.rs fixtures use the boxed-array into_vec pattern (Box::new([ .. ]).into_vec()).
[ suggest_optional_refactor ]

If your expand tests assert textual equality, you may want to align:

-                pub fn __shank_accounts() -> Vec<(u32, &'static str, bool, bool, bool, bool, Option<String>)> {
-                    vec![
-                        #account_list
-                    ]
-                }
+                pub fn __shank_accounts() -> Vec<(u32, &'static str, bool, bool, bool, bool, Option<String>)> {
+                    <[_]>::into_vec(
+                        #[rustc_box]
+                        ::alloc::boxed::Box::new([
+                            #account_list
+                        ]),
+                    )
+                }

If tests compare behavior only, the current vec! form is fine.

shank-idl/src/accounts_extraction.rs (3)

30-61: Doc comment still describes calling __shank_accounts rather than parsing attributes

This helper now parses struct fields and their #[account(...)] attributes; it does not call any generated __shank_accounts() method. The doc comment is misleading and previously flagged.

Consider updating it along these lines:

-/// Extract individual accounts from a ShankAccounts struct by calling its __shank_accounts method
+/// Extract individual accounts from a ShankAccounts struct by parsing its #[account(...)] field attributes

77-86: Option detection should use the last path segment, not the first

has_option_type checks path.segments.first() == "Option", which fails for fully-qualified types like std::option::Option<T>. Using the last segment is more robust and was previously suggested.

You can tighten this with:

-    let has_option_type = if let Type::Path(type_path) = &field.ty {
-        if let Some(segment) = type_path.path.segments.first() {
-            segment.ident == "Option"
-        } else {
-            false
-        }
-    } else {
-        false
-    };
+    let has_option_type = matches!(&field.ty, Type::Path(type_path)
+        if type_path
+            .path
+            .segments
+            .last()
+            .map_or(false, |seg| seg.ident == "Option"));

88-116: String-based #[account(...)] parsing is brittle; use syn’s nested-meta API

Attribute handling via tokens_str.contains(...) is whitespace/order-sensitive and can misfire on substrings in descriptions. Prior review already called this out and suggested Attribute::parse_nested_meta.

A more robust approach (adapt paths for your syn version) would be:

-    // Parse #[account(...)] attributes
-    for attr in &field.attrs {
-        if attr.path.is_ident("account") {
-            // Use a simple string-based parsing approach for now
-            // This is a simplified version - in production we'd want more robust parsing
-            let tokens_str = attr.tokens.to_string();
-
-            // Simple parsing of common attributes
-            if tokens_str.contains("mut") || tokens_str.contains("writable") {
-                writable = true;
-            }
-            if tokens_str.contains("signer") {
-                signer = true;
-            }
-            if tokens_str.contains("optional_signer") {
-                optional_signer = true;
-            } else if tokens_str.contains("optional") {
-                optional = true;
-            }
-
-            // Extract description using simple regex-like approach
-            if let Some(desc_start) = tokens_str.find("desc = \"") {
-                let desc_content = &tokens_str[desc_start + 8..];
-                if let Some(desc_end) = desc_content.find('"') {
-                    desc = Some(desc_content[..desc_end].to_string());
-                }
-            }
-        }
-    }
+    // Parse #[account(...)] attributes robustly
+    for attr in &field.attrs {
+        // Adjust to attr.path / attr.path() depending on syn version in use.
+        if attr.path.is_ident("account") {
+            attr.parse_nested_meta(|meta| {
+                let key = meta
+                    .path
+                    .segments
+                    .last()
+                    .map(|s| s.ident.to_string())
+                    .unwrap_or_default();
+                match key.as_str() {
+                    // writable variants
+                    "mut" | "writable" | "write" | "writ" | "w" => writable = true,
+                    // signer variants
+                    "signer" | "sign" | "sig" | "s" => signer = true,
+                    // optional variants
+                    "optional_signer" => optional_signer = true,
+                    "optional" | "opt" | "option" => optional = true,
+                    // desc = "..."
+                    "desc" => {
+                        let val: shank_macro_impl::syn::LitStr = meta.value()?.parse()?;
+                        desc = Some(val.value());
+                    }
+                    _ => {}
+                }
+                Ok(())
+            })?;
+        }
+    }

Please double-check against your syn version and feature flags when adopting this.

shank/tests/final_context_test.rs (1)

96-98: Remove main() from integration test file

Integration tests are built with the test harness’ own main; defining your own fn main() in shank/tests/final_context_test.rs can conflict with the harness and is unnecessary.

You can simply drop it:

-fn main() {
-    println!("Final context test - context method is working!");
-}
+// No custom main() in integration tests; use #[test] functions instead.
shank/tests/expand/complex_constraints.rs (1)

34-34: Remove main() from expand test fixture

As with other test files, a top-level fn main() in tests/expand/complex_constraints.rs is unnecessary and can clash with the test harness.

Suggested change:

-fn main() {}
+// No main() in expand tests; expansion is verified via macrotest.
shank/tests/context_api_working_test.rs (1)

117-119: Remove main() from integration test crate

tests/context_api_working_test.rs is compiled as an integration test crate with a test harness–generated main(). Defining another fn main() will conflict and prevent tests from compiling; drop this function so the file only contains #[test] functions.

-fn main() {
-    println!("Context API working test");
-}
+// No `main()` in integration tests; test harness provides the entry point.
shank/tests/accounts_macro_test.rs (1)

13-80: Consider minimally asserting on generated APIs to catch regressions

These tests currently only ensure the ShankAccounts derive compiles. To better guard against regressions (e.g., __shank_accounts/context no longer being emitted), consider adding a trivial use of a generated symbol, for example:

let _ = CreateVaultAccounts::__shank_accounts();

or

let _ctx_fn: fn(&[AccountInfo]) -> shank::Context<_, _> = CreateVaultAccounts::context;

Same applies to ComplexAccounts / AlternativeAccounts.

shank/tests/context_api_test.rs (1)

15-59: Align authority attribute with optional_signer semantics and assert description field

In CreateMachineV1Accounts<'info>:

  • The comment and later assertion (metadata[4] treated as an optional signer) imply optional_signer semantics, but the field is annotated as #[account(optional, signer, ...)]. Prefer using the combined form to exercise that code path explicitly:
-        #[account(
-            optional,
-            signer,
-            desc = "The authority signing for account creation"
-        )]
+        #[account(
+            optional_signer,
+            desc = "The authority signing for account creation"
+        )]
         pub authority: Option<&'info AccountInfo<'info>>,
  • To better lock the tuple layout, add a description assertion for the first entry:
     assert_eq!(metadata[0].1, "machine");
     assert_eq!(metadata[0].2, true); // mut
     assert_eq!(metadata[0].3, true); // signer
+    assert_eq!(
+        metadata[0].6.as_deref(),
+        Some("The new machine asset account")
+    );
shank/tests/expand/custom_lifetime.expanded.rs (1)

1-1: Remove unused ShankAccounts import from expanded test.

This expanded file no longer uses the derive macro; keeping use shank::ShankAccounts; just introduces an unused-import warning. It’s safe to drop.

-use shank::ShankAccounts;
shank/tests/expand/optional_accounts.expanded.rs (1)

1-1: Drop unused ShankAccounts import from expanded optional-accounts test.

The derive is already expanded; this use shank::ShankAccounts; is unused and can be removed to avoid warnings.

-use shank::ShankAccounts;
shank/tests/context_usage_test.rs (1)

41-73: Fix &mut <vec>.clone() when constructing AccountInfo to avoid borrowing temporaries.

Across these tests you build data buffers as vec![] and then pass &mut <buffer>.clone() into AccountInfo::new. That clones into a temporary and borrows it mutably for the account’s data, instead of borrowing a stable buffer variable.

This is both unnecessary and potentially unsound; use named mutable buffers and pass them directly.

Apply the pattern below to all four test cases (required accounts, optional account, program_id placeholder, not-enough-accounts):

-        let payer_data = vec![];
-        let mint_data = vec![];
-        let system_data = vec![];
+        let mut payer_data: Vec<u8> = vec![];
+        let mut mint_data: Vec<u8> = vec![];
+        let mut system_data: Vec<u8> = vec![];
@@
-            &mut payer_lamports,
-            &mut payer_data.clone(),
+            &mut payer_lamports,
+            &mut payer_data,
@@
-            &mut mint_lamports,
-            &mut mint_data.clone(),
+            &mut mint_lamports,
+            &mut mint_data,
@@
-            &mut system_lamports,
-            &mut system_data.clone(),
+            &mut system_lamports,
+            &mut system_data,

Extend the same change to any additional *_data buffers in the other tests (metadata_data, program_data, etc.): declare them as let mut ...: Vec<u8> = vec![]; and pass &mut ... instead of &mut ... .clone().

Also applies to: 103-151, 177-225, 248-271

shank-macro-impl/src/instruction/account_attrs.rs (1)

199-225: Reject multiple #[accounts(...)] attributes instead of silently taking the first.

AccountsSource::try_from currently uses .find(...), so if a variant accidentally has more than one #[accounts(...)] attribute, you’ll just pick the first and ignore the rest. That’s ambiguous and hard to debug.

Consider collecting all such attrs, erroring on duplicates, and then using the single allowed one, e.g.:

-        // First check for #[accounts(StructName)] attribute
-        let accounts_struct_attr = attrs
-            .iter()
-            .find(|attr| AccountsSource::is_accounts_struct_attr(attr));
-
-        if let Some(attr) = accounts_struct_attr {
+        // First check for #[accounts(StructName)] attribute(s)
+        let struct_attrs: Vec<&Attribute> = attrs
+            .iter()
+            .filter(|attr| AccountsSource::is_accounts_struct_attr(attr))
+            .collect();
+
+        if let Some(attr) = struct_attrs.first() {
+            if struct_attrs.len() > 1 {
+                return Err(ParseError::new_spanned(
+                    struct_attrs[1],
+                    "Multiple #[accounts(...)] attributes found; only one is allowed",
+                ));
+            }
             // Can't have both #[accounts(StructName)] and #[account(...)] attributes
-            let has_inline_accounts = attrs.iter().any(|attr| {
-                InstructionAccount::is_account_attr(attr).is_some()
-            });
+            let has_inline_accounts = attrs
+                .iter()
+                .any(|attr| InstructionAccount::is_account_attr(attr).is_some());

The rest of the logic (struct vs. inline exclusivity, and falling back to inline accounts) can stay as-is.

shank/examples/accounts_struct_example.rs (2)

48-51: Clarify that optional accounts become Option<...> in the generated context.

Here new_authority is a PhantomData<()> placeholder, but at runtime the generated context will expose it as an Option<&AccountInfo<'info>>. Without a note, readers might copy the PhantomData pattern into real programs.

-    /// Optional new authority
-    #[account(optional, desc = "Optional new authority")]
-    pub new_authority: std::marker::PhantomData<()>,
+    /// Optional new authority
+    #[account(optional, desc = "Optional new authority")]
+    pub new_authority: std::marker::PhantomData<()>, // In generated context: Option<&AccountInfo<'info>>

120-124: Soften “complete replacement” wording for ShankAccounts vs ShankContext.

The example currently claims ShankAccounts is a “complete replacement” for the older patterns, but docs/guides still reference ShankContext. To avoid over-claiming and confusing users, this should be phrased as an alternative/more ergonomic option rather than a full replacement.

-    println!("ShankAccounts is designed to be a complete replacement for:");
+    println!("ShankAccounts provides a more ergonomic alternative to:");
@@
-    println!("- ShankContext derive macro for context generation");
+    println!("- ShankContext derive macro for context generation (still supported)");
shank/tests/instruction_integration_test.rs (1)

12-36: Hoist all pub account/Instruction types out of tests (same issue as prior review).

Each #[test] defines pub struct / pub enum items inside the function body:

  • CreateTokenAccounts<'info> / TokenInstruction
  • InitializeAccounts<'info> / TransferAccounts<'info> / CloseAccounts<'info> / ProgramInstruction
  • BasicAccounts<'info> / CreateAccounts<'info> / UpdateAccounts<'info> / AdminAccounts<'info> / ComplexProgram
  • ComplexDataAccounts<'info> / DataInstruction
  • SharedAccounts<'info> / SharedInstruction

This pattern was already flagged earlier: Rust does not allow pub items at function scope, and the derives generate additional pub APIs, so these tests will either not compile or rely on undefined behavior of the macro expansions.

Apply the same fix suggested previously:

  • Move all these derived types to module scope (top of the file), keeping them pub.
  • Leave the test bodies to call __shank_accounts() / use the enums, without redefining the types inside the functions.

Example pattern for the first test:

-use shank::{ShankAccounts, ShankInstruction};
-
-// Mock AccountInfo for testing
-pub struct AccountInfo<'info> { /* ... */ }
-
-pub const ID: [u8; 32] = [1; 32];
-
-#[test]
-fn test_accounts_with_instruction_integration() {
-    // Define accounts struct
-    #[derive(ShankAccounts)]
-    pub struct CreateTokenAccounts<'info> { /* ... */ }
-
-    // Use accounts struct in instruction
-    #[derive(ShankInstruction)]
-    pub enum TokenInstruction { /* ... */ }
-
-    let accounts_metadata = CreateTokenAccounts::__shank_accounts();
-    // ...
-}
+use shank::{ShankAccounts, ShankInstruction};
+
+// Mock AccountInfo for testing
+pub struct AccountInfo<'info> { /* ... */ }
+
+pub const ID: [u8; 32] = [1; 32];
+
+#[derive(ShankAccounts)]
+pub struct CreateTokenAccounts<'info> { /* ... */ }
+
+#[derive(ShankInstruction)]
+pub enum TokenInstruction { /* ... */ }
+
+#[test]
+fn test_accounts_with_instruction_integration() {
+    let accounts_metadata = CreateTokenAccounts::__shank_accounts();
+    // ...
+}

Repeat this hoisting for the other account structs and instruction enums defined in the remaining tests.

After hoisting, re-check with:

#!/bin/bash
rg -nP '^\s*pub\s+(struct|enum)\s' shank/tests/instruction_integration_test.rs -n -C2
# Expect all matches at module scope, outside #[test] fns.

Also applies to: 53-103, 127-193, 215-243, 250-273

Comment on lines +73 to +141
fn gen_context_impl(&self) -> TokenStream {
let ident = &self.ident;
let (impl_gen, type_gen, where_clause) = self.generics.split_for_impl();
let fields = &self.account_struct.fields;

// All accounts must be provided, but optional ones can be program_id placeholders
let expected_accounts = fields.len();
let _total_accounts = fields.len();

// Use the same lifetime as the struct, or skip context method if no lifetimes
let context_method = if let Some(lifetime) =
self.generics.lifetimes().next()
{
let lifetime_ident = &lifetime.lifetime;

let account_assignments =
fields.iter().enumerate().map(|(idx, field)| {
let field_name = &field.name;
if field.optional || field.optional_signer {
quote! {
#field_name: if accounts[#idx].key == &crate::ID {
None
} else {
Some(&accounts[#idx])
}
}
} else {
quote! {
#field_name: &accounts[#idx]
}
}
});

quote! {
/// Create a context from a slice of accounts
///
/// This method parses the accounts according to the struct definition
/// and returns a Context containing the account struct.
///
/// Optional accounts are determined by checking if the account key
/// equals the program ID (crate::ID). If so, they are set to None, otherwise Some.
pub fn context(
accounts: &#lifetime_ident [AccountInfo<#lifetime_ident>]
) -> ::shank::Context<#lifetime_ident, Self, AccountInfo<#lifetime_ident>> {
if accounts.len() < #expected_accounts {
panic!("Expected at least {} accounts, got {}", #expected_accounts, accounts.len());
}

let account_struct = Self {
#(#account_assignments,)*
};

::shank::Context {
accounts: account_struct,
remaining_accounts: &accounts[#expected_accounts..],
}
}
}
} else {
// No lifetime parameters, don't generate context method
quote! {}
};

quote! {
impl #impl_gen #ident #type_gen #where_clause {
#context_method
}
}
}
Copy link

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

context() API: panic behavior, crate::ID coupling, and optional account handling

The generated context() currently:

  • Panics on short accounts slices instead of returning a Result.
  • Keys optional-ness off accounts[idx].key == &crate::ID, hard-wiring a crate-level ID sentinel.
  • Always requires at least one account per field (expected_accounts = fields.len()), even for optional fields.
  • Slices remaining_accounts from expected_accounts, ignoring how many entries were actually consumed.

This matches the current expansion fixtures, but it’s at odds with common Solana patterns (using program_id and ProgramError::NotEnoughAccountKeys) and makes the API brittle for callers.

Consider evolving the API (likely in a follow-up change) along these lines:

  • Gate on the Solana feature when you start depending on solana_program types:
#[cfg(feature = "solana-program")]
pub fn context(
    accounts: &'info [AccountInfo<'info>],
    program_id: &solana_program::pubkey::Pubkey,
) -> core::result::Result<
    ::shank::Context<'info, Self, AccountInfo<'info>>,
    solana_program::program_error::ProgramError,
> { /* ... */ }
  • Compute min_required as the count of non-optional, non-optional_signer fields, and return ProgramError::NotEnoughAccountKeys when accounts.len() < min_required instead of panicking.
  • For optional fields, bounds-check idx < accounts.len() and treat out-of-range indices as None.
  • Compare against program_id (or another explicit sentinel parameter) rather than crate::ID, so the macro doesn’t assume a global constant exists in the crate using it.
  • Use used = core::cmp::min(accounts.len(), total_accounts) and slice remaining_accounts from used to avoid accidental OOB if future changes relax the length checks.

This would make the generated API safer and closer to Solana runtime conventions; please coordinate with existing call sites and tests before changing the signature.


I'll verify the review comment by examining the implementation, tests, and call sites. Let me gather the necessary information.


🏁 Script executed:

#!/bin/bash
# 1. Check the actual implementation at the specified lines
echo "=== Checking current implementation ==="
sed -n '73,141p' shank-macro-impl/src/accounts/mod.rs

echo -e "\n=== Looking for context() tests and fixtures ==="
fd -e rs -x grep -l "context()" {} \; | head -20

echo -e "\n=== Checking Cargo.toml for dependencies and features ==="
cat Cargo.toml | head -50

Length of output: 3401


🏁 Script executed:

#!/bin/bash
# 2. Search for context() method usages in the codebase
echo "=== Finding context() usages ==="
rg -nC 3 'context\s*\(' shank-macro-impl/src --type rs | head -40

echo -e "\n=== Looking for ProgramError usage patterns ==="
rg -nC 2 'ProgramError|NotEnoughAccountKeys' --type rs | head -30

echo -e "\n=== Checking for Result<> patterns in similar macros ==="
rg -nC 2 'fn.*->.*Result' shank-macro-impl/src --type rs | head -30

Length of output: 220


🏁 Script executed:

#!/bin/bash
# 3. Look for tests specific to context generation
echo "=== Finding tests for context method ==="
fd -e rs -path '*/test*' -o -path '*/tests/*' | xargs grep -l "context" 2>/dev/null | head -10

echo -e "\n=== Checking test fixtures ==="
fd -e rs -path '*/fixtures/*' -o -path '*/snapshots/*' | head -20

# 4. Check if shank crate has solana_program as a feature
echo -e "\n=== Checking root Cargo.toml for features ==="
fd Cargo.toml | xargs grep -A 10 '\[features\]' | head -30

Length of output: 572


🏁 Script executed:

#!/bin/bash
# 5. Look at the complete gen_context_impl function and surrounding context
echo "=== Full context around gen_context_impl ==="
sed -n '50,150p' shank-macro-impl/src/accounts/mod.rs

Length of output: 3578


🏁 Script executed:

#!/bin/bash
# Look at test files to understand current usage
echo "=== Content of basic_context_test.rs ==="
cat shank/tests/basic_context_test.rs 2>/dev/null | head -100

echo -e "\n=== Content of context_issue_demo.rs ==="
cat shank/tests/context_issue_demo.rs 2>/dev/null | head -100

echo -e "\n=== Content of working_context_demo.rs ==="
cat shank/tests/working_context_demo.rs 2>/dev/null | head -100

Length of output: 4223


🏁 Script executed:

#!/bin/bash
# Search for context() calls across all .rs files
echo "=== All context() method calls ==="
find . -name "*.rs" -type f -exec grep -n "\.context(" {} + 2>/dev/null | head -40

echo -e "\n=== Checking if crate::ID is referenced in tests ==="
find . -name "*.rs" -type f -exec grep -n "crate::ID" {} + 2>/dev/null | head -20

Length of output: 2572


🏁 Script executed:

#!/bin/bash
# Check the structure and what shank crate exports
echo "=== shank/src/lib.rs or main entry ==="
head -80 shank/src/lib.rs 2>/dev/null || head -80 shank/src/main.rs 2>/dev/null

echo -e "\n=== Checking for ID definition in shank crate ==="
find shank/src -name "*.rs" -type f -exec grep -l "^pub.*ID\|pub const ID" {} + 2>/dev/null

echo -e "\n=== Looking for panic! in current generated code in tests ==="
find . -path "*/target" -prune -o -name "*.rs" -type f -exec grep -n "panic.*Expected at least" {} + 2>/dev/null

Length of output: 554


🏁 Script executed:

#!/bin/bash
# Check if there are integration tests that would break with Result signature
echo "=== Enhanced accounts test ==="
cat shank/tests/enhanced_accounts_test.rs 2>/dev/null | head -150

echo -e "\n=== Looking for programs that use context() ==="
cat shank/examples/context_usage_example.rs 2>/dev/null | head -100

Length of output: 7096


🏁 Script executed:

#!/bin/bash
# Check dependencies in shank-macro-impl/Cargo.toml
echo "=== shank-macro-impl dependencies ==="
cat shank-macro-impl/Cargo.toml

echo -e "\n=== shank-macro Cargo.toml ==="
cat shank-macro/Cargo.toml 2>/dev/null | head -40

Length of output: 1155


🏁 Script executed:

#!/bin/bash
# Check macro expansion snapshots to see what is ACTUALLY generated
echo "=== Checking macro expansion snapshot ==="
ls -la shank/tests/expand/ 2>/dev/null | head -20

echo -e "\n=== Complex constraints expanded output (shows generated context) ==="
head -150 shank/tests/expand/complex_constraints.expanded.rs 2>/dev/null

echo -e "\n=== Optional accounts expanded ==="
sed -n '60,90p' shank/tests/expand/optional_accounts.expanded.rs 2>/dev/null

Length of output: 6617


🏁 Script executed:

#!/bin/bash
# Check render_context.rs since it was found in search
echo "=== shank-render/src/context/render_context.rs ==="
cat shank-render/src/context/render_context.rs 2>/dev/null | head -150

echo -e "\n=== Checking what's imported/re-exported in shank-macro ==="
grep -n "render_context\|Context" shank-macro/src/lib.rs 2>/dev/null

Length of output: 2972


🏁 Script executed:

#!/bin/bash
# Check test configuration - are test files actually run?
echo "=== Cargo.toml [[test]] sections ==="
grep -A 5 "\[\[test\]\]" shank/Cargo.toml 2>/dev/null

echo -e "\n=== Check if example files are compiled ==="
grep -A 5 "\[\[example\]\]" shank/Cargo.toml 2>/dev/null

echo -e "\n=== Are context tests in lib or separate ==="
head -20 shank/src/lib.rs 2>/dev/null

# Check if generated code snapshot is what macro currently outputs
echo -e "\n=== First 50 lines of optional_accounts.expanded (shows actual output) ==="
head -50 shank/tests/expand/optional_accounts.expanded.rs 2>/dev/null

Length of output: 2078


🏁 Script executed:

#!/bin/bash
# Verify the actual function used - is it from accounts/mod.rs or render_context.rs?
echo "=== Searching for where gen_context_impl is called ==="
grep -rn "gen_context_impl\|render_context" shank-macro/src shank-macro-impl/src 2>/dev/null

echo -e "\n=== Check shank-render for context generation ==="
head -100 shank-render/src/lib.rs 2>/dev/null

Length of output: 452


Verify implementation concerns in context() method—all four identified issues are confirmed

The review comment accurately identifies real issues in the current context() implementation for ShankAccounts:

  1. Panic instead of Result: The macro expansion snapshots confirm that generated code calls panic!("Expected at least {} accounts, got {}", ...) when accounts.len() < expected_accounts. This is visible in shank/tests/expand/complex_constraints.expanded.rs (lines ~125-135).

  2. crate::ID coupling: The macro hardcodes accounts[idx].key == &crate::ID for optional field detection, confirmed in all .expanded.rs snapshots. The shank crate does not define ID itself; users must define it in their program, making this coupling implicit and fragile.

  3. All fields required even if optional: expected_accounts = fields.len() includes optional fields, so the generated code panics if fewer than all field slots are provided. Optional fields should only require bounds-checking, not panic-on-short-slice.

  4. Unsafe remaining_accounts slicing: The code slices from expected_accounts without validating the actual count of consumed entries, risking out-of-bounds access if optional field handling changes.

Additionally, an alternate implementation exists in shank-render/src/context/render_context.rs that already returns Result<Context, ProgramError::NotEnoughAccountKeys>, but it still relies on crate::ID and has the same optional field issues.

The test files (context_usage_example.rs, enhanced_accounts_test.rs) show the desired API signature but don't match the current implementation, indicating the concerns are well-founded aspirations for API improvement.

🤖 Prompt for AI Agents
In shank-macro-impl/src/accounts/mod.rs around lines 73–141, the generated
context() currently panics on short slices, hardcodes crate::ID for
optional-account detection, treats optional fields as required
(expected_accounts = fields.len()), and unsafely slices remaining_accounts;
change the generated method to return Result<::shank::Context<...>,
ProgramError> rather than panicking, accept a program_id parameter (or take it
from a passed-in Context/trait) and use program_id for optional checks instead
of crate::ID, compute expected_accounts as the number of non-optional fields
(only assert/return Err when accounts.len() < required_count), build each field
using safe indexing (e.g., check length before accessing each index or use get
and map) so optional fields can be None when their key equals program_id, and
compute consumed = number of assigned slots then safely slice remaining_accounts
using checked bounds before constructing and returning Ok(context).

Comment on lines +134 to +176
let (accounts_source, accounts, strategies) =
match IdlInstruction::try_from(attrs) {
Ok(idl_ix) => {
field_tys = idl_ix.to_instruction_fields(ident.clone());
let accounts = idl_ix.to_accounts(ident.clone());
(
AccountsSource::Inline(accounts.clone()),
accounts.0,
InstructionStrategies(
HashSet::<InstructionStrategy>::new(),
),
)
}
Err(_) => {
let accounts_source = AccountsSource::try_from(attrs)?;
let accounts = match &accounts_source {
AccountsSource::Inline(accs) => accs.0.clone(),
AccountsSource::Struct(path) => {
// Create placeholder accounts based on the struct name
// The actual resolution will happen during IDL generation
// For now, create a single placeholder account that indicates this uses a struct
vec![InstructionAccount {
ident: ident.clone(),
index: Some(0),
name: format!(
"__accounts_struct_{}",
path.get_ident().unwrap_or(&ident)
),
writable: false,
signer: false,
optional_signer: false,
desc: Some(format!(
"Accounts defined by struct: {}",
path.get_ident().unwrap_or(&ident)
)),
optional: false,
}]
}
};
let strategies: InstructionStrategies = attrs.into();
(accounts_source, accounts, strategies)
}
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't drop instruction strategy attributes

When IdlInstruction::try_from(attrs) succeeds we now hard-code strategies to an empty set, so any additional instruction-level attributes (e.g. #[legacy_optional_accounts]) silently stop working for those variants. Please keep parsing strategies from attrs in this branch just like the fallback path, so downstream logic still sees them.

                     (
                         AccountsSource::Inline(accounts.clone()),
                         accounts.0,
-                        InstructionStrategies(
-                            HashSet::<InstructionStrategy>::new(),
-                        ),
+                        InstructionStrategies::from(attrs),
                     )
📝 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
let (accounts_source, accounts, strategies) =
match IdlInstruction::try_from(attrs) {
Ok(idl_ix) => {
field_tys = idl_ix.to_instruction_fields(ident.clone());
let accounts = idl_ix.to_accounts(ident.clone());
(
AccountsSource::Inline(accounts.clone()),
accounts.0,
InstructionStrategies(
HashSet::<InstructionStrategy>::new(),
),
)
}
Err(_) => {
let accounts_source = AccountsSource::try_from(attrs)?;
let accounts = match &accounts_source {
AccountsSource::Inline(accs) => accs.0.clone(),
AccountsSource::Struct(path) => {
// Create placeholder accounts based on the struct name
// The actual resolution will happen during IDL generation
// For now, create a single placeholder account that indicates this uses a struct
vec![InstructionAccount {
ident: ident.clone(),
index: Some(0),
name: format!(
"__accounts_struct_{}",
path.get_ident().unwrap_or(&ident)
),
writable: false,
signer: false,
optional_signer: false,
desc: Some(format!(
"Accounts defined by struct: {}",
path.get_ident().unwrap_or(&ident)
)),
optional: false,
}]
}
};
let strategies: InstructionStrategies = attrs.into();
(accounts_source, accounts, strategies)
}
};
let (accounts_source, accounts, strategies) =
match IdlInstruction::try_from(attrs) {
Ok(idl_ix) => {
field_tys = idl_ix.to_instruction_fields(ident.clone());
let accounts = idl_ix.to_accounts(ident.clone());
(
AccountsSource::Inline(accounts.clone()),
accounts.0,
InstructionStrategies::from(attrs),
)
}
Err(_) => {
let accounts_source = AccountsSource::try_from(attrs)?;
let accounts = match &accounts_source {
AccountsSource::Inline(accs) => accs.0.clone(),
AccountsSource::Struct(path) => {
// Create placeholder accounts based on the struct name
// The actual resolution will happen during IDL generation
// For now, create a single placeholder account that indicates this uses a struct
vec![InstructionAccount {
ident: ident.clone(),
index: Some(0),
name: format!(
"__accounts_struct_{}",
path.get_ident().unwrap_or(&ident)
),
writable: false,
signer: false,
optional_signer: false,
desc: Some(format!(
"Accounts defined by struct: {}",
path.get_ident().unwrap_or(&ident)
)),
optional: false,
}]
}
};
let strategies: InstructionStrategies = attrs.into();
(accounts_source, accounts, strategies)
}
};
🤖 Prompt for AI Agents
In shank-macro-impl/src/instruction/instruction.rs around lines 134 to 176, the
match arm for Ok(idl_ix) currently hard-codes strategies to an empty set which
drops any instruction-level attributes; instead parse strategies from attrs the
same way as the Err branch (e.g. let strategies: InstructionStrategies =
attrs.into();) and return that strategies value in the tuple so downstream logic
still receives any instruction strategy attributes.

Comment on lines +361 to +453
// -----------------
// #[derive(ShankAccounts)]
// -----------------

/// Annotates a _struct_ that defines accounts for an instruction in a similar way to Anchor.
///
/// This is designed as a complete replacement for both:
/// - The `#[account]` attribute system on instruction enums
/// - The `ShankContext` derive macro for context generation
///
/// Instead of annotating instruction variants directly, you define a separate struct
/// that contains all accounts with their constraints. This generates both IDL metadata
/// and runtime context handling code for type-safe account access.
///
/// # Field Attributes
///
/// Each field in the struct represents an account and can be annotated with attributes:
///
/// - `#[account(writable)]` or `#[account(mut)]` - The account is writable
/// - `#[account(signer)]` - The account must sign the transaction
/// - `#[account(optional_signer)]` - The account may optionally sign
/// - `#[account(optional)]` - The account is optional (defaults to program_id when not provided)
/// - `#[account(desc = "...")]` - Description of the account for documentation
///
/// # Example
///
/// ```ignore
/// use shank::ShankAccounts;
///
/// #[derive(ShankAccounts)]
/// pub struct CreateVaultAccounts {
/// #[account(mut, desc = "Initialized fractional share mint")]
/// pub fraction_mint: std::marker::PhantomData<()>,
///
/// #[account(mut, desc = "Initialized redeem treasury")]
/// pub redeem_treasury: std::marker::PhantomData<()>,
///
/// #[account(mut, desc = "Fraction treasury")]
/// pub fraction_treasury: std::marker::PhantomData<()>,
///
/// #[account(mut, desc = "Uninitialized vault account")]
/// pub vault: std::marker::PhantomData<()>,
///
/// #[account(optional_signer, desc = "Authority on the vault")]
/// pub authority: std::marker::PhantomData<()>,
///
/// #[account(desc = "Token program")]
/// pub token_program: std::marker::PhantomData<()>,
/// }
///
/// // Then reference it in your instruction enum:
/// #[derive(ShankInstruction)]
/// pub enum VaultInstruction {
/// #[accounts(CreateVaultAccounts)]
/// InitVault(InitVaultArgs),
/// }
/// ```
///
/// # Generated Code
///
/// ShankAccounts generates:
/// 1. **IDL Metadata Methods** - For shank-idl to extract account information
/// 2. **Context Structs** - `{StructName}Context<'a>` with `AccountInfo<'a>` fields
/// 3. **Context Methods** - `{StructName}::context(program_id, accounts)` for validation
///
/// # Usage in Solana Programs
///
/// ```ignore
/// pub fn process_init_vault(
/// program_id: &Pubkey,
/// accounts: &[AccountInfo],
/// data: &[u8],
/// ) -> ProgramResult {
/// let ctx = CreateVaultAccounts::context(program_id, accounts)?;
///
/// // Type-safe access by name:
/// msg!("Vault: {}", ctx.vault.key);
/// msg!("Authority: {}", ctx.authority.key);
///
/// Ok(())
/// }
/// ```
///
/// Note: The field types don't affect IDL generation - ShankAccounts only processes
/// the `#[account(...)]` attributes. In real Solana programs, use `AccountInfo<'info>`
/// from `solana_program::account_info` for field types.
#[proc_macro_derive(ShankAccounts, attributes(account))]
pub fn shank_accounts(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
derive_accounts(input)
.unwrap_or_else(to_compile_error)
.into()
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

ShankAccounts derive entrypoint and docs are solid; consider explicitly calling out #[accounts(StructName)] here

The ShankAccounts proc‑macro entrypoint correctly delegates to derive_accounts and uses to_compile_error for error propagation. The docs give a good overview and examples.

You might consider adding a short subsection explicitly documenting the #[accounts(StructName)] usage on ShankInstruction variants (currently only shown in the example block) so users can quickly see how ShankAccounts ties into instruction enums.

🤖 Prompt for AI Agents
In shank-macro/src/lib.rs around lines 361 to 453, the ShankAccounts docs show
an example but do not explicitly call out the #[accounts(StructName)] usage on
ShankInstruction variants; add a short subsection immediately after the existing
example that briefly explains how to reference the generated accounts struct
from an instruction enum using #[accounts(StructName)] (one-sentence
description) and include the minimal example line (#[derive(ShankInstruction)]
pub enum X { #[accounts(CreateVaultAccounts)] InitVault(InitVaultArgs), }) to
make the linkage explicit for readers.

Comment on lines +5 to +20
// Mock program ID
pub const ID: [u8; 32] = [1; 32];

// Enable the solana-program feature for this example
#[cfg(feature = "solana-program")]
use solana_program::{
account_info::AccountInfo, entrypoint::ProgramResult, msg, pubkey::Pubkey,
};

// Mock AccountInfo when solana-program feature is not enabled
#[cfg(not(feature = "solana-program"))]
pub struct AccountInfo<'info> {
pub key: &'info [u8; 32],
pub data: &'info [u8],
pub owner: &'info [u8; 32],
}
Copy link

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

Fix ID type under solana-program to match AccountInfo::key.

With feature = "solana-program", AccountInfo<'info>::key is &Pubkey, but ID is defined as [u8; 32]. The generated optional-account logic compares accounts[i].key == &crate::ID, so this will fail to compile in Solana builds.

Define ID so its type matches the key type in each cfg branch, e.g.:

-// Mock program ID
-pub const ID: [u8; 32] = [1; 32];
+// Program ID sentinel used for optional-account detection
+#[cfg(feature = "solana-program")]
+solana_program::declare_id!("ShankExample11111111111111111111111111111");
+
+#[cfg(not(feature = "solana-program"))]
+pub const ID: [u8; 32] = [1; 32];

This keeps the mock [u8; 32] path for the non-Solana example while making crate::ID a Pubkey when using real solana_program::AccountInfo.

Please re-run cargo build --features solana-program --examples after this change to confirm the example and generated context() compile cleanly.






🏁 Script executed:

#!/bin/bash
# Find and examine the anchor_style_example.rs file
fd -t f "anchor_style_example.rs" --exec cat {} \;

Length of output: 4392


🏁 Script executed:

#!/bin/bash
# Search for how optional accounts are handled in code generation
# Look for patterns that compare account keys with ID
rg -nA5 -B5 "optional.*account\|key.*ID\|Option.*AccountInfo" --type rs -g "!examples" 2>/dev/null | head -100

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check if there are any derive macros or code generation related to optional accounts
rg -n "ShankContext\|shank_context\|optional" --type rs -g "!examples" | head -30

Length of output: 26


🏁 Script executed:

#!/bin/bash
# Look at the test or example files to see how ID is typically defined in the codebase
fd -t f -e "rs" -path "*/examples/*" --exec grep -l "ID.*32\|declare_id" {} \;

Length of output: 168


🏁 Script executed:

#!/bin/bash
# Check cargo.toml to understand the solana-program feature
find . -name "Cargo.toml" -exec grep -l "solana-program" {} \; | head -5

Length of output: 18


🌐 Web query:

solana_program::declare_id macro syntax usage

💡 Result:

Short answer

  • declare_id! takes a single base58 string literal (the program pubkey) and expands to a static Pubkey + helpers (ID / id() etc.). Use it in your crate (usually lib.rs) to embed the program ID at compile time. [1][2][3]

Minimal example

  • In lib.rs:
    use solana_program::{declare_id, pubkey::Pubkey};
    declare_id!("FILL_IN_YOUR_PROGRAM_PUBKEY_HERE");
    // expands to a static ID and an id() helper you can call in code.

Notes

  • The macro expects a literal (base58) string; it creates compile-time constants so you don’t need runtime parsing. [1][2]
  • See discussion of purpose (defines static Pubkey) for usage rationale. [3]

Sources
[1] solana_program::declare_id docs.
[2] solana-sdk declare_id docs.
[3] Solana StackExchange — “What is the point of declare_id?”



---

🏁 Script executed:

```shell
#!/bin/bash
# Find shank macro/derive implementation files
find . -name "*.rs" -path "*/shank/*" ! -path "*/examples/*" ! -path "*/tests/*" | head -20

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Search for ShankAccounts and ShankContext implementations
find . -name "*.rs" -path "*/shank/*" ! -path "*/examples/*" ! -path "*/tests/*" -exec grep -l "ShankAccounts\|ShankContext\|derive.*Shank" {} \;

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look for code generation logic related to optional accounts or ID comparison
find . -name "*.rs" -path "*/shank/*" ! -path "*/examples/*" ! -path "*/tests/*" -exec grep -l "optional\|Option\|accounts\[" {} \;

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check the structure of the shank directory
find ./shank -type f -name "*.rs" ! -path "*/target/*" ! -path "*/.git/*" | head -30

Length of output: 1195


🏁 Script executed:

#!/bin/bash
# Search for the actual proc macro code or context generation
find . -name "*.rs" ! -path "*/target/*" -exec grep -l "fn context\|generate.*context\|optional.*account" {} \; | grep -v examples | head -10

Length of output: 566



---

🏁 Script executed:

```shell
#!/bin/bash
# Examine the context.rs implementation
cat ./shank/src/context.rs

Length of output: 171


🏁 Script executed:

#!/bin/bash
# Look at optional accounts test input and expanded output
echo "=== OPTIONAL ACCOUNTS INPUT ===" && cat ./shank/tests/expand/optional_accounts.rs && echo -e "\n=== OPTIONAL ACCOUNTS EXPANDED ===" && cat ./shank/tests/expand/optional_accounts.expanded.rs

Length of output: 3793


🏁 Script executed:

#!/bin/bash
# Search for any ID-based validation or comparison in the codebase
find . -name "*.rs" ! -path "*/target/*" ! -path "*/examples/*" -exec grep -n "crate::ID\|== *ID\|&.*ID" {} + | head -20

Length of output: 2618


🏁 Script executed:

#!/bin/bash
# Check the AccountInfo trait definition in solana_program (if available locally)
# or check if there's a mock definition showing the type difference
grep -rn "pub key:" ./shank --include="*.rs" | grep -v target

Length of output: 2347


🏁 Script executed:

#!/bin/bash
# Check Cargo.toml for feature definitions
cat ./shank/Cargo.toml

Length of output: 564



---

🌐 Web query:

```web
Solana Pubkey type definition bytes array

💡 Result:

Short answer: a Solana Pubkey is a 32‑byte value.

Details:

  • In Rust (solana-program / solana-sdk) Pubkey is a wrapper around [u8; 32] (repr transparent); PUBKEY_BYTES = 32 and Pubkey::to_bytes() returns [u8; 32]. [1]
  • In JavaScript (@solana/web3.js) PublicKey.toBytes() returns a Uint8Array of length 32. [2]

Sources:
[1] solana-program / solana-sdk pubkey (source/docs showing Pubkey([u8;32]), PUBKEY_BYTES, to_bytes).
[2] @solana/web3.js PublicKey API (toBytes() → Uint8Array).


Fix ID type under solana-program to match AccountInfo::key.

With feature = "solana-program", the generated context() method compares accounts[i].key == &crate::ID where key is &Pubkey (a transparent wrapper around [u8; 32]), but ID is defined as [u8; 32]. This type mismatch causes compilation failure.

Define ID so its type matches in each cfg branch:

-// Mock program ID
-pub const ID: [u8; 32] = [1; 32];
+// Program ID sentinel used for optional-account detection
+#[cfg(feature = "solana-program")]
+solana_program::declare_id!("ShankExample11111111111111111111111111111");
+
+#[cfg(not(feature = "solana-program"))]
+pub const ID: [u8; 32] = [1; 32];

This keeps the mock [u8; 32] for non-Solana builds while making crate::ID a Pubkey when using real solana_program::AccountInfo.

🤖 Prompt for AI Agents
In shank/examples/anchor_style_example.rs around lines 5-20, the crate-level ID
is currently a [u8;32] causing a type mismatch with AccountInfo::key (which is
&Pubkey) when feature = "solana-program" is enabled; change the code to provide
two cfg branches: under #[cfg(feature = "solana-program")] define pub const ID:
solana_program::pubkey::Pubkey =
solana_program::pubkey::Pubkey::new_from_array([1; 32]) (or equivalent Pubkey
constructor), and keep the existing pub const ID: [u8; 32] = [1; 32] under
#[cfg(not(feature = "solana-program"))], so the ID type matches AccountInfo::key
in each build configuration.

Comment on lines +84 to +113
pub fn process_create_machine_v1<'info>(
accounts: &'info [AccountInfo<'info>],
name: String,
uri: String,
plugins: Vec<u8>,
) -> ProgramResult {
// Use the generated context method for type-safe account access
let ctx = CreateMachineV1Accounts::context(accounts)?;

// Access accounts by name with compile-time guarantees
msg!("Creating machine: {}", name);
msg!("Machine account: {:?}", ctx.machine.key);
msg!("Collection: {:?}", ctx.machine_collection.key);
msg!("Owner: {:?}", ctx.owner.key);
msg!("Payer: {:?}", ctx.payer.key);

// Handle optional accounts safely
match ctx.authority {
Some(authority) => {
msg!("Authority provided: {:?}", authority.key);
}
None => {
msg!("No authority provided");
}
}

// Machine creation logic would go here
msg!("Machine '{}' created successfully", name);

Ok(())
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider handling or silencing unused uri/plugins parameters.

In process_create_machine_v1, uri and plugins are never used, which may trigger warnings (or errors if #![deny(unused_variables)] is set). Either use them in logging or prefix with _ to make the intent explicit:

-pub fn process_create_machine_v1<'info>(
-    accounts: &'info [AccountInfo<'info>],
-    name: String,
-    uri: String,
-    plugins: Vec<u8>,
-) -> ProgramResult {
+pub fn process_create_machine_v1<'info>(
+    accounts: &'info [AccountInfo<'info>],
+    name: String,
+    _uri: String,
+    _plugins: Vec<u8>,
+) -> ProgramResult {

If you intend to use uri/plugins in real logic, consider adding at least one log line referencing them so the example demonstrates their purpose.

🤖 Prompt for AI Agents
In shank/examples/anchor_style_example.rs around lines 84 to 113, the function
parameters `uri` and `plugins` are unused which can trigger compiler warnings;
either reference them (e.g., add a msg! logging line for `uri` and `plugins`) or
rename them to start with an underscore (e.g., `_uri`, `_plugins`) to explicitly
silence unused-variable warnings — pick logging if you plan to demonstrate their
use, otherwise rename to underscore-prefixed names.

Comment on lines +6 to +175
// Mock AccountInfo for testing
pub struct AccountInfo<'info> {
pub key: &'info [u8; 32],
pub data: &'info [u8],
pub owner: &'info [u8; 32],
}

#[test]
fn test_conflicting_constraints_compile_time_error() {
// Note: These would cause compile-time errors, but we can't easily test
// compile-time failures in unit tests. However, we can document the expected behavior.

// This SHOULD cause an error (but we can't test it in a unit test):
// #[derive(ShankAccounts)]
// pub struct ConflictingAccounts<'info> {
// #[account(signer, optional_signer)] // Should error: can't be both
// pub bad_account: AccountInfo<'info>,
// }

// Instead, we test valid non-conflicting combinations
#[derive(ShankAccounts)]
pub struct ValidAccounts<'info> {
#[account(signer)]
pub signer_account: &'info AccountInfo<'info>,

#[account(optional_signer)]
pub optional_signer_account: Option<&'info AccountInfo<'info>>,

#[account(mut, signer)] // This combination is valid
pub mut_signer: &'info AccountInfo<'info>,
}

let metadata = ValidAccounts::__shank_accounts();
assert_eq!(metadata.len(), 3);

// Verify signer
assert_eq!(metadata[0].3, true); // signer
assert_eq!(metadata[0].4, false); // not optional_signer

// Verify optional_signer
assert_eq!(metadata[1].3, false); // not regular signer
assert_eq!(metadata[1].4, true); // optional_signer

// Verify combined constraints work
assert_eq!(metadata[2].2, true); // writable
assert_eq!(metadata[2].3, true); // signer
}

#[test]
fn test_description_variants() {
#[derive(ShankAccounts)]
pub struct DescriptionVariants<'info> {
#[account(desc = "Using desc")]
pub desc_account: &'info AccountInfo<'info>,

// Test that we properly handle all description attribute names
// (This tests our parsing logic)
pub no_desc_account: &'info AccountInfo<'info>,
}

let metadata = DescriptionVariants::__shank_accounts();
assert_eq!(metadata.len(), 2);

// Check description is captured
assert_eq!(metadata[0].6, Some("Using desc".to_string()));

// Check no description
assert_eq!(metadata[1].6, None);
}

#[test]
fn test_field_order_preservation() {
#[derive(ShankAccounts)]
pub struct OrderedAccounts<'info> {
pub first: &'info AccountInfo<'info>,
pub second: &'info AccountInfo<'info>,
pub third: &'info AccountInfo<'info>,
pub fourth: &'info AccountInfo<'info>,
}

let metadata = OrderedAccounts::__shank_accounts();
assert_eq!(metadata.len(), 4);

// Verify order is preserved
assert_eq!(metadata[0].0, 0);
assert_eq!(metadata[0].1, "first");

assert_eq!(metadata[1].0, 1);
assert_eq!(metadata[1].1, "second");

assert_eq!(metadata[2].0, 2);
assert_eq!(metadata[2].1, "third");

assert_eq!(metadata[3].0, 3);
assert_eq!(metadata[3].1, "fourth");
}

#[test]
fn test_complex_constraint_combinations() {
#[derive(ShankAccounts)]
pub struct ComplexAccounts<'info> {
// All basic combinations that make sense
#[account(mut)]
pub mut_only: &'info AccountInfo<'info>,

#[account(signer)]
pub signer_only: &'info AccountInfo<'info>,

#[account(optional)]
pub optional_only: Option<&'info AccountInfo<'info>>,

#[account(optional_signer)]
pub optional_signer_only: Option<&'info AccountInfo<'info>>,

#[account(mut, signer)]
pub mut_signer: &'info AccountInfo<'info>,

#[account(mut, optional)]
pub mut_optional: Option<&'info AccountInfo<'info>>,

#[account(mut, signer, desc = "Complex account")]
pub mut_signer_desc: &'info AccountInfo<'info>,
}

let metadata = ComplexAccounts::__shank_accounts();
assert_eq!(metadata.len(), 7);

// Test mut_only
assert!(metadata[0].2); // writable
assert!(!metadata[0].3); // not signer
assert!(!metadata[0].4); // not optional_signer
assert!(!metadata[0].5); // not optional

// Test signer_only
assert!(!metadata[1].2); // not writable
assert!(metadata[1].3); // signer
assert!(!metadata[1].4); // not optional_signer
assert!(!metadata[1].5); // not optional

// Test optional_only
assert!(!metadata[2].2); // not writable
assert!(!metadata[2].3); // not signer
assert!(!metadata[2].4); // not optional_signer
assert!(metadata[2].5); // optional

// Test optional_signer_only
assert!(!metadata[3].2); // not writable
assert!(!metadata[3].3); // not regular signer
assert!(metadata[3].4); // optional_signer
assert!(!metadata[3].5); // not optional (optional_signer is different)

// Test mut_signer combination
assert!(metadata[4].2); // writable
assert!(metadata[4].3); // signer
assert!(!metadata[4].4); // not optional_signer
assert!(!metadata[4].5); // not optional

// Test mut_optional combination
assert!(metadata[5].2); // writable
assert!(!metadata[5].3); // not signer
assert!(!metadata[5].4); // not optional_signer
assert!(metadata[5].5); // optional

// Test all combined with description
assert!(metadata[6].2); // writable
assert!(metadata[6].3); // signer
assert!(!metadata[6].4); // not optional_signer
assert!(!metadata[6].5); // not optional
assert_eq!(metadata[6].6, Some("Complex account".to_string()));
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Comprehensive positive‑path coverage for ShankAccounts

The tests exercise signer vs optional_signer, mut, optional, descriptions, field order, and complex combinations, and they match the tuple layout produced by __shank_accounts(). This is a solid baseline for the new macro behavior.

A possible follow‑up (not blocking) would be to add compile‑fail tests (e.g., via trybuild) for misuses like conflicting attributes or non‑Option types on #[account(optional)] / #[account(optional_signer)], once the macro enforces those constraints.

🤖 Prompt for AI Agents
In shank/tests/error_handling_test.rs lines 6-175, the reviewer suggests adding
compile-fail tests for macro misuse; add a trybuild-based test suite that
includes .rs fixtures demonstrating invalid cases (e.g., conflicting attributes
signer+optional_signer, #[account(optional)] on non-Option types,
optional_signer on non-Option, etc.), add trybuild as a dev-dependency in
Cargo.toml, create a new test (e.g., shank/tests/compile_fail_tests.rs) that
invokes trybuild::TestCases::new().compile_fail() against the fixture files, and
place the failing fixtures under shank/tests/trybuild/*.rs so CI will assert the
macro emits compile errors for those misuse cases.

Comment on lines +56 to +63
/// Create a context from a slice of accounts
///
/// This method parses the accounts according to the struct definition
/// and returns a Context containing the account struct.
///
/// Optional accounts are determined by checking if the account key
/// equals the program ID (crate::ID). If so, they are set to None, otherwise Some.
pub fn context(
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix doc comment: BasicAccounts has no optional accounts

The context docstring mentions optional-account handling via crate::ID, but BasicAccounts<'info> contains only required accounts. That part of the comment is misleading for this fixture; suggest dropping the optional-accounts paragraph:

-    ///
-    /// Optional accounts are determined by checking if the account key
-    /// equals the program ID (crate::ID). If so, they are set to None, otherwise Some.
🤖 Prompt for AI Agents
In shank/tests/expand/basic_accounts.expanded.rs around lines 56 to 63, the doc
comment for the context method erroneously describes optional-account handling
via checking for crate::ID, but BasicAccounts<'info> contains only required
accounts; remove the paragraph that mentions optional accounts (the lines
explaining crate::ID detection and setting Some/None) so the docstring only
describes creating a context from a slice of required accounts and parsing them
into the Context containing the account struct.

Comment on lines +195 to +211
// Verify all account structures work correctly
assert_eq!(BasicAccounts::__shank_accounts().len(), 1);
assert_eq!(CreateAccounts::__shank_accounts().len(), 3);
assert_eq!(UpdateAccounts::__shank_accounts().len(), 3);
assert_eq!(AdminAccounts::__shank_accounts().len(), 3);

// Verify specific constraints are applied correctly
let admin_accounts = AdminAccounts::__shank_accounts();
assert_eq!(admin_accounts[0].1, "admin");
assert_eq!(admin_accounts[0].3, true); // signer

assert_eq!(admin_accounts[1].1, "config");
assert_eq!(admin_accounts[1].2, true); // mut

assert_eq!(admin_accounts[2].1, "co_signer");
assert_eq!(admin_accounts[2].4, true); // optional_signer
}
Copy link

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

Fix optional_signer assertion to check the correct tuple fields.

For AdminAccounts, the test currently does:

assert_eq!(admin_accounts[2].1, "co_signer");
assert_eq!(admin_accounts[2].4, true); // optional_signer

From the expanded tests, the tuple layout of __shank_accounts() is:

(index, name, is_mut, is_signer, is_pda, is_optional, desc)

So:

  • optional_signer should set is_signer (.3) and is_optional (.5),
  • .4 is the PDA/seeds flag and will be false here.

Update the assertion to match the actual semantics:

-    assert_eq!(admin_accounts[2].1, "co_signer");
-    assert_eq!(admin_accounts[2].4, true); // optional_signer
+    assert_eq!(admin_accounts[2].1, "co_signer");
+    assert_eq!(admin_accounts[2].3, true); // signer
+    assert_eq!(admin_accounts[2].5, true); // optional

This should align with the behavior seen in shank/tests/expand/complex_constraints.expanded.rs and optional_accounts.expanded.rs where optional accounts set the last boolean flag.


🏁 Script executed:

#!/bin/bash
# Find and examine the test file
echo "=== Test file content ==="
find . -name "instruction_integration_test.rs" -type f | head -5

Length of output: 71


🏁 Script executed:

#!/bin/bash
# Look for the expanded test files referenced
echo "=== Searching for expanded test files ==="
find . -name "*expanded.rs" -type f | grep -E "(complex_constraints|optional_accounts)"

Length of output: 145


🏁 Script executed:

#!/bin/bash
# Search for __shank_accounts definition or usage to understand tuple structure
echo "=== Searching for __shank_accounts usage ==="
rg -n "__shank_accounts" --type rs -C 5 | head -100

Length of output: 71


🏁 Script executed:

#!/bin/bash
# View the test file at lines 195-211
echo "=== Test file lines 195-211 ==="
sed -n '195,211p' ./shank/tests/instruction_integration_test.rs

Length of output: 755


🏁 Script executed:

#!/bin/bash
# View the optional_accounts expanded file
echo "=== optional_accounts.expanded.rs content ==="
cat ./shank/tests/expand/optional_accounts.expanded.rs

Length of output: 3109


🏁 Script executed:

#!/bin/bash
# View the complex_constraints expanded file
echo "=== complex_constraints.expanded.rs content ==="
cat ./shank/tests/expand/complex_constraints.expanded.rs

Length of output: 4443


🏁 Script executed:

#!/bin/bash
# Search for __shank_accounts usage with correct type
echo "=== Searching for __shank_accounts usage ==="
rg -n "__shank_accounts" --type rust -C 3

Length of output: 29843


🏁 Script executed:

#!/bin/bash
# Find AdminAccounts definition in the test file
echo "=== Searching for AdminAccounts definition ==="
rg -n "struct AdminAccounts" ./shank/tests/instruction_integration_test.rs -A 10

Length of output: 455


🏁 Script executed:

#!/bin/bash
# Look for the entire test function to see context
echo "=== Viewing the test function around line 180 ==="
sed -n '170,220p' ./shank/tests/instruction_integration_test.rs

Length of output: 1708


Fix optional_signer assertion to check the correct tuple fields.

The test currently checks admin_accounts[2].4 for the optional_signer constraint, but the __shank_accounts() tuple layout is:

(index, name, is_mut, is_signer, is_pda, is_optional, desc)

with indices: (.0, .1, .2, .3, .4, .5, .6)

Since co_signer uses #[account(optional_signer)], it should set both .3 (is_signer) and .5 (is_optional) to true, while .4 (is_pda) remains false.

Update the assertion:

-    assert_eq!(admin_accounts[2].1, "co_signer");
-    assert_eq!(admin_accounts[2].4, true); // optional_signer
+    assert_eq!(admin_accounts[2].1, "co_signer");
+    assert_eq!(admin_accounts[2].3, true); // signer
+    assert_eq!(admin_accounts[2].5, true); // optional
📝 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
// Verify all account structures work correctly
assert_eq!(BasicAccounts::__shank_accounts().len(), 1);
assert_eq!(CreateAccounts::__shank_accounts().len(), 3);
assert_eq!(UpdateAccounts::__shank_accounts().len(), 3);
assert_eq!(AdminAccounts::__shank_accounts().len(), 3);
// Verify specific constraints are applied correctly
let admin_accounts = AdminAccounts::__shank_accounts();
assert_eq!(admin_accounts[0].1, "admin");
assert_eq!(admin_accounts[0].3, true); // signer
assert_eq!(admin_accounts[1].1, "config");
assert_eq!(admin_accounts[1].2, true); // mut
assert_eq!(admin_accounts[2].1, "co_signer");
assert_eq!(admin_accounts[2].4, true); // optional_signer
}
// Verify all account structures work correctly
assert_eq!(BasicAccounts::__shank_accounts().len(), 1);
assert_eq!(CreateAccounts::__shank_accounts().len(), 3);
assert_eq!(UpdateAccounts::__shank_accounts().len(), 3);
assert_eq!(AdminAccounts::__shank_accounts().len(), 3);
// Verify specific constraints are applied correctly
let admin_accounts = AdminAccounts::__shank_accounts();
assert_eq!(admin_accounts[0].1, "admin");
assert_eq!(admin_accounts[0].3, true); // signer
assert_eq!(admin_accounts[1].1, "config");
assert_eq!(admin_accounts[1].2, true); // mut
assert_eq!(admin_accounts[2].1, "co_signer");
assert_eq!(admin_accounts[2].3, true); // signer
assert_eq!(admin_accounts[2].5, true); // optional
}
🤖 Prompt for AI Agents
In shank/tests/instruction_integration_test.rs around lines 195 to 211, the test
incorrectly checks admin_accounts[2].4 for the optional_signer constraint;
update the assertions so that for the "co_signer" account you assert that .3
(is_signer) is true and .5 (is_optional) is true, and also assert that .4
(is_pda) is false to ensure the tuple fields match the documented layout.

Comment on lines +3 to +11
// Mock program ID - this simulates what declare_id! macro would create
pub const ID: [u8; 32] = [1; 32];

// Mock AccountInfo for testing
pub struct AccountInfo<'info> {
pub key: &'info [u8; 32],
pub data: &'info [u8],
pub owner: &'info [u8; 32],
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider consolidating test mocks into a shared module.

The mock ID constant and AccountInfo struct are likely duplicated across multiple test files. Extracting them into a shared test utilities module (e.g., tests/common/mod.rs) would reduce duplication and improve maintainability.

🤖 Prompt for AI Agents
In shank/tests/simple_context_test.rs around lines 3 to 11, the test-local mock
ID constant and AccountInfo struct are duplicated across tests; extract these
into a shared test utilities module (e.g., create tests/common/mod.rs) and
replace the local definitions with re-exports or use statements. Move pub const
ID and pub struct AccountInfo<'info> into tests/common/mod.rs, update Cargo.toml
if needed to expose the tests module, and change simple_context_test.rs to
import the shared items (use crate::common::ID; use crate::common::AccountInfo;)
to eliminate duplication and centralize test mocks.

Comment on lines +25 to +30
#[test]
fn test_context_method() {
// Test that the IDL generation works
let accounts = SimpleAccounts::__shank_accounts();
assert_eq!(accounts.len(), 3);
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Enhance test assertions to verify account metadata.

The test only checks the length of the accounts vector. Consider asserting the actual account properties (names, mutability, signer flags, optional flags) to ensure the macro generates correct metadata.

Apply this diff:

 #[test]
 fn test_context_method() {
     // Test that the IDL generation works
     let accounts = SimpleAccounts::__shank_accounts();
     assert_eq!(accounts.len(), 3);
+    
+    // Verify payer account
+    assert_eq!(accounts[0].0, 0); // index
+    assert_eq!(accounts[0].1, "payer"); // name
+    assert_eq!(accounts[0].2, true); // writable
+    assert_eq!(accounts[0].3, true); // signer
+    assert_eq!(accounts[0].5, false); // not optional
+    
+    // Verify data account
+    assert_eq!(accounts[1].0, 1);
+    assert_eq!(accounts[1].1, "data");
+    assert_eq!(accounts[1].2, true); // writable
+    assert_eq!(accounts[1].3, false); // not signer
+    assert_eq!(accounts[1].5, false); // not optional
+    
+    // Verify optional_account
+    assert_eq!(accounts[2].0, 2);
+    assert_eq!(accounts[2].1, "optional_account");
+    assert_eq!(accounts[2].2, false); // not writable
+    assert_eq!(accounts[2].3, false); // not signer
+    assert_eq!(accounts[2].5, true); // optional
 }
📝 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
#[test]
fn test_context_method() {
// Test that the IDL generation works
let accounts = SimpleAccounts::__shank_accounts();
assert_eq!(accounts.len(), 3);
}
#[test]
fn test_context_method() {
// Test that the IDL generation works
let accounts = SimpleAccounts::__shank_accounts();
assert_eq!(accounts.len(), 3);
// Verify payer account
assert_eq!(accounts[0].0, 0); // index
assert_eq!(accounts[0].1, "payer"); // name
assert_eq!(accounts[0].2, true); // writable
assert_eq!(accounts[0].3, true); // signer
assert_eq!(accounts[0].5, false); // not optional
// Verify data account
assert_eq!(accounts[1].0, 1);
assert_eq!(accounts[1].1, "data");
assert_eq!(accounts[1].2, true); // writable
assert_eq!(accounts[1].3, false); // not signer
assert_eq!(accounts[1].5, false); // not optional
// Verify optional_account
assert_eq!(accounts[2].0, 2);
assert_eq!(accounts[2].1, "optional_account");
assert_eq!(accounts[2].2, false); // not writable
assert_eq!(accounts[2].3, false); // not signer
assert_eq!(accounts[2].5, true); // optional
}
🤖 Prompt for AI Agents
In shank/tests/simple_context_test.rs around lines 25 to 30, the test only
asserts the accounts vector length; update it to assert each account's metadata
as well: for every index (0..accounts.len()) assert the account's name,
mutability flag, signer flag, and optional flag match the expected values
produced by the macro (e.g., assert accounts[0].name == "<expected_name>";
assert accounts[0].is_mut == <expected_bool>; assert accounts[0].is_signer ==
<expected_bool>; assert accounts[0].is_optional == <expected_bool>), and repeat
for accounts[1] and accounts[2] to validate all generated metadata. Ensure the
expected values reflect the test fixture definitions.

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.

1 participant