light_sdk_macros/
lib.rs

1extern crate proc_macro;
2use accounts::{process_light_accounts, process_light_system_accounts};
3use discriminator::discriminator;
4use hasher::{derive_light_hasher, derive_light_hasher_sha};
5use proc_macro::TokenStream;
6use syn::{parse_macro_input, DeriveInput, ItemStruct};
7use traits::process_light_traits;
8use utils::into_token_stream;
9
10mod account;
11mod accounts;
12mod compressible;
13mod discriminator;
14mod hasher;
15mod program;
16mod rent_sponsor;
17mod traits;
18mod utils;
19
20/// Adds required fields to your anchor instruction for applying a zk-compressed
21/// state transition.
22///
23/// ## Usage
24/// Add `#[light_system_accounts]` to your struct. Ensure it's applied before Anchor's
25/// `#[derive(Accounts)]` and Light's `#[derive(LightTraits)]`.
26///
27/// ## Example
28/// Note: You will have to build your program IDL using Anchor's `idl-build`
29/// feature, otherwise your IDL won't include these accounts.
30/// ```ignore
31/// use anchor_lang::prelude::*;
32/// use light_sdk_macros::light_system_accounts;
33///
34/// declare_id!("Fg6PaFpoGXkYsidMpWxTWKGNpKK39H3UKo7wjRZnq89u");
35///
36/// #[program]
37/// pub mod my_program {
38///     use super::*;
39/// }
40///
41/// #[light_system_accounts]
42/// #[derive(Accounts)]
43/// pub struct ExampleInstruction<'info> {
44///     pub my_program: Program<'info, MyProgram>,
45/// }
46/// ```
47/// This will expand to add the following fields to your struct:
48/// - `light_system_program`:           Verifies and applies zk-compression
49///   state transitions.
50/// - `registered_program_pda`:         A light protocol PDA to authenticate
51///   state tree updates.
52/// - `noop_program`:                   The SPL noop program to write
53///   compressed-account state as calldata to
54///   the Solana ledger.
55/// - `account_compression_authority`:  The authority for account compression
56///   operations.
57/// - `account_compression_program`:    Called by light_system_program. Updates
58///   state trees.
59/// - `system_program`:                 The Solana System program.
60#[proc_macro_attribute]
61pub fn light_system_accounts(_: TokenStream, input: TokenStream) -> TokenStream {
62    let input = parse_macro_input!(input as ItemStruct);
63    into_token_stream(process_light_system_accounts(input))
64}
65
66#[proc_macro_attribute]
67pub fn light_accounts(_: TokenStream, input: TokenStream) -> TokenStream {
68    let input = parse_macro_input!(input as ItemStruct);
69    into_token_stream(process_light_accounts(input))
70}
71
72#[proc_macro_derive(LightAccounts, attributes(light_account))]
73pub fn light_accounts_derive(input: TokenStream) -> TokenStream {
74    let input = parse_macro_input!(input as ItemStruct);
75    into_token_stream(accounts::process_light_accounts_derive(input))
76}
77
78/// Implements traits on the given struct required for invoking The Light system
79/// program via CPI.
80///
81/// ## Usage
82/// Add `#[derive(LightTraits)]` to your struct which specifies the accounts
83/// required for your Anchor program instruction. Specify the attributes
84/// `self_program`, `fee_payer`, `authority`, and optionally `cpi_context` to
85/// the relevant fields.
86///
87/// ### Attributes
88/// - `self_program`:   Marks the field that represents the program invoking the
89///   light system program, i.e. your program. You need to
90///   list your program as part of the struct.
91/// - `fee_payer`:      Marks the field that represents the account responsible
92///   for paying transaction fees. (Signer)
93///
94/// - `authority`:      TODO: explain authority.
95/// - `cpi_context`:    TODO: explain cpi_context.
96///
97/// ### Required accounts (must specify exact name).
98///
99/// - `light_system_program`:           Light systemprogram. verifies & applies
100///   compression state transitions.
101/// - `registered_program_pda`:         Light Systemprogram PDA
102/// - `noop_program`:                   SPL noop program
103/// - `account_compression_authority`:  TODO: explain.
104/// - `account_compression_program`:    Account Compression program.
105/// - `system_program`:                 The Solana Systemprogram.
106///
107/// ### Example
108/// ```ignore
109/// use anchor_lang::prelude::*;
110/// use light_sdk_macros::LightTraits;
111///
112/// declare_id!("Fg6PaFpoGXkYsidMpWxTWKGNpKK39H3UKo7wjRZnq89u");
113///
114/// #[program]
115/// pub mod my_program {
116///     use super::*;
117/// }
118///
119/// #[derive(Accounts, LightTraits)]
120/// pub struct ExampleInstruction<'info> {
121///     #[self_program]
122///     pub my_program: Program<'info, MyProgram>,
123///     #[fee_payer]
124///     pub payer: Signer<'info>,
125///     #[authority]
126///     pub user: AccountInfo<'info>,
127///     #[cpi_context]
128///     pub cpi_context_account: AccountInfo<'info>,
129///     pub light_system_program: AccountInfo<'info>,
130///     pub registered_program_pda: AccountInfo<'info>,
131///     pub noop_program: AccountInfo<'info>,
132///     pub account_compression_authority: AccountInfo<'info>,
133///     pub account_compression_program: AccountInfo<'info>,
134///     pub system_program: Program<'info, System>,
135/// }
136/// ```
137#[proc_macro_derive(
138    LightTraits,
139    attributes(self_program, fee_payer, authority, cpi_context)
140)]
141pub fn light_traits_derive(input: TokenStream) -> TokenStream {
142    let input = parse_macro_input!(input as DeriveInput);
143    into_token_stream(process_light_traits(input))
144}
145
146#[proc_macro_derive(LightDiscriminator)]
147pub fn light_discriminator(input: TokenStream) -> TokenStream {
148    let input = parse_macro_input!(input as ItemStruct);
149    into_token_stream(discriminator(input))
150}
151
152/// Makes the annotated struct hashable by implementing the following traits:
153///
154/// - [`ToByteArray`](light_hasher::to_byte_array::ToByteArray), which makes the struct
155///   convertable to a 2D byte vector.
156/// - [`DataHasher`](light_hasher::DataHasher), which makes the struct hashable
157///   with the `hash()` method, based on the byte inputs from `ToByteArray`
158///   implementation.
159///
160/// This macro assumes that all the fields of the struct implement the
161/// `AsByteVec` trait. The trait is implemented by default for the most of
162/// standard Rust types (primitives, `String`, arrays and options carrying the
163/// former). If there is a field of a type not implementing the trait, there
164/// will be a compilation error.
165///
166/// ## Example
167///
168/// ```ignore
169/// use light_sdk_macros::LightHasher;
170/// use solana_pubkey::Pubkey;
171///
172/// #[derive(LightHasher)]
173/// pub struct UserRecord {
174///     pub owner: Pubkey,
175///     pub name: String,
176///     pub score: u64,
177/// }
178/// ```
179///
180/// ## Hash attribute
181///
182/// Fields marked with `#[hash]` will be hashed to field size (31 bytes) before
183/// being included in the main hash calculation. This is useful for fields that
184/// exceed the field size limit (like Pubkeys which are 32 bytes).
185///
186/// ```ignore
187/// use light_sdk_macros::LightHasher;
188/// use solana_pubkey::Pubkey;
189///
190/// #[derive(LightHasher)]
191/// pub struct GameState {
192///     #[hash]
193///     pub player: Pubkey,  // Will be hashed to 31 bytes
194///     pub level: u32,
195/// }
196/// ```
197#[proc_macro_derive(LightHasher, attributes(hash, skip))]
198pub fn light_hasher(input: TokenStream) -> TokenStream {
199    let input = parse_macro_input!(input as ItemStruct);
200    into_token_stream(derive_light_hasher(input))
201}
202
203/// SHA256 variant of the LightHasher derive macro.
204///
205/// This derive macro automatically implements the `DataHasher` and `ToByteArray` traits
206/// for structs, using SHA256 as the hashing algorithm instead of Poseidon.
207///
208/// ## Example
209///
210/// ```ignore
211/// use light_sdk_macros::LightHasherSha;
212/// use solana_pubkey::Pubkey;
213///
214/// #[derive(LightHasherSha)]
215/// pub struct GameState {
216///     #[hash]
217///     pub player: Pubkey,  // Will be hashed to 31 bytes
218///     pub level: u32,
219/// }
220/// ```
221#[proc_macro_derive(LightHasherSha, attributes(hash, skip))]
222pub fn light_hasher_sha(input: TokenStream) -> TokenStream {
223    let input = parse_macro_input!(input as ItemStruct);
224    into_token_stream(derive_light_hasher_sha(input))
225}
226
227/// Alias of `LightHasher`.
228#[proc_macro_derive(DataHasher, attributes(skip, hash))]
229pub fn data_hasher(input: TokenStream) -> TokenStream {
230    let input = parse_macro_input!(input as ItemStruct);
231    into_token_stream(derive_light_hasher_sha(input))
232}
233
234/// Automatically implements the HasCompressionInfo trait for structs that have a
235/// `compression_info: Option<CompressionInfo>` field.
236///
237/// This derive macro generates the required trait methods for managing compression
238/// information in compressible account structs.
239///
240/// ## Example
241///
242/// ```ignore
243/// use light_sdk_macros::HasCompressionInfo;
244/// use light_compressible::CompressionInfo;
245/// use solana_pubkey::Pubkey;
246///
247/// #[derive(HasCompressionInfo)]
248/// pub struct UserRecord {
249///     #[skip]
250///     pub compression_info: Option<CompressionInfo>,
251///     pub owner: Pubkey,
252///     pub name: String,
253///     pub score: u64,
254/// }
255/// ```
256///
257/// ## Requirements
258///
259/// The struct must have exactly one field named `compression_info` of type
260/// `Option<CompressionInfo>`. The field should be marked with `#[skip]` to
261/// exclude it from hashing.
262#[proc_macro_derive(HasCompressionInfo)]
263pub fn has_compression_info(input: TokenStream) -> TokenStream {
264    let input = parse_macro_input!(input as ItemStruct);
265    into_token_stream(compressible::traits::derive_has_compression_info(input))
266}
267
268/// Legacy CompressAs trait implementation (use Compressible instead).
269///
270/// This derive macro allows you to specify which fields should be reset/overridden
271/// during compression while keeping other fields as-is. Only the specified fields
272/// are modified; all others retain their current values.
273///
274/// ## Example
275///
276/// ```ignore
277/// use light_sdk_macros::CompressAs;
278/// use light_compressible::CompressionInfo;
279/// use solana_pubkey::Pubkey;
280///
281/// #[derive(CompressAs)]
282/// #[compress_as(
283///     start_time = 0,
284///     end_time = None,
285///     score = 0
286/// )]
287/// pub struct GameSession {
288///     #[skip]
289///     pub compression_info: Option<CompressionInfo>,
290///     pub session_id: u64,
291///     pub player: Pubkey,
292///     pub game_type: String,
293///     pub start_time: u64,
294///     pub end_time: Option<u64>,
295///     pub score: u64,
296/// }
297/// ```
298///
299/// ## Note
300///
301/// Use the new `Compressible` derive instead - it includes this functionality plus more.
302#[proc_macro_derive(CompressAs, attributes(compress_as))]
303pub fn compress_as_derive(input: TokenStream) -> TokenStream {
304    let input = parse_macro_input!(input as ItemStruct);
305    into_token_stream(compressible::traits::derive_compress_as(input))
306}
307
308/// Adds compressible account support with automatic seed generation.
309///
310/// This macro generates everything needed for compressible accounts:
311/// - CompressedAccountVariant enum with all trait implementations  
312/// - Compress and decompress instructions with auto-generated seed derivation
313/// - CTokenSeedProvider implementation for token accounts
314/// - All required account structs and functions
315///
316/// ## Usage
317/// ```ignore
318/// use anchor_lang::prelude::*;
319/// use light_sdk_macros::add_compressible_instructions;
320///
321/// declare_id!("Fg6PaFpoGXkYsidMpWxTWKGNpKK39H3UKo7wjRZnq89u");
322///
323/// #[add_compressible_instructions(
324///     UserRecord = ("user_record", data.owner),
325///     GameSession = ("game_session", data.session_id.to_le_bytes()),
326///     CTokenSigner = (is_token, "ctoken_signer", ctx.fee_payer, ctx.mint)
327/// )]
328/// #[program]
329/// pub mod my_program {
330///     use super::*;
331///     // Your regular instructions here - everything else is auto-generated!
332///     // CTokenAccountVariant enum is automatically generated with:
333///     // - CTokenSigner = 0
334/// }
335/// ```
336#[proc_macro_attribute]
337pub fn add_compressible_instructions(args: TokenStream, input: TokenStream) -> TokenStream {
338    let module = syn::parse_macro_input!(input as syn::ItemMod);
339    into_token_stream(compressible::instructions::add_compressible_instructions(
340        args.into(),
341        module,
342    ))
343}
344
345#[proc_macro_attribute]
346pub fn account(_: TokenStream, input: TokenStream) -> TokenStream {
347    let input = parse_macro_input!(input as ItemStruct);
348    into_token_stream(account::account(input))
349}
350
351/// Automatically implements all required traits for compressible accounts.
352///
353/// This derive macro generates HasCompressionInfo, Size, and CompressAs trait implementations.
354/// It supports optional compress_as attribute for custom compression behavior.
355///
356/// ## Example - Basic Usage
357///
358/// ```ignore
359/// use light_sdk_macros::Compressible;
360/// use light_compressible::CompressionInfo;
361/// use solana_pubkey::Pubkey;
362///
363/// #[derive(Compressible)]
364/// pub struct UserRecord {
365///     pub compression_info: Option<CompressionInfo>,
366///     pub owner: Pubkey,
367///     pub name: String,
368///     pub score: u64,
369/// }
370/// ```
371///
372/// ## Example - Custom Compression
373///
374/// ```ignore
375/// use light_sdk_macros::Compressible;
376/// use light_compressible::CompressionInfo;
377/// use solana_pubkey::Pubkey;
378///
379/// #[derive(Compressible)]
380/// #[compress_as(start_time = 0, end_time = None, score = 0)]
381/// pub struct GameSession {
382///     pub compression_info: Option<CompressionInfo>,
383///     pub session_id: u64,        // KEPT
384///     pub player: Pubkey,         // KEPT  
385///     pub game_type: String,      // KEPT
386///     pub start_time: u64,        // RESET to 0
387///     pub end_time: Option<u64>,  // RESET to None
388///     pub score: u64,             // RESET to 0
389/// }
390/// ```
391#[proc_macro_derive(Compressible, attributes(compress_as, light_seeds))]
392pub fn compressible_derive(input: TokenStream) -> TokenStream {
393    let input = parse_macro_input!(input as DeriveInput);
394    into_token_stream(compressible::traits::derive_compressible(input))
395}
396
397/// Automatically implements Pack and Unpack traits for compressible accounts.
398///
399/// For types with Pubkey fields, generates a PackedXxx struct and proper packing.
400/// For types without Pubkeys, generates identity Pack/Unpack implementations.
401///
402/// ## Example
403///
404/// ```ignore
405/// use light_sdk_macros::CompressiblePack;
406/// use light_compressible::CompressionInfo;
407/// use solana_pubkey::Pubkey;
408///
409/// #[derive(CompressiblePack)]
410/// pub struct UserRecord {
411///     pub compression_info: Option<CompressionInfo>,
412///     pub owner: Pubkey,  // Will be packed as u8 index
413///     pub name: String,   // Kept as-is
414///     pub score: u64,     // Kept as-is
415/// }
416/// // This generates PackedUserRecord struct + Pack/Unpack implementations
417/// ```
418#[proc_macro_derive(CompressiblePack)]
419pub fn compressible_pack(input: TokenStream) -> TokenStream {
420    let input = parse_macro_input!(input as DeriveInput);
421    into_token_stream(compressible::pack_unpack::derive_compressible_pack(input))
422}
423
424// DEPRECATED: compressed_account_variant macro is now integrated into add_compressible_instructions
425// Use add_compressible_instructions instead for complete automation
426
427/// Generates complete compressible instructions with auto-generated seed derivation.
428///
429/// This is a drop-in replacement for manual decompress_accounts_idempotent and
430/// compress_accounts_idempotent instructions. It reads #[light_seeds(...)] attributes
431/// from account types and generates complete instructions with inline seed derivation.
432///
433/// ## Example
434///
435/// Add #[light_seeds(...)] to your account types:
436/// ```ignore
437/// use light_sdk_macros::{Compressible, CompressiblePack};
438/// use solana_pubkey::Pubkey;
439///
440/// #[derive(Compressible, CompressiblePack)]
441/// #[light_seeds(b"user_record", owner.as_ref())]
442/// pub struct UserRecord {
443///     pub owner: Pubkey,
444///     // ...
445/// }
446///
447/// #[derive(Compressible, CompressiblePack)]  
448/// #[light_seeds(b"game_session", session_id.to_le_bytes().as_ref())]
449/// pub struct GameSession {
450///     pub session_id: u64,
451///     // ...
452/// }
453/// ```
454///
455/// Then generate complete instructions:
456/// ```ignore
457/// # macro_rules! compressed_account_variant_with_instructions { ($($t:ty),*) => {} }
458/// compressed_account_variant_with_instructions!(UserRecord, GameSession, PlaceholderRecord);
459/// ```
460///
461/// This generates:
462/// - CompressedAccountVariant enum + all trait implementations
463/// - Complete decompress_accounts_idempotent instruction with auto-generated seed derivation
464/// - Complete compress_accounts_idempotent instruction with auto-generated seed derivation
465/// - CompressedAccountData struct
466///
467/// The generated instructions automatically handle seed derivation for each account type
468/// without requiring manual seed function calls.
469///
470/// Derive DecompressContext trait implementation.
471///
472/// This generates the full DecompressContext trait implementation for
473/// decompression account structs. Can be used standalone or is automatically
474/// used by add_compressible_instructions.
475///
476/// ## Attributes
477/// - `#[pda_types(Type1, Type2, ...)]` - List of PDA account types
478/// - `#[token_variant(CTokenAccountVariant)]` - The token variant enum name
479///
480/// ## Example
481///
482/// ```ignore
483/// use anchor_lang::prelude::*;
484/// use light_sdk_macros::DecompressContext;
485///
486/// declare_id!("Fg6PaFpoGXkYsidMpWxTWKGNpKK39H3UKo7wjRZnq89u");
487///
488/// struct UserRecord;
489/// struct GameSession;
490/// enum CTokenAccountVariant {}
491///
492/// #[derive(Accounts, DecompressContext)]
493/// #[pda_types(UserRecord, GameSession)]
494/// #[token_variant(CTokenAccountVariant)]
495/// pub struct DecompressAccountsIdempotent<'info> {
496///     #[account(mut)]
497///     pub fee_payer: Signer<'info>,
498///     pub config: AccountInfo<'info>,
499///     #[account(mut)]
500///     pub rent_sponsor: Signer<'info>,
501///     #[account(mut)]
502///     pub ctoken_rent_sponsor: AccountInfo<'info>,
503///     pub ctoken_program: UncheckedAccount<'info>,
504///     pub ctoken_cpi_authority: UncheckedAccount<'info>,
505///     pub ctoken_config: UncheckedAccount<'info>,
506/// }
507/// ```
508#[proc_macro_derive(DecompressContext, attributes(pda_types, token_variant))]
509pub fn derive_decompress_context(input: TokenStream) -> TokenStream {
510    let input = parse_macro_input!(input as DeriveInput);
511    into_token_stream(compressible::decompress_context::derive_decompress_context(
512        input,
513    ))
514}
515
516/// Derives a Rent Sponsor PDA for a program at compile time.
517///
518/// Seeds: ["rent_sponsor", <u16 version little-endian>]
519///
520/// ## Example
521///
522/// ```ignore
523/// use light_sdk_macros::derive_light_rent_sponsor_pda;
524///
525/// pub const RENT_SPONSOR_DATA: ([u8; 32], u8) =
526///     derive_light_rent_sponsor_pda!("8Ld9pGkCNfU6A7KdKe1YrTNYJWKMCFqVHqmUvjNmER7B", 1);
527/// ```
528#[proc_macro]
529pub fn derive_light_rent_sponsor_pda(input: TokenStream) -> TokenStream {
530    rent_sponsor::derive_light_rent_sponsor_pda(input)
531}
532
533/// Derives a complete Rent Sponsor configuration for a program at compile time.
534///
535/// Returns ::light_sdk_types::RentSponsor { program_id, rent_sponsor, bump, version }.
536///
537/// ## Example
538///
539/// ```ignore
540/// use light_sdk_macros::derive_light_rent_sponsor;
541///
542/// pub const RENT_SPONSOR: ::light_sdk_types::RentSponsor =
543///     derive_light_rent_sponsor!("8Ld9pGkCNfU6A7KdKe1YrTNYJWKMCFqVHqmUvjNmER7B", 1);
544/// ```
545#[proc_macro]
546pub fn derive_light_rent_sponsor(input: TokenStream) -> TokenStream {
547    rent_sponsor::derive_light_rent_sponsor(input)
548}
549/// Generates a Light program for the given module.
550///
551/// ## Example
552///
553/// ```ignore
554/// use light_sdk_macros::light_program;
555/// use anchor_lang::prelude::*;
556///
557/// declare_id!("Fg6PaFpoGXkYsidMpWxTWKGNpKK39H3UKo7wjRZnq89u");
558///
559/// #[derive(Accounts)]
560/// pub struct MyInstruction {}
561///
562/// #[light_program]
563/// pub mod my_program {
564///     use super::*;
565///     pub fn my_instruction(ctx: Context<MyInstruction>) -> Result<()> {
566///         // Your instruction logic here
567///         Ok(())
568///     }
569/// }
570/// ```
571#[proc_macro_attribute]
572pub fn light_program(_: TokenStream, input: TokenStream) -> TokenStream {
573    let input = parse_macro_input!(input as syn::ItemMod);
574    into_token_stream(program::program(input))
575}