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