Skip to main content

rialo_sol_attribute_event/
lib.rs

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