light_sdk_macros/
lib.rs

1extern crate proc_macro;
2use accounts::{process_light_accounts, process_light_system_accounts};
3use hasher::derive_light_hasher;
4use proc_macro::TokenStream;
5use syn::{parse_macro_input, DeriveInput, ItemMod, ItemStruct};
6use traits::process_light_traits;
7
8mod account;
9mod accounts;
10mod cpi_signer;
11mod discriminator;
12mod hasher;
13mod program;
14mod traits;
15
16/// Adds required fields to your anchor instruction for applying a zk-compressed
17/// state transition.
18///
19/// ## Usage
20/// Add `#[light_system_accounts]` to your struct. Ensure it's applied before Anchor's
21/// `#[derive(Accounts)]` and Light's `#[derive(LightTraits)]`.
22///
23/// ## Example
24/// Note: You will have to build your program IDL using Anchor's `idl-build`
25/// feature, otherwise your IDL won't include these accounts.
26/// ```ignore
27/// #[light_system_accounts]
28/// #[derive(Accounts)]
29/// pub struct ExampleInstruction<'info> {
30///     pub my_program: Program<'info, MyProgram>,
31/// }
32/// ```
33/// This will expand to add the following fields to your struct:
34/// - `light_system_program`:           Verifies and applies zk-compression
35///   state transitions.
36/// - `registered_program_pda`:         A light protocol PDA to authenticate
37///   state tree updates.
38/// - `noop_program`:                   The SPL noop program to write
39///   compressed-account state as calldata to
40///   the Solana ledger.
41/// - `account_compression_authority`:  The authority for account compression
42///   operations.
43/// - `account_compression_program`:    Called by light_system_program. Updates
44///   state trees.
45/// - `system_program`:                 The Solana System program.
46#[proc_macro_attribute]
47pub fn light_system_accounts(_: TokenStream, input: TokenStream) -> TokenStream {
48    let input = parse_macro_input!(input as ItemStruct);
49
50    process_light_system_accounts(input)
51        .unwrap_or_else(|err| err.to_compile_error())
52        .into()
53}
54
55#[proc_macro_attribute]
56pub fn light_accounts(_: TokenStream, input: TokenStream) -> TokenStream {
57    let input = parse_macro_input!(input as ItemStruct);
58
59    match process_light_accounts(input) {
60        Ok(token_stream) => token_stream.into(),
61        Err(err) => TokenStream::from(err.to_compile_error()),
62    }
63}
64
65#[proc_macro_derive(LightAccounts, attributes(light_account))]
66pub fn light_accounts_derive(input: TokenStream) -> TokenStream {
67    let input = parse_macro_input!(input as ItemStruct);
68    accounts::process_light_accounts_derive(input)
69        .unwrap_or_else(|err| err.to_compile_error())
70        .into()
71}
72
73/// Implements traits on the given struct required for invoking The Light system
74/// program via CPI.
75///
76/// ## Usage
77/// Add `#[derive(LightTraits)]` to your struct which specifies the accounts
78/// required for your Anchor program instruction. Specify the attributes
79/// `self_program`, `fee_payer`, `authority`, and optionally `cpi_context` to
80/// the relevant fields.
81///
82/// ### Attributes
83/// - `self_program`:   Marks the field that represents the program invoking the
84///   light system program, i.e. your program. You need to
85///   list your program as part of the struct.
86/// - `fee_payer`:      Marks the field that represents the account responsible
87///   for paying transaction fees. (Signer)
88///
89/// - `authority`:      TODO: explain authority.
90/// - `cpi_context`:    TODO: explain cpi_context.
91///
92/// ### Required accounts (must specify exact name).
93///
94/// - `light_system_program`:           Light systemprogram. verifies & applies
95///   compression state transitions.
96/// - `registered_program_pda`:         Light Systemprogram PDA
97/// - `noop_program`:                   SPL noop program
98/// - `account_compression_authority`:  TODO: explain.
99/// - `account_compression_program`:    Account Compression program.
100/// - `system_program`:                 The Solana Systemprogram.
101///
102/// ### Example
103/// ```ignore
104/// #[derive(Accounts, LightTraits)]
105/// pub struct ExampleInstruction<'info> {
106///     #[self_program]
107///     pub my_program: Program<'info, MyProgram>,
108///     #[fee_payer]
109///     pub payer: Signer<'info>,
110///     #[authority]
111///     pub user: AccountInfo<'info>,
112///     #[cpi_context]
113///     pub cpi_context_account: AccountInfo<'info>,
114///     pub light_system_program: AccountInfo<'info>,
115///     pub registered_program_pda: AccountInfo<'info>,
116///     pub noop_program: AccountInfo<'info>,
117///     pub account_compression_authority: AccountInfo<'info>,
118///     pub account_compression_program: AccountInfo<'info>,
119///     pub system_program: Program<'info, System>,
120/// }
121/// ```
122#[proc_macro_derive(
123    LightTraits,
124    attributes(self_program, fee_payer, authority, cpi_context)
125)]
126pub fn light_traits_derive(input: TokenStream) -> TokenStream {
127    let input = parse_macro_input!(input as DeriveInput);
128
129    match process_light_traits(input) {
130        Ok(token_stream) => token_stream.into(),
131        Err(err) => TokenStream::from(err.to_compile_error()),
132    }
133}
134
135#[proc_macro_derive(LightDiscriminator)]
136pub fn light_discriminator(input: TokenStream) -> TokenStream {
137    let input = parse_macro_input!(input as ItemStruct);
138    discriminator::discriminator(input)
139        .unwrap_or_else(|err| err.to_compile_error())
140        .into()
141}
142
143/// Makes the annotated struct hashable by implementing the following traits:
144///
145/// - [`AsByteVec`](light_hasher::bytes::AsByteVec), which makes the struct
146///   convertable to a 2D byte vector.
147/// - [`DataHasher`](light_hasher::DataHasher), which makes the struct hashable
148///   with the `hash()` method, based on the byte inputs from `AsByteVec`
149///   implementation.
150///
151/// This macro assumes that all the fields of the struct implement the
152/// `AsByteVec` trait. The trait is implemented by default for the most of
153/// standard Rust types (primitives, `String`, arrays and options carrying the
154/// former). If there is a field of a type not implementing the trait, there
155/// are two options:
156///
157/// 1. The most recommended one - annotating that type with the `light_hasher`
158///    macro as well.
159/// 2. Manually implementing the `AsByteVec` trait.
160///
161/// # Attributes
162///
163/// - `skip` - skips the given field, it doesn't get included neither in
164///   `AsByteVec` nor `DataHasher` implementation.
165/// - `hash` - makes sure that the byte value does not exceed the BN254
166///   prime field modulus, by hashing it (with Keccak) and truncating it to 31
167///   bytes. It's generally a good idea to use it on any field which is
168///   expected to output more than 31 bytes.
169///
170/// # Examples
171///
172/// Compressed account with only primitive types as fields:
173///
174/// ```ignore
175/// #[derive(LightHasher)]
176/// pub struct MyCompressedAccount {
177///     a: i64,
178///     b: Option<u64>,
179/// }
180/// ```
181///
182/// Compressed account with fields which might exceed the BN254 prime field:
183///
184/// ```ignore
185/// #[derive(LightHasher)]
186/// pub struct MyCompressedAccount {
187///     a: i64
188///     b: Option<u64>,
189///     #[hash]
190///     c: [u8; 32],
191///     #[hash]
192///     d: String,
193/// }
194/// ```
195///
196/// Compressed account with fields we want to skip:
197///
198/// ```ignore
199/// #[derive(LightHasher)]
200/// pub struct MyCompressedAccount {
201///     a: i64
202///     b: Option<u64>,
203///     #[skip]
204///     c: [u8; 32],
205/// }
206/// ```
207///
208/// Compressed account with a nested struct:
209///
210/// ```ignore
211/// #[derive(LightHasher)]
212/// pub struct MyCompressedAccount {
213///     a: i64
214///     b: Option<u64>,
215///     c: MyStruct,
216/// }
217///
218/// #[derive(LightHasher)]
219/// pub struct MyStruct {
220///     a: i32
221///     b: u32,
222/// }
223/// ```
224///
225/// Compressed account with a type with a custom `AsByteVec` implementation:
226///
227/// ```ignore
228/// #[derive(LightHasher)]
229/// pub struct MyCompressedAccount {
230///     a: i64
231///     b: Option<u64>,
232///     c: RData,
233/// }
234///
235/// pub enum RData {
236///     A(Ipv4Addr),
237///     AAAA(Ipv6Addr),
238///     CName(String),
239/// }
240///
241/// impl AsByteVec for RData {
242///     fn as_byte_vec(&self) -> Vec<Vec<u8>> {
243///         match self {
244///             Self::A(ipv4_addr) => vec![ipv4_addr.octets().to_vec()],
245///             Self::AAAA(ipv6_addr) => vec![ipv6_addr.octets().to_vec()],
246///             Self::CName(cname) => cname.as_byte_vec(),
247///         }
248///     }
249/// }
250/// ```
251#[proc_macro_derive(LightHasher, attributes(skip, hash))]
252pub fn light_hasher(input: TokenStream) -> TokenStream {
253    let input = parse_macro_input!(input as ItemStruct);
254    derive_light_hasher(input)
255        .unwrap_or_else(|err| err.to_compile_error())
256        .into()
257}
258
259/// Alias of `LightHasher`.
260#[proc_macro_derive(DataHasher, attributes(skip, hash))]
261pub fn data_hasher(input: TokenStream) -> TokenStream {
262    let input = parse_macro_input!(input as ItemStruct);
263    derive_light_hasher(input)
264        .unwrap_or_else(|err| err.to_compile_error())
265        .into()
266}
267
268#[proc_macro_attribute]
269pub fn light_account(_: TokenStream, input: TokenStream) -> TokenStream {
270    let input = parse_macro_input!(input as ItemStruct);
271    account::account(input)
272        .unwrap_or_else(|err| err.to_compile_error())
273        .into()
274}
275
276#[proc_macro_attribute]
277pub fn light_program(_: TokenStream, input: TokenStream) -> TokenStream {
278    let input = parse_macro_input!(input as ItemMod);
279    program::program(input)
280        .unwrap_or_else(|err| err.to_compile_error())
281        .into()
282}
283
284/// Derives a Light Protocol CPI signer address at compile time
285///
286/// This macro computes the CPI signer PDA using the "cpi_authority" seed
287/// for the given program ID at compile time.
288///
289/// ## Usage
290///
291/// ```
292/// use light_sdk_macros::derive_light_cpi_signer_pda;
293/// // Derive CPI signer for your program
294/// const CPI_SIGNER_DATA: ([u8; 32], u8) = derive_light_cpi_signer_pda!("SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7");
295/// const CPI_SIGNER: [u8; 32] = CPI_SIGNER_DATA.0;
296/// const CPI_SIGNER_BUMP: u8 = CPI_SIGNER_DATA.1;
297/// ```
298///
299/// This macro computes the PDA during compile time and returns a tuple of ([u8; 32], bump).
300#[proc_macro]
301pub fn derive_light_cpi_signer_pda(input: TokenStream) -> TokenStream {
302    cpi_signer::derive_light_cpi_signer_pda(input)
303}
304
305/// Derives a complete Light Protocol CPI configuration at compile time
306///
307/// This macro computes the program ID, CPI signer PDA, and bump seed
308/// for the given program ID at compile time.
309///
310/// ## Usage
311///
312/// ```
313/// use light_sdk_macros::derive_light_cpi_signer;
314/// use light_sdk_types::CpiSigner;
315/// // Derive complete CPI signer for your program
316/// const LIGHT_CPI_SIGNER: CpiSigner = derive_light_cpi_signer!("SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7");
317///
318/// // Access individual fields:
319/// const PROGRAM_ID: [u8; 32] = LIGHT_CPI_SIGNER.program_id;
320/// const CPI_SIGNER: [u8; 32] = LIGHT_CPI_SIGNER.cpi_signer;
321/// const BUMP: u8 = LIGHT_CPI_SIGNER.bump;
322/// ```
323///
324/// This macro computes all values during compile time and returns a CpiSigner struct
325/// containing the program ID, CPI signer address, and bump seed.
326#[proc_macro]
327pub fn derive_light_cpi_signer(input: TokenStream) -> TokenStream {
328    cpi_signer::derive_light_cpi_signer(input)
329}