bolt_anchor_attribute_event/
lib.rs

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