Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
1b9ff06
add permissionless event cpi api
ngundotra Mar 17, 2023
eed5e6f
add cpi event test
ngundotra Mar 17, 2023
6c9fced
move __emit_cpi_invoke to __private in lib.rs
ngundotra Mar 20, 2023
e1d234c
export emit_cpi and _emit_cpi_data in prelude
ngundotra Mar 24, 2023
f5c5ab9
remove empty file
ngundotra Mar 24, 2023
bca57d6
rewrite emit_cpi as a proc_macro
ngundotra Mar 24, 2023
ffd6fb1
remove unused code
ngundotra Mar 30, 2023
aadba4e
inline _emit_cpi_invoke to proc_macro declaration
ngundotra Mar 30, 2023
bbd869b
address acheron feedback
ngundotra May 3, 2023
7bc2c25
optimize emit macro to reduce cloning
ngundotra May 3, 2023
3d04a71
explicitly only parse two args
ngundotra May 3, 2023
014256f
update events package.json
ngundotra May 3, 2023
76ee7be
add event instruction error code to anchor
ngundotra May 4, 2023
f4c225c
add event authority
ngundotra May 4, 2023
25bc040
require event authority PDA to sign
ngundotra May 5, 2023
4786e85
turn on seeds to hide eventAuthority
ngundotra May 5, 2023
b072f59
change feature to cpi-events
ngundotra May 5, 2023
0b678cf
fix no-idl, no-cpi-events, and cpi-events features
ngundotra May 5, 2023
b7ccc2a
update tests
ngundotra May 5, 2023
8c2fbdc
fix no-idl cfg dispatch
ngundotra May 5, 2023
e68be79
fix tests/events
ngundotra May 5, 2023
d9cd325
remove cpi-events from Anchor.toml
ngundotra May 5, 2023
f9bfeff
add documentation
ngundotra May 5, 2023
5634cef
slightly better interface for self-program in ctx
ngundotra May 5, 2023
9cd071f
Remove accounts and bump argument
acheroncrypto May 11, 2023
8b7f515
Add `event_cpi` attribute macro
acheroncrypto May 11, 2023
1f456ea
Generate IDL accounts with `event_cpi` macro
acheroncrypto May 11, 2023
fe49ef6
Resolve event CPI accounts in client
acheroncrypto May 11, 2023
397108b
Update tests
acheroncrypto May 11, 2023
cdd9776
Fix clippy
acheroncrypto May 23, 2023
8cc2642
Remove accounts from test
acheroncrypto May 23, 2023
690e8c7
Remove Anchor.toml features in tests
acheroncrypto May 23, 2023
66d72b1
Add malicious invocation test
acheroncrypto May 23, 2023
87652d2
Validate authority in the self-cpi handler to block malicious invocat…
acheroncrypto May 23, 2023
eb051ef
Make `event-cpi` feature opt-in instead of opt-out
acheroncrypto May 24, 2023
fd182fe
Fix parsing multiple fields
acheroncrypto May 24, 2023
852b8bd
Generate attributes and fields inside the main `TokenStream`
acheroncrypto May 25, 2023
aa11826
Add documentation
acheroncrypto May 25, 2023
ac45fc7
Add a note about `ctx` being in scope
acheroncrypto May 25, 2023
9b17a4c
Merge master
acheroncrypto May 26, 2023
22b902a
Update CHANGELOG
acheroncrypto May 26, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ The minor version will be incremented upon a breaking change and the patch versi
- cli: Add support for Solidity programs. `anchor init` and `anchor new` take an option `--solidity` which creates solidity code rather than rust. `anchor build` and `anchor test` work accordingly ([#2421](https://github.com/coral-xyz/anchor/pull/2421))
- bench: Add benchmarking for compute units usage ([#2466](https://github.com/coral-xyz/anchor/pull/2466))
- cli: `idl set-buffer`, `idl set-authority` and `idl close` take an option `--print-only`. which prints transaction in a base64 Borsh compatible format but not sent to the cluster. It's helpful when managing authority under a multisig, e.g., a user can create a proposal for a `Custom Instruction` in SPL Governance ([#2486](https://github.com/coral-xyz/anchor/pull/2486)).
- lang: Add `emit_cpi!` and `#[event_cpi]` macros(behind `event-cpi` feature flag) to store event logs in transaction metadata ([#2438](https://github.com/coral-xyz/anchor/pull/2438)).

### Fixes

Expand Down
2 changes: 1 addition & 1 deletion cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ bincode = "1.3.3"
syn = { version = "1.0.60", features = ["full", "extra-traits"] }
anchor-lang = { path = "../lang", version = "0.27.0" }
anchor-client = { path = "../client", version = "0.27.0" }
anchor-syn = { path = "../lang/syn", features = ["idl", "init-if-needed"], version = "0.27.0" }
anchor-syn = { path = "../lang/syn", features = ["event-cpi", "idl", "init-if-needed"], version = "0.27.0" }
serde_json = "1.0"
shellexpand = "2.1.0"
toml = "0.5.8"
Expand Down
1 change: 1 addition & 0 deletions lang/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ allow-missing-optionals = ["anchor-derive-accounts/allow-missing-optionals"]
init-if-needed = ["anchor-derive-accounts/init-if-needed"]
derive = []
default = []
event-cpi = ["anchor-attribute-event/event-cpi"]
anchor-debug = [
"anchor-attribute-access-control/anchor-debug",
"anchor-attribute-account/anchor-debug",
Expand Down
1 change: 1 addition & 0 deletions lang/attribute/event/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ proc-macro = true

[features]
anchor-debug = ["anchor-syn/anchor-debug"]
event-cpi = ["anchor-syn/event-cpi"]

[dependencies]
proc-macro2 = "1.0"
Expand Down
139 changes: 133 additions & 6 deletions lang/attribute/event/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
extern crate proc_macro;

#[cfg(feature = "event-cpi")]
use anchor_syn::parser::accounts::event_cpi::{add_event_cpi_accounts, EventAuthority};
use quote::quote;
use syn::parse_macro_input;

Expand Down Expand Up @@ -45,6 +47,14 @@ pub fn event(
})
}

// EventIndex is a marker macro. It functionally does nothing other than
// allow one to mark fields with the `#[index]` inert attribute, which is
// used to add metadata to IDLs.
#[proc_macro_derive(EventIndex, attributes(index))]
pub fn derive_event(_item: proc_macro::TokenStream) -> proc_macro::TokenStream {
proc_macro::TokenStream::from(quote! {})
}

/// Logs an event that can be subscribed to by clients.
/// Uses the [`sol_log_data`](https://docs.rs/solana-program/latest/solana_program/log/fn.sol_log_data.html)
/// syscall which results in the following log:
Expand Down Expand Up @@ -81,10 +91,127 @@ pub fn emit(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
})
}

// EventIndex is a marker macro. It functionally does nothing other than
// allow one to mark fields with the `#[index]` inert attribute, which is
// used to add metadata to IDLs.
#[proc_macro_derive(EventIndex, attributes(index))]
pub fn derive_event(_item: proc_macro::TokenStream) -> proc_macro::TokenStream {
proc_macro::TokenStream::from(quote! {})
/// Log an event by making a self-CPI that can be subscribed to by clients.
///
/// This way of logging events is more reliable than [`emit!`](emit!) because RPCs are less likely
/// to truncate CPI information than program logs.
///
/// Uses a [`invoke_signed`](https://docs.rs/solana-program/latest/solana_program/program/fn.invoke_signed.html)
/// syscall to store the event data in the ledger, which results in the data being stored in the
/// transaction metadata.
///
/// This method requires the usage of an additional PDA to guarantee that the self-CPI is truly
/// being invoked by the same program. Requiring this PDA to be a signer during `invoke_signed`
/// syscall ensures that the program is the one doing the logging.
///
/// The necessary accounts are added to the accounts struct via [`#[event_cpi]`](event_cpi)
/// attribute macro.
///
/// # Example
///
/// ```ignore
/// use anchor_lang::prelude::*;
///
/// #[program]
/// pub mod my_program {
/// use super::*;
///
/// pub fn my_instruction(ctx: Context<MyInstruction>) -> Result<()> {
/// emit_cpi!(MyEvent { data: 42 });
/// Ok(())
/// }
/// }
///
/// #[event_cpi]
/// #[derive(Accounts)]
/// pub struct MyInstruction {}
///
/// #[event]
/// pub struct MyEvent {
/// pub data: u64,
/// }
/// ```
///
/// **NOTE:** This macro requires `ctx` to be in scope.
///
/// *Only available with `event-cpi` feature enabled.*
#[cfg(feature = "event-cpi")]
#[proc_macro]
pub fn emit_cpi(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let event_struct = parse_macro_input!(input as syn::Expr);

let authority = EventAuthority::get();
let authority_name = authority.name_token_stream();
let authority_name_str = authority.name;
let authority_seeds = authority.seeds;

proc_macro::TokenStream::from(quote! {
{
let authority_info = ctx.accounts.#authority_name.to_account_info();
let authority_bump = *ctx.bumps.get(#authority_name_str).unwrap();

let disc = anchor_lang::event::EVENT_IX_TAG_LE;
let inner_data = anchor_lang::Event::data(&#event_struct);
let ix_data: Vec<u8> = disc.into_iter().chain(inner_data.into_iter()).collect();

let ix = anchor_lang::solana_program::instruction::Instruction::new_with_bytes(
crate::ID,
&ix_data,
vec![
anchor_lang::solana_program::instruction::AccountMeta::new_readonly(
*authority_info.key,
true,
),
],
);
anchor_lang::solana_program::program::invoke_signed(
&ix,
&[authority_info],
&[&[#authority_seeds, &[authority_bump]]],
)
.map_err(anchor_lang::error::Error::from)?;
}
})
}

/// An attribute macro to add necessary event CPI accounts to the given accounts struct.
///
/// Two accounts named `event_authority` and `program` will be appended to the list of accounts.
///
/// # Example
///
/// ```ignore
/// #[event_cpi]
/// #[derive(Accounts)]
/// pub struct MyInstruction<'info> {
/// pub signer: Signer<'info>,
/// }
/// ```
///
/// The code above will be expanded to:
///
/// ```ignore
/// #[derive(Accounts)]
/// pub struct MyInstruction<'info> {
/// pub signer: Signer<'info>,
/// /// CHECK: Only the event authority can invoke self-CPI
/// #[account(seeds = [b"__event_authority"], bump)]
/// pub event_authority: AccountInfo<'info>,
/// /// CHECK: Self-CPI will fail if the program is not the current program
/// pub program: AccountInfo<'info>,
/// }
/// ```
///
/// See [`emit_cpi!`](emit_cpi!) for a full example.
///
/// *Only available with `event-cpi` feature enabled.*
#[cfg(feature = "event-cpi")]
#[proc_macro_attribute]
pub fn event_cpi(
_attr: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let accounts_struct = parse_macro_input!(input as syn::ItemStruct);
let accounts_struct = add_event_cpi_accounts(&accounts_struct).unwrap();
proc_macro::TokenStream::from(quote! {#accounts_struct})
}
5 changes: 5 additions & 0 deletions lang/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ pub enum ErrorCode {
#[msg("IDL account must be empty in order to resize, try closing first")]
IdlAccountNotEmpty,

// Event instructions
/// 1500 - The program was compiled without `event-cpi` feature
#[msg("The program was compiled without `event-cpi` feature")]
EventInstructionStub = 1500,

// Constraints
/// 2000 - A mut constraint was violated
#[msg("A mut constraint was violated")]
Expand Down
3 changes: 3 additions & 0 deletions lang/src/event.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Sha256(anchor:event)[..8]
pub const EVENT_IX_TAG: u64 = 0x1d9acb512ea545e4;
pub const EVENT_IX_TAG_LE: [u8; 8] = EVENT_IX_TAG.to_le_bytes();
6 changes: 6 additions & 0 deletions lang/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ mod common;
pub mod context;
pub mod error;
#[doc(hidden)]
pub mod event;
#[doc(hidden)]
pub mod idl;
pub mod system_program;

Expand All @@ -48,6 +50,8 @@ pub use anchor_attribute_account::{account, declare_id, zero_copy};
pub use anchor_attribute_constant::constant;
pub use anchor_attribute_error::*;
pub use anchor_attribute_event::{emit, event};
#[cfg(feature = "event-cpi")]
pub use anchor_attribute_event::{emit_cpi, event_cpi};
pub use anchor_attribute_program::program;
pub use anchor_derive_accounts::Accounts;
pub use anchor_derive_space::InitSpace;
Expand Down Expand Up @@ -299,6 +303,8 @@ pub mod prelude {
AccountsClose, AccountsExit, AnchorDeserialize, AnchorSerialize, Id, InitSpace, Key, Owner,
ProgramData, Result, Space, ToAccountInfo, ToAccountInfos, ToAccountMetas,
};
#[cfg(feature = "event-cpi")]
pub use super::{emit_cpi, event_cpi};
pub use anchor_attribute_error::*;
pub use borsh;
pub use error::*;
Expand Down
1 change: 1 addition & 0 deletions lang/syn/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ hash = []
default = []
anchor-debug = []
seeds = []
event-cpi = []

[dependencies]
proc-macro2 = { version = "1.0", features=["span-locations"]}
Expand Down
31 changes: 28 additions & 3 deletions lang/syn/src/codegen/program/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,13 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
}
})
.collect();

let fallback_fn = gen_fallback(program).unwrap_or(quote! {
Err(anchor_lang::error::ErrorCode::InstructionFallbackNotFound.into())
});

let event_cpi_handler = generate_event_cpi_handler();

quote! {
/// Performs method dispatch.
///
Expand Down Expand Up @@ -67,17 +71,24 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
#(#global_dispatch_arms)*
anchor_lang::idl::IDL_IX_TAG_LE => {
// If the method identifier is the IDL tag, then execute an IDL
// instruction, injected into all Anchor programs.
if cfg!(not(feature = "no-idl")) {
// instruction, injected into all Anchor programs unless they have
// no-idl enabled
#[cfg(not(feature = "no-idl"))]
{
__private::__idl::__idl_dispatch(
program_id,
accounts,
&ix_data,
)
} else {
}
#[cfg(feature = "no-idl")]
{
Err(anchor_lang::error::ErrorCode::IdlInstructionStub.into())
}
}
anchor_lang::event::EVENT_IX_TAG_LE => {
#event_cpi_handler
}
_ => {
#fallback_fn
}
Expand All @@ -96,3 +107,17 @@ pub fn gen_fallback(program: &Program) -> Option<proc_macro2::TokenStream> {
}
})
}

/// Generate the event-cpi instruction handler based on whether the `event-cpi` feature is enabled.
pub fn generate_event_cpi_handler() -> proc_macro2::TokenStream {
#[cfg(feature = "event-cpi")]
quote! {
// `event-cpi` feature is enabled, dispatch self-cpi instruction
__private::__events::__event_dispatch(program_id, accounts, &ix_data)
}
#[cfg(not(feature = "event-cpi"))]
quote! {
// `event-cpi` feature is not enabled
Err(anchor_lang::error::ErrorCode::EventInstructionStub.into())
}
}
52 changes: 50 additions & 2 deletions lang/syn/src/codegen/program/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
}
};

let event_cpi_mod = generate_event_cpi_mod();

let non_inlined_handlers: Vec<proc_macro2::TokenStream> = program
.ixs
.iter()
Expand Down Expand Up @@ -173,14 +175,14 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
#idl_accounts_and_functions
}



/// __global mod defines wrapped handlers for global instructions.
pub mod __global {
use super::*;

#(#non_inlined_handlers)*
}

#event_cpi_mod
}
}
}
Expand All @@ -189,3 +191,49 @@ fn generate_ix_variant_name(name: String) -> proc_macro2::TokenStream {
let n = name.to_camel_case();
n.parse().unwrap()
}

/// Generate the event module based on whether the `event-cpi` feature is enabled.
fn generate_event_cpi_mod() -> proc_macro2::TokenStream {
#[cfg(feature = "event-cpi")]
{
let authority = crate::parser::accounts::event_cpi::EventAuthority::get();
let authority_name = authority.name;
let authority_seeds = authority.seeds;

quote! {
/// __events mod defines handler for self-cpi based event logging
pub mod __events {
use super::*;

#[inline(never)]
pub fn __event_dispatch(
program_id: &Pubkey,
accounts: &[AccountInfo],
event_data: &[u8],
) -> anchor_lang::Result<()> {
let given_event_authority = next_account_info(&mut accounts.iter())?;
if !given_event_authority.is_signer {
return Err(anchor_lang::error::Error::from(
anchor_lang::error::ErrorCode::ConstraintSigner,
)
.with_account_name(#authority_name));
}

let (expected_event_authority, _) =
Pubkey::find_program_address(&[#authority_seeds], &program_id);
if given_event_authority.key() != expected_event_authority {
return Err(anchor_lang::error::Error::from(
anchor_lang::error::ErrorCode::ConstraintSeeds,
)
.with_account_name(#authority_name)
.with_pubkeys((given_event_authority.key(), expected_event_authority)));
}

Ok(())
}
}
}
}
#[cfg(not(feature = "event-cpi"))]
quote! {}
}
Loading