mainstay_attribute_event/lib.rs
1extern crate proc_macro;
2
3#[cfg(feature = "event-cpi")]
4use mainstay_syn::parser::accounts::event_cpi::{add_event_cpi_accounts, EventAuthority};
5use mainstay_syn::{codegen::program::common::gen_discriminator, Overrides};
6use quote::quote;
7use syn::parse_macro_input;
8
9/// The event attribute allows a struct to be used with
10/// [emit!](./macro.emit.html) so that programs can log significant events in
11/// their programs that clients can subscribe to. Currently, this macro is for
12/// structs only.
13///
14/// # Arguments
15///
16/// - `discriminator`: Override the default 8-byte discriminator
17///
18/// **Usage:** `discriminator = <CONST_EXPR>`
19///
20/// All constant expressions are supported.
21///
22/// **Examples:**
23///
24/// - `discriminator = 1` (shortcut for `[1]`)
25/// - `discriminator = [1, 2, 3, 4]`
26/// - `discriminator = b"hi"`
27/// - `discriminator = MY_DISC`
28/// - `discriminator = get_disc(...)`
29///
30/// See the [`emit!` macro](emit!) for an example.
31#[proc_macro_attribute]
32pub fn event(
33 args: proc_macro::TokenStream,
34 input: proc_macro::TokenStream,
35) -> proc_macro::TokenStream {
36 let args = parse_macro_input!(args as Overrides);
37 let event_strct = parse_macro_input!(input as syn::ItemStruct);
38 let event_name = &event_strct.ident;
39
40 let discriminator = args
41 .discriminator
42 .unwrap_or_else(|| gen_discriminator("event", event_name));
43
44 let ret = quote! {
45 #[derive(MainstaySerialize, MainstayDeserialize)]
46 #event_strct
47
48 impl mainstay_lang::Event for #event_name {
49 fn data(&self) -> Vec<u8> {
50 let mut data = Vec::with_capacity(256);
51 data.extend_from_slice(#event_name::DISCRIMINATOR);
52 self.serialize(&mut data).unwrap();
53 data
54 }
55 }
56
57 impl mainstay_lang::Discriminator for #event_name {
58 const DISCRIMINATOR: &'static [u8] = #discriminator;
59 }
60 };
61
62 #[cfg(feature = "idl-build")]
63 {
64 let idl_build = mainstay_syn::idl::gen_idl_print_fn_event(&event_strct);
65 return proc_macro::TokenStream::from(quote! {
66 #ret
67 #idl_build
68 });
69 }
70
71 #[allow(unreachable_code)]
72 proc_macro::TokenStream::from(ret)
73}
74
75/// Logs an event that can be subscribed to by clients.
76/// Uses the [`sol_log_data`](https://docs.rs/solana-program/latest/solana_program/log/fn.sol_log_data.html)
77/// syscall which results in the following log:
78/// ```ignore
79/// Program data: <Base64EncodedEvent>
80/// ```
81/// # Example
82///
83/// ```rust,ignore
84/// use mainstay_lang::prelude::*;
85///
86/// // handler function inside #[program]
87/// pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
88/// emit!(MyEvent {
89/// data: 5,
90/// label: [1,2,3,4,5],
91/// });
92/// Ok(())
93/// }
94///
95/// #[event]
96/// pub struct MyEvent {
97/// pub data: u64,
98/// pub label: [u8; 5],
99/// }
100/// ```
101#[proc_macro]
102pub fn emit(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
103 let data: proc_macro2::TokenStream = input.into();
104 proc_macro::TokenStream::from(quote! {
105 {
106 mainstay_lang::solana_program::log::sol_log_data(&[&mainstay_lang::Event::data(&#data)]);
107 }
108 })
109}
110
111/// Log an event by making a self-CPI that can be subscribed to by clients.
112///
113/// This way of logging events is more reliable than [`emit!`](emit!) because RPCs are less likely
114/// to truncate CPI information than program logs.
115///
116/// Uses a [`invoke_signed`](https://docs.rs/solana-program/latest/solana_program/program/fn.invoke_signed.html)
117/// syscall to store the event data in the ledger, which results in the data being stored in the
118/// transaction metadata.
119///
120/// This method requires the usage of an additional PDA to guarantee that the self-CPI is truly
121/// being invoked by the same program. Requiring this PDA to be a signer during `invoke_signed`
122/// syscall ensures that the program is the one doing the logging.
123///
124/// The necessary accounts are added to the accounts struct via [`#[event_cpi]`](event_cpi)
125/// attribute macro.
126///
127/// # Example
128///
129/// ```ignore
130/// use mainstay_lang::prelude::*;
131///
132/// #[program]
133/// pub mod my_program {
134/// use super::*;
135///
136/// pub fn my_instruction(ctx: Context<MyInstruction>) -> Result<()> {
137/// emit_cpi!(MyEvent { data: 42 });
138/// Ok(())
139/// }
140/// }
141///
142/// #[event_cpi]
143/// #[derive(Accounts)]
144/// pub struct MyInstruction {}
145///
146/// #[event]
147/// pub struct MyEvent {
148/// pub data: u64,
149/// }
150/// ```
151///
152/// **NOTE:** This macro requires `ctx` to be in scope.
153///
154/// *Only available with `event-cpi` feature enabled.*
155#[cfg(feature = "event-cpi")]
156#[proc_macro]
157pub fn emit_cpi(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
158 let event_struct = parse_macro_input!(input as syn::Expr);
159
160 let authority = EventAuthority::get();
161 let authority_name = authority.name_token_stream();
162 let authority_seeds = authority.seeds;
163
164 proc_macro::TokenStream::from(quote! {
165 {
166 let authority_info = ctx.accounts.#authority_name.to_account_info();
167 let authority_bump = ctx.bumps.#authority_name;
168
169 let disc = mainstay_lang::event::EVENT_IX_TAG_LE;
170 let inner_data = mainstay_lang::Event::data(&#event_struct);
171 let ix_data: Vec<u8> = disc
172 .into_iter()
173 .map(|b| *b)
174 .chain(inner_data.into_iter())
175 .collect();
176
177 let ix = mainstay_lang::solana_program::instruction::Instruction::new_with_bytes(
178 crate::ID,
179 &ix_data,
180 vec![
181 mainstay_lang::solana_program::instruction::AccountMeta::new_readonly(
182 *authority_info.key,
183 true,
184 ),
185 ],
186 );
187 mainstay_lang::solana_program::program::invoke_signed(
188 &ix,
189 &[authority_info],
190 &[&[#authority_seeds, &[authority_bump]]],
191 )
192 .map_err(mainstay_lang::error::Error::from)?;
193 }
194 })
195}
196
197/// An attribute macro to add necessary event CPI accounts to the given accounts struct.
198///
199/// Two accounts named `event_authority` and `program` will be appended to the list of accounts.
200///
201/// # Example
202///
203/// ```ignore
204/// #[event_cpi]
205/// #[derive(Accounts)]
206/// pub struct MyInstruction<'info> {
207/// pub signer: Signer<'info>,
208/// }
209/// ```
210///
211/// The code above will be expanded to:
212///
213/// ```ignore
214/// #[derive(Accounts)]
215/// pub struct MyInstruction<'info> {
216/// pub signer: Signer<'info>,
217/// /// CHECK: Only the event authority can invoke self-CPI
218/// #[account(seeds = [b"__event_authority"], bump)]
219/// pub event_authority: AccountInfo<'info>,
220/// /// CHECK: Self-CPI will fail if the program is not the current program
221/// pub program: AccountInfo<'info>,
222/// }
223/// ```
224///
225/// See [`emit_cpi!`](emit_cpi!) for a full example.
226///
227/// *Only available with `event-cpi` feature enabled.*
228#[cfg(feature = "event-cpi")]
229#[proc_macro_attribute]
230pub fn event_cpi(
231 _attr: proc_macro::TokenStream,
232 input: proc_macro::TokenStream,
233) -> proc_macro::TokenStream {
234 let accounts_struct = parse_macro_input!(input as syn::ItemStruct);
235 let accounts_struct = add_event_cpi_accounts(&accounts_struct).unwrap();
236 proc_macro::TokenStream::from(quote! {#accounts_struct})
237}