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 {
6 anchor_syn::{codegen::program::common::gen_discriminator, Overrides},
7 quote::{quote, ToTokens},
8 syn::parse_macro_input,
9};
10
11/// The event attribute allows a struct to be used with
12/// [emit!](./macro.emit.html) so that programs can log significant events in
13/// their programs that clients can subscribe to. Currently, this macro is for
14/// structs only.
15///
16/// # Arguments
17///
18/// - `discriminator`: Override the default 8-byte discriminator
19///
20/// **Usage:** `discriminator = <CONST_EXPR>`
21///
22/// All constant expressions are supported.
23///
24/// **Examples:**
25///
26/// - `discriminator = 1` (shortcut for `[1]`)
27/// - `discriminator = [1, 2, 3, 4]`
28/// - `discriminator = b"hi"`
29/// - `discriminator = MY_DISC`
30/// - `discriminator = get_disc(...)`
31///
32/// See the [`emit!` macro](emit!) for an example.
33#[proc_macro_attribute]
34pub fn event(
35 args: proc_macro::TokenStream,
36 input: proc_macro::TokenStream,
37) -> proc_macro::TokenStream {
38 let args = parse_macro_input!(args as Overrides);
39 let event_strct = parse_macro_input!(input as syn::ItemStruct);
40 let event_name = &event_strct.ident;
41
42 let discriminator = args
43 .discriminator
44 .map(|d| d.to_token_stream())
45 .unwrap_or_else(|| gen_discriminator("event", event_name));
46
47 let ret = quote! {
48 #[derive(AnchorSerialize, AnchorDeserialize)]
49 #event_strct
50
51 impl anchor_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 anchor_lang::Discriminator for #event_name {
61 const DISCRIMINATOR: &'static [u8] = #discriminator;
62 }
63 };
64
65 #[cfg(feature = "idl-build")]
66 {
67 let idl_build = anchor_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 [`sol_log_data`](https://docs.rs/solana-program/latest/solana_program/log/fn.sol_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 anchor_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 anchor_lang::solana_program::log::sol_log_data(&[&anchor_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/solana_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 anchor_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 let authority_info = syn::Ident::new("authority_info", proc_macro2::Span::mixed_site());
167 let disc = syn::Ident::new("disc", proc_macro2::Span::mixed_site());
168 let inner_data = syn::Ident::new("inner_data", proc_macro2::Span::mixed_site());
169 let ix_data = syn::Ident::new("ix_data", proc_macro2::Span::mixed_site());
170 let ix = syn::Ident::new("ix", proc_macro2::Span::mixed_site());
171
172 proc_macro::TokenStream::from(quote! {
173 {
174 let #authority_info = ctx.accounts.#authority_name.to_account_info();
175
176 let #disc = anchor_lang::event::EVENT_IX_TAG_LE;
177 let #inner_data = anchor_lang::Event::data(&#event_struct);
178 let #ix_data: Vec<u8> = #disc
179 .into_iter()
180 .map(|b| *b)
181 .chain(#inner_data.into_iter())
182 .collect();
183
184 let #ix = anchor_lang::solana_program::instruction::Instruction::new_with_bytes(
185 // In a doctest the ID will be in the current scope, not the crate root
186 #[cfg(not(doctest))]
187 { crate::ID },
188 #[cfg(doctest)]
189 { ID },
190 &#ix_data,
191 vec![
192 anchor_lang::solana_program::instruction::AccountMeta::new_readonly(
193 *#authority_info.key,
194 true,
195 ),
196 ],
197 );
198 anchor_lang::solana_program::program::invoke_signed(
199 &#ix,
200 &[#authority_info],
201 &[&[#authority_seeds, &[crate::EVENT_AUTHORITY_AND_BUMP.1]]],
202 )
203 .map_err(anchor_lang::error::Error::from)?;
204 }
205 })
206}
207
208/// An attribute macro to add necessary event CPI accounts to the given accounts struct.
209///
210/// Two accounts named `event_authority` and `program` will be appended to the list of accounts.
211///
212/// # Example
213///
214/// ```ignore
215/// use anchor_lang::prelude::*;
216///
217/// #[event_cpi]
218/// #[derive(Accounts)]
219/// pub struct MyInstruction<'info> {
220/// pub signer: Signer<'info>,
221/// }
222/// ```
223///
224/// The code above will be expanded to:
225///
226/// ```ignore
227/// use anchor_lang::prelude::*;
228///
229/// #[derive(Accounts)]
230/// pub struct MyInstruction<'info> {
231/// pub signer: Signer<'info>,
232/// /// CHECK: Only the event authority can invoke self-CPI
233/// #[account(address = crate::EVENT_AUTHORITY_AND_BUMP.0)]
234/// pub event_authority: UncheckedAccount<'info>,
235/// /// CHECK: Self-CPI will fail if the program is not the current program
236/// pub program: UncheckedAccount<'info>,
237/// }
238/// ```
239///
240/// See [`emit_cpi!`](emit_cpi!) for a full example.
241///
242/// *Only available with `event-cpi` feature enabled.*
243#[cfg(feature = "event-cpi")]
244#[proc_macro_attribute]
245pub fn event_cpi(
246 _attr: proc_macro::TokenStream,
247 input: proc_macro::TokenStream,
248) -> proc_macro::TokenStream {
249 let accounts_struct = parse_macro_input!(input as syn::ItemStruct);
250 #[allow(
251 clippy::unwrap_used,
252 reason = "quote-generated struct tokens always parse"
253 )]
254 let accounts_struct = add_event_cpi_accounts(&accounts_struct).unwrap();
255 proc_macro::TokenStream::from(quote! {#accounts_struct})
256}