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}