|
1 | 1 | extern crate proc_macro; |
2 | 2 |
|
| 3 | +#[cfg(feature = "event-cpi")] |
| 4 | +use anchor_syn::parser::accounts::event_cpi::{add_event_cpi_accounts, EventAuthority}; |
3 | 5 | use quote::quote; |
4 | 6 | use syn::parse_macro_input; |
5 | 7 |
|
@@ -45,6 +47,14 @@ pub fn event( |
45 | 47 | }) |
46 | 48 | } |
47 | 49 |
|
| 50 | +// EventIndex is a marker macro. It functionally does nothing other than |
| 51 | +// allow one to mark fields with the `#[index]` inert attribute, which is |
| 52 | +// used to add metadata to IDLs. |
| 53 | +#[proc_macro_derive(EventIndex, attributes(index))] |
| 54 | +pub fn derive_event(_item: proc_macro::TokenStream) -> proc_macro::TokenStream { |
| 55 | + proc_macro::TokenStream::from(quote! {}) |
| 56 | +} |
| 57 | + |
48 | 58 | /// Logs an event that can be subscribed to by clients. |
49 | 59 | /// Uses the [`sol_log_data`](https://docs.rs/solana-program/latest/solana_program/log/fn.sol_log_data.html) |
50 | 60 | /// syscall which results in the following log: |
@@ -81,10 +91,127 @@ pub fn emit(input: proc_macro::TokenStream) -> proc_macro::TokenStream { |
81 | 91 | }) |
82 | 92 | } |
83 | 93 |
|
84 | | -// EventIndex is a marker macro. It functionally does nothing other than |
85 | | -// allow one to mark fields with the `#[index]` inert attribute, which is |
86 | | -// used to add metadata to IDLs. |
87 | | -#[proc_macro_derive(EventIndex, attributes(index))] |
88 | | -pub fn derive_event(_item: proc_macro::TokenStream) -> proc_macro::TokenStream { |
89 | | - proc_macro::TokenStream::from(quote! {}) |
| 94 | +/// Log an event by making a self-CPI that can be subscribed to by clients. |
| 95 | +/// |
| 96 | +/// This way of logging events is more reliable than [`emit!`](emit!) because RPCs are less likely |
| 97 | +/// to truncate CPI information than program logs. |
| 98 | +/// |
| 99 | +/// Uses a [`invoke_signed`](https://docs.rs/solana-program/latest/solana_program/program/fn.invoke_signed.html) |
| 100 | +/// syscall to store the event data in the ledger, which results in the data being stored in the |
| 101 | +/// transaction metadata. |
| 102 | +/// |
| 103 | +/// This method requires the usage of an additional PDA to guarantee that the self-CPI is truly |
| 104 | +/// being invoked by the same program. Requiring this PDA to be a signer during `invoke_signed` |
| 105 | +/// syscall ensures that the program is the one doing the logging. |
| 106 | +/// |
| 107 | +/// The necessary accounts are added to the accounts struct via [`#[event_cpi]`](event_cpi) |
| 108 | +/// attribute macro. |
| 109 | +/// |
| 110 | +/// # Example |
| 111 | +/// |
| 112 | +/// ```ignore |
| 113 | +/// use anchor_lang::prelude::*; |
| 114 | +/// |
| 115 | +/// #[program] |
| 116 | +/// pub mod my_program { |
| 117 | +/// use super::*; |
| 118 | +/// |
| 119 | +/// pub fn my_instruction(ctx: Context<MyInstruction>) -> Result<()> { |
| 120 | +/// emit_cpi!(MyEvent { data: 42 }); |
| 121 | +/// Ok(()) |
| 122 | +/// } |
| 123 | +/// } |
| 124 | +/// |
| 125 | +/// #[event_cpi] |
| 126 | +/// #[derive(Accounts)] |
| 127 | +/// pub struct MyInstruction {} |
| 128 | +/// |
| 129 | +/// #[event] |
| 130 | +/// pub struct MyEvent { |
| 131 | +/// pub data: u64, |
| 132 | +/// } |
| 133 | +/// ``` |
| 134 | +/// |
| 135 | +/// **NOTE:** This macro requires `ctx` to be in scope. |
| 136 | +/// |
| 137 | +/// *Only available with `event-cpi` feature enabled.* |
| 138 | +#[cfg(feature = "event-cpi")] |
| 139 | +#[proc_macro] |
| 140 | +pub fn emit_cpi(input: proc_macro::TokenStream) -> proc_macro::TokenStream { |
| 141 | + let event_struct = parse_macro_input!(input as syn::Expr); |
| 142 | + |
| 143 | + let authority = EventAuthority::get(); |
| 144 | + let authority_name = authority.name_token_stream(); |
| 145 | + let authority_name_str = authority.name; |
| 146 | + let authority_seeds = authority.seeds; |
| 147 | + |
| 148 | + proc_macro::TokenStream::from(quote! { |
| 149 | + { |
| 150 | + let authority_info = ctx.accounts.#authority_name.to_account_info(); |
| 151 | + let authority_bump = *ctx.bumps.get(#authority_name_str).unwrap(); |
| 152 | + |
| 153 | + let disc = anchor_lang::event::EVENT_IX_TAG_LE; |
| 154 | + let inner_data = anchor_lang::Event::data(&#event_struct); |
| 155 | + let ix_data: Vec<u8> = disc.into_iter().chain(inner_data.into_iter()).collect(); |
| 156 | + |
| 157 | + let ix = anchor_lang::solana_program::instruction::Instruction::new_with_bytes( |
| 158 | + crate::ID, |
| 159 | + &ix_data, |
| 160 | + vec![ |
| 161 | + anchor_lang::solana_program::instruction::AccountMeta::new_readonly( |
| 162 | + *authority_info.key, |
| 163 | + true, |
| 164 | + ), |
| 165 | + ], |
| 166 | + ); |
| 167 | + anchor_lang::solana_program::program::invoke_signed( |
| 168 | + &ix, |
| 169 | + &[authority_info], |
| 170 | + &[&[#authority_seeds, &[authority_bump]]], |
| 171 | + ) |
| 172 | + .map_err(anchor_lang::error::Error::from)?; |
| 173 | + } |
| 174 | + }) |
| 175 | +} |
| 176 | + |
| 177 | +/// An attribute macro to add necessary event CPI accounts to the given accounts struct. |
| 178 | +/// |
| 179 | +/// Two accounts named `event_authority` and `program` will be appended to the list of accounts. |
| 180 | +/// |
| 181 | +/// # Example |
| 182 | +/// |
| 183 | +/// ```ignore |
| 184 | +/// #[event_cpi] |
| 185 | +/// #[derive(Accounts)] |
| 186 | +/// pub struct MyInstruction<'info> { |
| 187 | +/// pub signer: Signer<'info>, |
| 188 | +/// } |
| 189 | +/// ``` |
| 190 | +/// |
| 191 | +/// The code above will be expanded to: |
| 192 | +/// |
| 193 | +/// ```ignore |
| 194 | +/// #[derive(Accounts)] |
| 195 | +/// pub struct MyInstruction<'info> { |
| 196 | +/// pub signer: Signer<'info>, |
| 197 | +/// /// CHECK: Only the event authority can invoke self-CPI |
| 198 | +/// #[account(seeds = [b"__event_authority"], bump)] |
| 199 | +/// pub event_authority: AccountInfo<'info>, |
| 200 | +/// /// CHECK: Self-CPI will fail if the program is not the current program |
| 201 | +/// pub program: AccountInfo<'info>, |
| 202 | +/// } |
| 203 | +/// ``` |
| 204 | +/// |
| 205 | +/// See [`emit_cpi!`](emit_cpi!) for a full example. |
| 206 | +/// |
| 207 | +/// *Only available with `event-cpi` feature enabled.* |
| 208 | +#[cfg(feature = "event-cpi")] |
| 209 | +#[proc_macro_attribute] |
| 210 | +pub fn event_cpi( |
| 211 | + _attr: proc_macro::TokenStream, |
| 212 | + input: proc_macro::TokenStream, |
| 213 | +) -> proc_macro::TokenStream { |
| 214 | + let accounts_struct = parse_macro_input!(input as syn::ItemStruct); |
| 215 | + let accounts_struct = add_event_cpi_accounts(&accounts_struct).unwrap(); |
| 216 | + proc_macro::TokenStream::from(quote! {#accounts_struct}) |
90 | 217 | } |
0 commit comments