Skip to main content

light_sdk_macros/
lib.rs

1extern crate proc_macro;
2use discriminator::{anchor_discriminator, light_discriminator};
3use hasher::{derive_light_hasher, derive_light_hasher_sha};
4use proc_macro::TokenStream;
5use syn::{parse_macro_input, DeriveInput, ItemStruct};
6use utils::into_token_stream;
7
8mod account;
9mod discriminator;
10mod hasher;
11mod light_pdas;
12mod rent_sponsor;
13mod utils;
14
15#[cfg(test)]
16mod light_pdas_tests;
17
18/// Derives a discriminator using SHA256("{struct_name}")[0..8].
19///
20/// This is the Light Protocol native discriminator format.
21/// Use this for new Light Protocol accounts that don't need Anchor compatibility.
22///
23/// ## Example
24///
25/// ```ignore
26/// use light_sdk::LightDiscriminator;
27///
28/// #[derive(LightDiscriminator)]
29/// pub struct MyAccount {
30///     pub owner: Pubkey,
31///     pub counter: u64,
32/// }
33/// // MyAccount::LIGHT_DISCRIMINATOR = SHA256("MyAccount")[0..8]
34/// ```
35#[proc_macro_derive(LightDiscriminator)]
36pub fn light_discriminator_derive(input: TokenStream) -> TokenStream {
37    let input = parse_macro_input!(input as ItemStruct);
38    into_token_stream(light_discriminator(input))
39}
40
41/// Derives a discriminator using SHA256("account:{struct_name}")[0..8].
42///
43/// This is the Anchor-compatible discriminator format.
44/// Use this when you need compatibility with Anchor's account discriminator format.
45///
46/// ## Example
47///
48/// ```ignore
49/// use light_sdk::AnchorDiscriminator;
50///
51/// #[derive(AnchorDiscriminator)]
52/// pub struct MyAccount {
53///     pub owner: Pubkey,
54///     pub counter: u64,
55/// }
56/// // MyAccount::LIGHT_DISCRIMINATOR = SHA256("account:MyAccount")[0..8]
57/// ```
58#[proc_macro_derive(AnchorDiscriminator)]
59pub fn anchor_discriminator_derive(input: TokenStream) -> TokenStream {
60    let input = parse_macro_input!(input as ItemStruct);
61    into_token_stream(anchor_discriminator(input))
62}
63
64/// Makes the annotated struct hashable by implementing the following traits:
65///
66/// - [`ToByteArray`](light_hasher::to_byte_array::ToByteArray), which makes the struct
67///   convertable to a 2D byte vector.
68/// - [`DataHasher`](light_hasher::DataHasher), which makes the struct hashable
69///   with the `hash()` method, based on the byte inputs from `ToByteArray`
70///   implementation.
71///
72/// This macro assumes that all the fields of the struct implement the
73/// `AsByteVec` trait. The trait is implemented by default for the most of
74/// standard Rust types (primitives, `String`, arrays and options carrying the
75/// former). If there is a field of a type not implementing the trait, there
76/// will be a compilation error.
77///
78/// ## Example
79///
80/// ```ignore
81/// use light_sdk_macros::LightHasher;
82/// use solana_pubkey::Pubkey;
83///
84/// #[derive(LightHasher)]
85/// pub struct UserRecord {
86///     pub owner: Pubkey,
87///     pub name: String,
88///     pub score: u64,
89/// }
90/// ```
91///
92/// ## Hash attribute
93///
94/// Fields marked with `#[hash]` will be hashed to field size (31 bytes) before
95/// being included in the main hash calculation. This is useful for fields that
96/// exceed the field size limit (like Pubkeys which are 32 bytes).
97///
98/// ```ignore
99/// use light_sdk_macros::LightHasher;
100/// use solana_pubkey::Pubkey;
101///
102/// #[derive(LightHasher)]
103/// pub struct GameState {
104///     #[hash]
105///     pub player: Pubkey,  // Will be hashed to 31 bytes
106///     pub level: u32,
107/// }
108/// ```
109#[proc_macro_derive(LightHasher, attributes(hash, skip))]
110pub fn light_hasher(input: TokenStream) -> TokenStream {
111    let input = parse_macro_input!(input as ItemStruct);
112    into_token_stream(derive_light_hasher(input))
113}
114
115/// SHA256 variant of the LightHasher derive macro.
116///
117/// This derive macro automatically implements the `DataHasher` and `ToByteArray` traits
118/// for structs, using SHA256 as the hashing algorithm instead of Poseidon.
119///
120/// ## Example
121///
122/// ```ignore
123/// use light_sdk_macros::LightHasherSha;
124/// use solana_pubkey::Pubkey;
125///
126/// #[derive(LightHasherSha)]
127/// pub struct GameState {
128///     #[hash]
129///     pub player: Pubkey,  // Will be hashed to 31 bytes
130///     pub level: u32,
131/// }
132/// ```
133#[proc_macro_derive(LightHasherSha, attributes(hash, skip))]
134pub fn light_hasher_sha(input: TokenStream) -> TokenStream {
135    let input = parse_macro_input!(input as ItemStruct);
136    into_token_stream(derive_light_hasher_sha(input))
137}
138
139/// Automatically implements the HasCompressionInfo trait for structs that have a
140/// `compression_info: Option<CompressionInfo>` field.
141///
142/// This derive macro generates the required trait methods for managing compression
143/// information in compressible account structs.
144///
145/// ## Example
146///
147/// ```ignore
148/// use light_sdk_macros::HasCompressionInfo;
149/// use light_compressible::CompressionInfo;
150/// use solana_pubkey::Pubkey;
151///
152/// #[derive(HasCompressionInfo)]
153/// pub struct UserRecord {
154///     #[skip]
155///     pub compression_info: Option<CompressionInfo>,
156///     pub owner: Pubkey,
157///     pub name: String,
158///     pub score: u64,
159/// }
160/// ```
161///
162/// ## Requirements
163///
164/// The struct must have exactly one field named `compression_info` of type
165/// `Option<CompressionInfo>`. The field should be marked with `#[skip]` to
166/// exclude it from hashing.
167#[proc_macro_derive(HasCompressionInfo)]
168pub fn has_compression_info(input: TokenStream) -> TokenStream {
169    let input = parse_macro_input!(input as ItemStruct);
170    into_token_stream(light_pdas::account::traits::derive_has_compression_info(
171        input,
172    ))
173}
174
175/// Legacy CompressAs trait implementation (use Compressible instead).
176///
177/// This derive macro allows you to specify which fields should be reset/overridden
178/// during compression while keeping other fields as-is. Only the specified fields
179/// are modified; all others retain their current values.
180///
181/// ## Example
182///
183/// ```ignore
184/// use light_sdk_macros::CompressAs;
185/// use light_compressible::CompressionInfo;
186/// use solana_pubkey::Pubkey;
187///
188/// #[derive(CompressAs)]
189/// #[compress_as(
190///     start_time = 0,
191///     end_time = None,
192///     score = 0
193/// )]
194/// pub struct GameSession {
195///     #[skip]
196///     pub compression_info: Option<CompressionInfo>,
197///     pub session_id: u64,
198///     pub player: Pubkey,
199///     pub game_type: String,
200///     pub start_time: u64,
201///     pub end_time: Option<u64>,
202///     pub score: u64,
203/// }
204/// ```
205///
206/// ## Note
207///
208/// Use the new `Compressible` derive instead - it includes this functionality plus more.
209#[proc_macro_derive(CompressAs, attributes(compress_as))]
210pub fn compress_as_derive(input: TokenStream) -> TokenStream {
211    let input = parse_macro_input!(input as ItemStruct);
212    into_token_stream(light_pdas::account::traits::derive_compress_as(input))
213}
214
215/// Auto-discovering Light program macro that reads external module files.
216///
217/// This macro automatically discovers #[light_account(init)] fields in Accounts structs
218/// by reading external module files. No explicit type list needed!
219///
220/// It also **automatically wraps** instruction handlers that use Light Accounts
221/// structs with `light_pre_init`/`light_finalize` logic - no separate attribute needed!
222///
223/// Usage:
224/// ```ignore
225/// #[light_program]
226/// #[program]
227/// pub mod my_program {
228///     pub mod instruction_accounts;  // Macro reads this file!
229///     pub mod state;
230///
231///     use instruction_accounts::*;
232///     use state::*;
233///
234///     pub fn create_user(ctx: Context<CreateUser>, params: Params) -> Result<()> {
235///         // Your business logic
236///     }
237/// }
238/// ```
239///
240/// The macro:
241/// 1. Scans the crate's `src/` directory for `#[derive(Accounts)]` structs
242/// 2. Extracts seeds from `#[account(seeds = [...])]` on `#[light_account(init)]` fields
243/// 3. Auto-wraps instruction handlers that use those Accounts structs
244/// 4. Generates all necessary types, enums, and instruction handlers
245///
246/// Seeds are declared ONCE in Anchor attributes - no duplication!
247#[proc_macro_attribute]
248pub fn light_program(args: TokenStream, input: TokenStream) -> TokenStream {
249    let module = syn::parse_macro_input!(input as syn::ItemMod);
250    into_token_stream(light_pdas::program::light_program_impl(args.into(), module))
251}
252
253/// Derive macro for manually specifying compressed account variants on an enum.
254///
255/// Generates equivalent code to `#[light_program]` auto-discovery, but allows
256/// specifying account types and seeds explicitly. Useful for external programs
257/// where you don't own the module.
258///
259/// ## Example
260///
261/// ```ignore
262/// #[derive(LightProgram)]
263/// pub enum ProgramAccounts {
264///     #[light_account(pda::seeds = [b"record", ctx.owner])]
265///     Record(MinimalRecord),
266///
267///     #[light_account(pda::seeds = [RECORD_SEED, ctx.owner], pda::zero_copy)]
268///     ZeroCopyRecord(ZeroCopyRecord),
269///
270///     #[light_account(token::seeds = [VAULT_SEED, ctx.mint], token::owner_seeds = [AUTH_SEED])]
271///     Vault,
272///
273///     #[light_account(associated_token)]
274///     Ata,
275/// }
276/// ```
277///
278/// Seed expressions use explicit prefixes:
279/// - `ctx.field` - context account reference
280/// - `data.field` - instruction data parameter
281/// - `b"literal"` or `"literal"` - byte/string literal
282/// - `CONSTANT` or `path::CONSTANT` - constant in SCREAMING_SNAKE_CASE
283#[proc_macro_derive(LightProgram, attributes(light_account))]
284pub fn light_program_derive(input: TokenStream) -> TokenStream {
285    let input = parse_macro_input!(input as DeriveInput);
286    into_token_stream(light_pdas::program::derive_light_program_impl(input))
287}
288
289/// Pinocchio variant of `#[derive(LightProgram)]`.
290///
291/// Generates pinocchio-compatible code instead of Anchor:
292/// - `BorshSerialize/BorshDeserialize` instead of `AnchorSerialize/AnchorDeserialize`
293/// - `light_account_pinocchio::` paths instead of `light_account::`
294/// - Config/compress/decompress as enum associated functions
295/// - `[u8; 32]` instead of `Pubkey` in generated params
296///
297/// See `#[derive(LightProgram)]` for usage syntax (identical attribute syntax).
298#[proc_macro_derive(LightProgramPinocchio, attributes(light_account))]
299pub fn light_program_pinocchio_derive(input: TokenStream) -> TokenStream {
300    let input = parse_macro_input!(input as DeriveInput);
301    into_token_stream(light_pdas::program::derive_light_program_pinocchio_impl(
302        input,
303    ))
304}
305
306#[proc_macro_attribute]
307pub fn account(_: TokenStream, input: TokenStream) -> TokenStream {
308    let input = parse_macro_input!(input as ItemStruct);
309    into_token_stream(account::account(input))
310}
311
312/// Generates a unified `LightAccount` trait implementation for light account structs.
313///
314/// This macro generates:
315/// - `LightHasherSha` (SHA256/ShaFlat hashing via DataHasher + ToByteArray)
316/// - `LightDiscriminator` (unique 8-byte discriminator)
317/// - `impl LightAccount for T` (unified trait with pack/unpack, compression_info accessors)
318/// - `PackedT` struct (Pubkeys -> u8 indices, compression_info excluded to save 24 bytes)
319///
320/// ## Example
321///
322/// ```ignore
323/// use light_sdk_macros::{LightAccount, LightDiscriminator, LightHasherSha};
324/// use light_account::CompressionInfo;
325/// use solana_pubkey::Pubkey;
326///
327/// #[derive(Default, Debug, InitSpace, LightAccount, LightDiscriminator, LightHasherSha)]
328/// #[account]
329/// pub struct UserRecord {
330///     pub compression_info: CompressionInfo,  // Non-Option, first or last field
331///     pub owner: Pubkey,
332///     #[max_len(32)]
333///     pub name: String,
334///     pub score: u64,
335/// }
336/// ```
337///
338/// ## Generated Code
339///
340/// The macro generates:
341/// - `PackedUserRecord` struct with Pubkeys replaced by u8 indices and compression_info excluded
342/// - `impl LightAccount for UserRecord` with:
343///   - `const ACCOUNT_TYPE: AccountType = AccountType::Pda`
344///   - `const INIT_SPACE: usize` (from Anchor's Space trait)
345///   - `fn compression_info(&self)` / `fn compression_info_mut(&mut self)`
346///   - `fn set_decompressed(&mut self, config, slot)` (resets transient fields)
347///   - `fn pack(&self, accounts)` / `fn unpack(packed, accounts)`
348/// - Compile-time assertion that INIT_SPACE <= 800 bytes
349///
350/// ## Attributes
351///
352/// - `#[compress_as(field = value)]` - Optional: reset field values during set_decompressed
353/// - `#[skip]` - Exclude fields from compression/hashing entirely
354///
355/// ## Requirements
356///
357/// - The `compression_info` field must be non-Option `CompressionInfo` type
358/// - The `compression_info` field must be first or last field in the struct
359/// - SHA256 hashing serializes the entire struct (no `#[hash]` needed)
360#[proc_macro_derive(LightAccount, attributes(compress_as, skip))]
361pub fn light_account_derive(input: TokenStream) -> TokenStream {
362    let input = parse_macro_input!(input as DeriveInput);
363    into_token_stream(light_pdas::account::derive::derive_light_account(input))
364}
365
366/// Pinocchio variant of `LightAccount` derive macro.
367///
368/// Same as `#[derive(LightAccount)]` but generates pinocchio-compatible code:
369/// - Uses `BorshSerialize/BorshDeserialize` instead of Anchor serialization
370/// - Uses `light_account_pinocchio::` paths for on-chain code
371/// - Uses `core::mem::size_of::<Self>()` for INIT_SPACE (always zero-copy style)
372///
373/// ## Example
374///
375/// ```ignore
376/// use light_sdk_macros::{LightPinocchioAccount, LightDiscriminator, LightHasherSha};
377/// use light_account_pinocchio::CompressionInfo;
378///
379/// #[derive(
380///     Default, Debug, Clone, PartialEq,
381///     BorshSerialize, BorshDeserialize,
382///     LightDiscriminator, LightHasherSha,
383///     LightPinocchioAccount,
384/// )]
385/// #[repr(C)]
386/// pub struct MinimalRecord {
387///     pub compression_info: CompressionInfo,
388///     pub owner: [u8; 32],
389/// }
390/// ```
391///
392/// ## Requirements
393///
394/// - The `compression_info` field must be non-Option `CompressionInfo` type
395/// - The `compression_info` field must be first or last field in the struct
396/// - Struct should be `#[repr(C)]` for predictable memory layout
397/// - Use `[u8; 32]` instead of `Pubkey` for address fields
398#[proc_macro_derive(LightPinocchioAccount, attributes(compress_as, skip))]
399pub fn light_pinocchio_account_derive(input: TokenStream) -> TokenStream {
400    let input = parse_macro_input!(input as DeriveInput);
401    into_token_stream(light_pdas::account::derive::derive_light_pinocchio_account(
402        input,
403    ))
404}
405
406/// Derives a Rent Sponsor PDA for a program at compile time.
407///
408/// Seeds: ["rent_sponsor"]
409///
410/// ## Example
411///
412/// ```ignore
413/// use light_sdk_macros::derive_light_rent_sponsor_pda;
414///
415/// pub const RENT_SPONSOR_DATA: ([u8; 32], u8) =
416///     derive_light_rent_sponsor_pda!("8Ld9pGkCNfU6A7KdKe1YrTNYJWKMCFqVHqmUvjNmER7B");
417/// ```
418#[proc_macro]
419pub fn derive_light_rent_sponsor_pda(input: TokenStream) -> TokenStream {
420    rent_sponsor::derive_light_rent_sponsor_pda(input)
421}
422
423/// Derives a complete Rent Sponsor configuration for a program at compile time.
424///
425/// Returns ::light_sdk_types::RentSponsor { program_id, rent_sponsor, bump }.
426///
427/// ## Example
428///
429/// ```ignore
430/// use light_sdk_macros::derive_light_rent_sponsor;
431///
432/// pub const RENT_SPONSOR: ::light_sdk_types::RentSponsor =
433///     derive_light_rent_sponsor!("8Ld9pGkCNfU6A7KdKe1YrTNYJWKMCFqVHqmUvjNmER7B");
434/// ```
435#[proc_macro]
436pub fn derive_light_rent_sponsor(input: TokenStream) -> TokenStream {
437    rent_sponsor::derive_light_rent_sponsor(input)
438}
439
440/// Generates `LightFinalize` trait implementation for Light Protocol accounts.
441///
442/// This derive macro works alongside Anchor's `#[derive(Accounts)]` to add
443/// compression finalize logic for:
444/// - Accounts marked with `#[light_account(init)]` (PDAs)
445/// - Accounts marked with `#[light_account(init, mint, ...)]` (compressed mints)
446/// - Accounts marked with `#[light_account(token, ...)]` (rent-free token accounts)
447///
448/// The trait is defined in `light_account::LightFinalize`.
449///
450/// ## Usage - PDAs
451///
452/// ```ignore
453/// #[derive(Accounts, LightAccounts)]
454/// #[instruction(params: CompressionParams)]
455/// pub struct CreatePda<'info> {
456///     #[account(mut)]
457///     pub fee_payer: Signer<'info>,
458///
459///     #[account(
460///         init, payer = fee_payer, space = 8 + MyData::INIT_SPACE,
461///         seeds = [b"my_data", authority.key().as_ref()],
462///         bump
463///     )]
464///     #[light_account(init)]
465///     pub my_account: Account<'info, MyData>,
466///
467///     /// CHECK: Compression config
468///     pub compression_config: AccountInfo<'info>,
469/// }
470/// ```
471///
472/// ## Usage - Rent-free Token Accounts
473///
474/// ```ignore
475/// #[derive(Accounts, LightAccounts)]
476/// pub struct CreateVault<'info> {
477///     #[account(
478///         mut,
479///         seeds = [b"vault", mint.key().as_ref()],
480///         bump
481///     )]
482///     #[light_account(token, authority = [b"vault_authority"])]
483///     pub vault: UncheckedAccount<'info>,
484/// }
485/// ```
486///
487/// ## Usage - Light Mints
488///
489/// ```ignore
490/// #[derive(Accounts, LightAccounts)]
491/// #[instruction(params: MintParams)]
492/// pub struct CreateMint<'info> {
493///     #[account(mut)]
494///     pub fee_payer: Signer<'info>,
495///
496///     #[account(mut)]
497///     #[light_account(init, mint,
498///         mint_signer = mint_signer,
499///         authority = authority,
500///         decimals = 9,
501///         mint_seeds = &[...]
502///     )]
503///     pub mint: UncheckedAccount<'info>,
504///
505///     pub mint_signer: Signer<'info>,
506///     pub authority: Signer<'info>,
507/// }
508/// ```
509///
510/// ## Requirements
511///
512/// Your program must define:
513/// - `LIGHT_CPI_SIGNER`: CPI signer pubkey constant
514/// - `ID`: Program ID (from declare_id!)
515///
516/// The struct should have fields named `fee_payer` (or `payer`) and `compression_config`.
517#[proc_macro_derive(LightAccounts, attributes(light_account, instruction))]
518pub fn light_accounts_derive(input: TokenStream) -> TokenStream {
519    let input = parse_macro_input!(input as DeriveInput);
520    into_token_stream(light_pdas::accounts::derive_light_accounts(input))
521}