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