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,
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 .unwrap_or_else(|| gen_discriminator("event", event_name));
45
46 let ret = quote! {
47 #[derive(AnchorSerialize, AnchorDeserialize)]
48 #event_strct
49
50 impl anchor_lang::Event for #event_name {
51 fn data(&self) -> Vec<u8> {
52 let mut data = Vec::with_capacity(256);
53 data.extend_from_slice(#event_name::DISCRIMINATOR);
54 self.serialize(&mut data).unwrap();
55 data
56 }
57 }
58
59 impl anchor_lang::Discriminator for #event_name {
60 const DISCRIMINATOR: &'static [u8] = #discriminator;
61 }
62 };
63
64 #[cfg(feature = "idl-build")]
65 {
66 let idl_build = anchor_syn::idl::gen_idl_print_fn_event(&event_strct);
67 return proc_macro::TokenStream::from(quote! {
68 #ret
69 #idl_build
70 });
71 }
72
73 #[allow(unreachable_code)]
74 proc_macro::TokenStream::from(ret)
75}
76
77/// Logs an event that can be subscribed to by clients.
78/// Uses the [`sol_log_data`](https://docs.rs/solana-program/latest/solana_program/log/fn.sol_log_data.html)
79/// syscall which results in the following log:
80/// ```ignore
81/// Program data: <Base64EncodedEvent>
82/// ```
83/// # Example
84///
85/// ```rust,ignore
86/// use anchor_lang::prelude::*;
87///
88/// // handler function inside #[program]
89/// pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
90/// emit!(MyEvent {
91/// data: 5,
92/// label: [1,2,3,4,5],
93/// });
94/// Ok(())
95/// }
96///
97/// #[event]
98/// pub struct MyEvent {
99/// pub data: u64,
100/// pub label: [u8; 5],
101/// }
102/// ```
103#[proc_macro]
104pub fn emit(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
105 let data: proc_macro2::TokenStream = input.into();
106 proc_macro::TokenStream::from(quote! {
107 {
108 anchor_lang::solana_program::log::sol_log_data(&[&anchor_lang::Event::data(&#data)]);
109 }
110 })
111}
112
113/// Log an event by making a self-CPI that can be subscribed to by clients.
114///
115/// This way of logging events is more reliable than [`emit!`](emit!) because RPCs are less likely
116/// to truncate CPI information than program logs.
117///
118/// Uses a [`invoke_signed`](https://docs.rs/solana-program/latest/solana_program/program/fn.invoke_signed.html)
119/// syscall to store the event data in the ledger, which results in the data being stored in the
120/// transaction metadata.
121///
122/// This method requires the usage of an additional PDA to guarantee that the self-CPI is truly
123/// being invoked by the same program. Requiring this PDA to be a signer during `invoke_signed`
124/// syscall ensures that the program is the one doing the logging.
125///
126/// The necessary accounts are added to the accounts struct via [`#[event_cpi]`](event_cpi)
127/// attribute macro.
128///
129/// # Example
130///
131/// ```ignore
132/// use anchor_lang::prelude::*;
133///
134/// #[program]
135/// pub mod my_program {
136/// use super::*;
137///
138/// pub fn my_instruction(ctx: Context<MyInstruction>) -> Result<()> {
139/// emit_cpi!(MyEvent { data: 42 });
140/// Ok(())
141/// }
142/// }
143///
144/// #[event_cpi]
145/// #[derive(Accounts)]
146/// pub struct MyInstruction {}
147///
148/// #[event]
149/// pub struct MyEvent {
150/// pub data: u64,
151/// }
152/// ```
153///
154/// **NOTE:** This macro requires `ctx` to be in scope.
155///
156/// *Only available with `event-cpi` feature enabled.*
157#[cfg(feature = "event-cpi")]
158#[proc_macro]
159pub fn emit_cpi(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
160 let event_struct = parse_macro_input!(input as syn::Expr);
161
162 let authority = EventAuthority::get();
163 let authority_name = authority.name_token_stream();
164 let authority_seeds = authority.seeds;
165
166 proc_macro::TokenStream::from(quote! {
167 {
168 let authority_info = ctx.accounts.#authority_name.to_account_info();
169
170 let disc = anchor_lang::event::EVENT_IX_TAG_LE;
171 let inner_data = anchor_lang::Event::data(&#event_struct);
172 let ix_data: Vec<u8> = disc
173 .into_iter()
174 .map(|b| *b)
175 .chain(inner_data.into_iter())
176 .collect();
177
178 let ix = anchor_lang::solana_program::instruction::Instruction::new_with_bytes(
179 // In a doctest the ID will be in the current scope, not the crate root
180 #[cfg(not(doctest))]
181 { crate::ID },
182 #[cfg(doctest)]
183 { ID },
184 &ix_data,
185 vec![
186 anchor_lang::solana_program::instruction::AccountMeta::new_readonly(
187 *authority_info.key,
188 true,
189 ),
190 ],
191 );
192 anchor_lang::solana_program::program::invoke_signed(
193 &ix,
194 &[authority_info],
195 &[&[#authority_seeds, &[crate::EVENT_AUTHORITY_AND_BUMP.1]]],
196 )
197 .map_err(anchor_lang::error::Error::from)?;
198 }
199 })
200}
201
202/// An attribute macro to add necessary event CPI accounts to the given accounts struct.
203///
204/// Two accounts named `event_authority` and `program` will be appended to the list of accounts.
205///
206/// # Example
207///
208/// ```ignore
209/// #[event_cpi]
210/// #[derive(Accounts)]
211/// pub struct MyInstruction<'info> {
212/// pub signer: Signer<'info>,
213/// }
214/// ```
215///
216/// The code above will be expanded to:
217///
218/// ```ignore
219/// #[derive(Accounts)]
220/// pub struct MyInstruction<'info> {
221/// pub signer: Signer<'info>,
222/// /// CHECK: Only the event authority can invoke self-CPI
223/// #[account(seeds = [b"__event_authority"], bump)]
224/// pub event_authority: UncheckedAccount<'info>,
225/// /// CHECK: Self-CPI will fail if the program is not the current program
226/// pub program: UncheckedAccount<'info>,
227/// }
228/// ```
229///
230/// See [`emit_cpi!`](emit_cpi!) for a full example.
231///
232/// *Only available with `event-cpi` feature enabled.*
233#[cfg(feature = "event-cpi")]
234#[proc_macro_attribute]
235pub fn event_cpi(
236 _attr: proc_macro::TokenStream,
237 input: proc_macro::TokenStream,
238) -> proc_macro::TokenStream {
239 let accounts_struct = parse_macro_input!(input as syn::ItemStruct);
240 #[allow(
241 clippy::unwrap_used,
242 reason = "quote-generated struct tokens always parse"
243 )]
244 let accounts_struct = add_event_cpi_accounts(&accounts_struct).unwrap();
245 proc_macro::TokenStream::from(quote! {#accounts_struct})
246}