Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 42 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ members = [
"sdk-libs/sdk-types",
"sdk-libs/photon-api",
"sdk-libs/program-test",
"sdk-libs/instruction-decoder",
"sdk-libs/instruction-decoder-derive",
"xtask",
"program-tests/account-compression-test",
"program-tests/batched-merkle-tree-test",
Expand Down Expand Up @@ -132,6 +134,7 @@ pinocchio = { version = "0.9" }
pinocchio-pubkey = { version = "0.3.0" }
pinocchio-system = { version = "0.3.0" }
bs58 = "^0.5.1"
sha2 = "0.10"
litesvm = "0.7"
# Anchor
anchor-lang = { version = "0.31.1" }
Expand All @@ -150,6 +153,7 @@ proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "2.0", features = ["visit", "visit-mut", "full"] }
darling = "0.21"
heck = "0.5"

# Async ecosystem
futures = "0.3.31"
Expand Down Expand Up @@ -223,6 +227,8 @@ create-address-test-program = { path = "program-tests/create-address-test-progra
"cpi",
] }
light-program-test = { path = "sdk-libs/program-test", version = "0.18.0" }
light-instruction-decoder = { path = "sdk-libs/instruction-decoder", version = "0.1.0" }
light-instruction-decoder-derive = { path = "sdk-libs/instruction-decoder-derive", version = "0.1.0" }
light-batched-merkle-tree = { path = "program-libs/batched-merkle-tree", version = "0.8.0" }
light-merkle-tree-metadata = { path = "program-libs/merkle-tree-metadata", version = "0.8.0" }
aligned-sized = { path = "program-libs/aligned-sized", version = "1.1.0" }
Expand Down
1 change: 1 addition & 0 deletions program-tests/compressed-token-test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ default = []
[dependencies]
anchor-lang = { workspace = true }
light-sdk = { workspace = true, features = ["anchor"] }
light-instruction-decoder = { workspace = true }

[dev-dependencies]
light-compressed-token = { workspace = true }
Expand Down
2 changes: 2 additions & 0 deletions program-tests/compressed-token-test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
#![allow(deprecated)]

use anchor_lang::{prelude::*, solana_program::instruction::Instruction};
use light_instruction_decoder::instruction_decoder;

declare_id!("CompressedTokenTestProgram11111111111111111");

#[instruction_decoder]
#[program]
pub mod compressed_token_test {
use super::*;
Expand Down
1 change: 1 addition & 0 deletions program-tests/create-address-test-program/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ account-compression = { workspace = true, features = ["cpi"] }
light-compressed-account = { workspace = true, features = ["anchor"] }
light-sdk = { workspace = true, features = ["anchor", "v2"] }
light-sdk-types = { workspace = true }
light-instruction-decoder = { workspace = true }
2 changes: 2 additions & 0 deletions program-tests/create-address-test-program/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use anchor_lang::{
solana_program::{instruction::Instruction, pubkey::Pubkey},
InstructionData,
};
use light_instruction_decoder::instruction_decoder;
use light_sdk::{
cpi::{v2::CpiAccounts, CpiAccountsConfig, CpiSigner},
derive_light_cpi_signer,
Expand Down Expand Up @@ -59,6 +60,7 @@ declare_id!("FNt7byTHev1k5x2cXZLBr8TdWiC3zoP5vcnZR4P682Uy");
pub const LIGHT_CPI_SIGNER: CpiSigner =
derive_light_cpi_signer!("FNt7byTHev1k5x2cXZLBr8TdWiC3zoP5vcnZR4P682Uy");

#[instruction_decoder]
#[program]
pub mod system_cpi_test {

Expand Down
1 change: 1 addition & 0 deletions program-tests/system-cpi-test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ light-merkle-tree-metadata = { workspace = true, features = ["anchor"] }
light-account-checks = { workspace = true }
light-sdk = { workspace = true, features = ["v2", "cpi-context"] }
light-sdk-types = { workspace = true, features = ["v2", "cpi-context"] }
light-instruction-decoder = { workspace = true }

[target.'cfg(not(target_os = "solana"))'.dependencies]
solana-sdk = { workspace = true }
Expand Down
2 changes: 2 additions & 0 deletions program-tests/system-cpi-test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use light_compressed_account::{
data::{NewAddressParamsPacked, PackedReadOnlyAddress},
},
};
use light_instruction_decoder::instruction_decoder;
use light_sdk::derive_light_cpi_signer;
use light_sdk_types::CpiSigner;

Expand All @@ -32,6 +33,7 @@ declare_id!("FNt7byTHev1k5x2cXZLBr8TdWiC3zoP5vcnZR4P682Uy");
pub const LIGHT_CPI_SIGNER: CpiSigner =
derive_light_cpi_signer!("FNt7byTHev1k5x2cXZLBr8TdWiC3zoP5vcnZR4P682Uy");

#[instruction_decoder]
#[program]
pub mod system_cpi_test {

Expand Down
148 changes: 148 additions & 0 deletions sdk-libs/instruction-decoder-derive/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# light-instruction-decoder-derive

Procedural macros for generating `InstructionDecoder` implementations.

## Overview

This crate provides two macros for generating instruction decoders:

| Macro | Type | Purpose |
|-------|------|---------|
| `#[derive(InstructionDecoder)]` | Derive | Generate decoder for instruction enums |
| `#[instruction_decoder]` | Attribute | Auto-generate from Anchor program modules |

## Module Structure

```
src/
├── lib.rs # Macro entry points only (~100 lines)
├── utils.rs # Case conversion, discriminator, error handling
├── parsing.rs # Darling-based attribute parsing structs
├── builder.rs # InstructionDecoderBuilder (code generation)
├── derive_impl.rs # #[derive(InstructionDecoder)] implementation
├── attribute_impl.rs # #[instruction_decoder] attribute implementation
└── crate_context.rs # Recursive crate parsing for Accounts struct discovery
```
Comment on lines +16 to +25
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add language specifier to fenced code block.

The module structure code block should have a language specifier for proper rendering and linting compliance.

📝 Suggested fix
-```
+```text
 src/
 ├── lib.rs              # Macro entry points only (~100 lines)
 ├── utils.rs            # Case conversion, discriminator, error handling
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

16-16: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
In `@sdk-libs/instruction-decoder-derive/CLAUDE.md` around lines 16 - 25, Update
the fenced code block in CLAUDE.md that shows the src/ module tree to include a
language specifier (e.g., change the opening fence from ``` to ```text) so the
block renders and lints correctly; locate the module-structure block near the
top of the file (the tree listing including lib.rs, utils.rs, parsing.rs,
builder.rs, derive_impl.rs, attribute_impl.rs, crate_context.rs) and add the
language tag to the opening fence.


## Key Features
Comment on lines +1 to +27
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add required CLAUDE.md sections (Summary/Used in/Navigation/Source Code Structure).

This file doesn’t include the mandatory sections required for every crate’s CLAUDE.md. Please add: Summary (2–5 bullets), Used in, Navigation instructions, and Source Code Structure (library-style: Core Types, etc.). As per coding guidelines, this is required for compliance and discoverability.

As per coding guidelines, ...

📌 Suggested structure
## Summary
- ...
- ...

## Used in
- ...

## Navigation
- ...

## Source Code Structure
src/
├── ...
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

16-16: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
In `@sdk-libs/instruction-decoder-derive/CLAUDE.md` around lines 1 - 27, Add the
required CLAUDE.md sections: create a "Summary" section with 2–5 concise bullet
points describing the crate, a "Used in" section listing consumers (e.g., SDKs
or other crates that import this derive), a "Navigation" section with pointers
to key files/entry points (e.g., lib.rs, derive_impl.rs, attribute_impl.rs) and
usage notes, and a "Source Code Structure" section that lists the source tree in
a library-style breakdown (Core Types, Parsing, Builders, Utilities, Macros)
mirroring the existing src/ listing; ensure each section uses the exact headers
Summary, Used in, Navigation, and Source Code Structure so the file meets the
required template.


### Multiple Discriminator Sizes

- **1 byte**: Native programs with simple instruction indices
- **4 bytes**: System-style programs (little-endian u32)
- **8 bytes**: Anchor programs (SHA256 prefix, default)

### Explicit Discriminators

Two syntax forms for specifying explicit discriminators:

1. **Integer**: `#[discriminator = 5]` - for 1-byte and 4-byte modes
2. **Array**: `#[discriminator(26, 16, 169, 7, 21, 202, 242, 25)]` - for 8-byte mode with custom discriminators

Comment on lines +37 to +41
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Documentation uses unsupported 8‑byte discriminator syntax.

The builder error message expects #[discriminator = [a, b, ...]], but the docs show #[discriminator(…)]. Please align the docs with the actual syntax to prevent user-facing compile errors.

📝 Suggested fix
-2. **Array**: `#[discriminator(26, 16, 169, 7, 21, 202, 242, 25)]` - for 8-byte mode with custom discriminators
+2. **Array**: `#[discriminator = [26, 16, 169, 7, 21, 202, 242, 25]]` - for 8-byte mode with custom discriminators

Also applies to: 81-83

🤖 Prompt for AI Agents
In `@sdk-libs/instruction-decoder-derive/CLAUDE.md` around lines 37 - 41, The docs
currently show an unsupported 8-byte discriminator syntax using the form
`#[discriminator(26, 16, ...)]`; update the documentation examples in the "Two
syntax forms for specifying explicit discriminators" section so the array form
matches the builder's expected syntax `#[discriminator = [a, b, ...]]` (leave
the integer form `#[discriminator = 5]` as-is) and change any other occurrences
of `#[discriminator(...)]` (including the later example around lines 81-83) to
the `#[discriminator = [ ... ]]` form so docs match the compiler/builder error
message.

### Account Names Extraction

Two ways to specify account names:

1. **Accounts type reference**: `accounts = MyAccountsStruct` - extracts field names at compile time
2. **Inline names**: Direct array `["source", "dest", "authority"]`

When using `accounts = SomeType`, the macro uses `CrateContext` to parse the crate at macro expansion time and extract field names from the struct definition. This works for any struct with named fields (including standard Anchor `#[derive(Accounts)]` structs) without requiring any special trait implementation.

### Off-chain Only

All generated code is gated with `#[cfg(not(target_os = "solana"))]` since instruction decoding is only needed for logging/debugging.

## Usage Examples

### Derive Macro

```rust
use light_instruction_decoder_derive::InstructionDecoder;

#[derive(InstructionDecoder)]
#[instruction_decoder(
program_id = "MyProgramId111111111111111111111111111111111",
program_name = "My Program", // optional
discriminator_size = 8 // optional: 1, 4, or 8
)]
pub enum MyInstruction {
// Reference Accounts struct for account names
#[instruction_decoder(accounts = CreateRecord, params = CreateRecordParams)]
CreateRecord,

// Inline account names
#[instruction_decoder(account_names = ["source", "dest"])]
Transfer,

// Explicit integer discriminator (for 1-byte or 4-byte modes)
#[discriminator = 5]
Close,

// Explicit array discriminator (for 8-byte mode with custom discriminators)
#[discriminator(26, 16, 169, 7, 21, 202, 242, 25)]
#[instruction_decoder(account_names = ["fee_payer", "authority"])]
CustomInstruction,
}
```

### Attribute Macro (Anchor Programs)

```rust
use light_instruction_decoder_derive::instruction_decoder;

#[instruction_decoder] // or #[instruction_decoder(program_id = crate::ID)]
#[program]
pub mod my_program {
pub fn create_record(ctx: Context<CreateRecord>, params: CreateParams) -> Result<()> { ... }
pub fn transfer(ctx: Context<Transfer>) -> Result<()> { ... }
}
```

This generates `MyProgramInstructionDecoder` that:
- Gets program_id from `crate::ID` (or `declare_id!` if found)
- Extracts function names and converts to discriminators
- Discovers Accounts struct field names from the crate
- Decodes params using borsh if specified

## Architecture

### Darling-Based Parsing

Attributes are parsed using the `darling` crate for:
- Declarative struct-based definitions
- Automatic validation
- Better error messages with span preservation

### Builder Pattern

`InstructionDecoderBuilder` separates:
- **Parsing**: Extract and validate attributes
- **Code Generation**: Produce TokenStream output

This follows the pattern from `sdk-libs/macros`.

### Crate Context

`CrateContext` recursively parses all module files at macro expansion time to discover structs by name. This enables both macros to automatically find field names:

- **Derive macro**: When `accounts = SomeType` is specified, extracts struct field names
- **Attribute macro**: Discovers Accounts structs from `Context<T>` parameters

The struct lookup finds any struct with named fields - no special trait implementation required. This makes the macro completely independent and works with any Anchor program.

## Testing

```bash
# Unit tests
cargo test -p light-instruction-decoder-derive

# Integration tests (verifies generated code compiles and works)
cargo test-sbf -p csdk-anchor-full-derived-test --test instruction_decoder_test
```

## Dependencies

- `darling`: Attribute parsing
- `syn/quote/proc-macro2`: Token manipulation
- `sha2`: Anchor discriminator computation
- `bs58`: Program ID encoding
22 changes: 22 additions & 0 deletions sdk-libs/instruction-decoder-derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "light-instruction-decoder-derive"
version = "0.1.0"
description = "Derive macros for InstructionDecoder implementations in Light Protocol"
repository = "https://github.com/Lightprotocol/light-protocol"
license = "Apache-2.0"
edition = "2021"

[dependencies]
bs58 = { workspace = true }
darling = { workspace = true }
heck = { workspace = true }
proc-macro2 = { workspace = true }
quote = { workspace = true }
sha2 = "0.10"
syn = { workspace = true }

[dev-dependencies]
light-instruction-decoder = { workspace = true }

[lib]
proc-macro = true
Loading
Loading