Skip to main content

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}