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}