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}