Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lang: Add Event utility type to get events from bytes #2897

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -42,6 +42,7 @@ The minor version will be incremented upon a breaking change and the patch versi
- cli: Add `deactivate_feature` flag to `solana-test-validator` config in Anchor.toml ([#2872](https://github.com/coral-xyz/anchor/pull/2872)).
- idl: Add `docs` field for constants ([#2887](https://github.com/coral-xyz/anchor/pull/2887)).
- idl: Store deployment addresses for other clusters ([#2892](https://github.com/coral-xyz/anchor/pull/2892)).
- lang: Add `Event` utility type to get events from bytes ([#2897](https://github.com/coral-xyz/anchor/pull/2897)).

### Fixes

Expand Down
7 changes: 6 additions & 1 deletion lang/attribute/program/src/declare_program/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use common::gen_docs;
use mods::{
accounts::gen_accounts_mod, client::gen_client_mod, constants::gen_constants_mod,
cpi::gen_cpi_mod, events::gen_events_mod, internal::gen_internal_mod, program::gen_program_mod,
types::gen_types_mod,
types::gen_types_mod, utils::gen_utils_mod,
};

pub struct DeclareProgram {
Expand Down Expand Up @@ -66,6 +66,9 @@ fn gen_program(idl: &Idl, name: &syn::Ident) -> proc_macro2::TokenStream {
let client_mod = gen_client_mod(idl);
let internal_mod = gen_internal_mod(idl);

// Utils
let utils_mod = gen_utils_mod(idl);

quote! {
#docs
pub mod #name {
Expand All @@ -82,6 +85,8 @@ fn gen_program(idl: &Idl, name: &syn::Ident) -> proc_macro2::TokenStream {
#cpi_mod
#client_mod
#internal_mod

#utils_mod
}
}
}
Expand Down
1 change: 1 addition & 0 deletions lang/attribute/program/src/declare_program/mods/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ pub mod events;
pub mod internal;
pub mod program;
pub mod types;
pub mod utils;

use super::common;
69 changes: 69 additions & 0 deletions lang/attribute/program/src/declare_program/mods/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use anchor_idl::types::Idl;
use quote::{format_ident, quote};

use super::common::gen_discriminator;

pub fn gen_utils_mod(idl: &Idl) -> proc_macro2::TokenStream {
let event = gen_event(idl);

quote! {
/// Program utilities.
pub mod utils {
#event
}
}
}

fn gen_event(idl: &Idl) -> proc_macro2::TokenStream {
let variants = idl
.events
.iter()
.map(|ev| format_ident!("{}", ev.name))
.map(|name| quote! { #name(#name) });
let match_arms = idl.events.iter().map(|ev| {
let disc = gen_discriminator(&ev.discriminator);
let name = format_ident!("{}", ev.name);
let event = quote! {
#name::try_from_slice(&value[8..])
.map(Self::#name)
.map_err(Into::into)
};
quote! { #disc => #event }
});

quote! {
use super::{*, events::*};

/// An enum that includes all events of the declared program as a tuple variant.
///
/// See [`Self::try_from_bytes`] to create an instance from bytes.
pub enum Event {
#(#variants,)*
}

impl Event {
/// Try to create an event based on the given bytes.
///
/// This method returns an error if the discriminator of the given bytes don't match
/// with any of the existing events, or if the deserialization fails.
pub fn try_from_bytes(bytes: &[u8]) -> Result<Self> {
Self::try_from(bytes)
}
}

impl TryFrom<&[u8]> for Event {
type Error = anchor_lang::error::Error;

fn try_from(value: &[u8]) -> Result<Self> {
if value.len() < 8 {
return Err(ProgramError::InvalidArgument.into());
}

match &value[..8] {
#(#match_arms,)*
_ => Err(ProgramError::InvalidArgument.into()),
}
}
}
}
}
27 changes: 27 additions & 0 deletions tests/declare-program/idls/external.json
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,21 @@
]
}
],
"events": [
{
"name": "MyEvent",
"discriminator": [
96,
184,
197,
243,
139,
2,
90,
148
]
}
],
"types": [
{
"name": "MyAccount",
Expand All @@ -151,6 +166,18 @@
}
]
}
},
{
"name": "MyEvent",
"type": {
"kind": "struct",
"fields": [
{
"name": "value",
"type": "u32"
}
]
}
}
]
}
30 changes: 30 additions & 0 deletions tests/declare-program/programs/declare-program/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,31 @@ pub mod declare_program {

Ok(())
}

pub fn event_utils(_ctx: Context<Utils>) -> Result<()> {
use external::utils::Event;

// Empty
if Event::try_from_bytes(&[]).is_ok() {
return Err(ProgramError::Custom(0).into());
}

const DISC: &[u8] =
&<external::events::MyEvent as anchor_lang::Discriminator>::DISCRIMINATOR;

// Correct discriminator but invalid data
if Event::try_from_bytes(DISC).is_ok() {
return Err(ProgramError::Custom(1).into());
};

// Correct discriminator and valid data
match Event::try_from_bytes(&[DISC, &[1, 0, 0, 0]].concat()) {
Ok(Event::MyEvent(my_event)) => require_eq!(my_event.value, 1),
Err(e) => return Err(e.into()),
}

Ok(())
}
}

#[derive(Accounts)]
Expand All @@ -57,3 +82,8 @@ pub struct Cpi<'info> {
pub cpi_my_account: Account<'info, external::accounts::MyAccount>,
pub external_program: Program<'info, External>,
}

#[derive(Accounts)]
pub struct Utils<'info> {
pub authority: Signer<'info>,
}
5 changes: 5 additions & 0 deletions tests/declare-program/programs/external/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,8 @@ pub struct UpdateComposite<'info> {
pub struct MyAccount {
pub field: u32,
}

#[event]
pub struct MyEvent {
pub value: u32,
}
4 changes: 4 additions & 0 deletions tests/declare-program/tests/declare-program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,8 @@ describe("declare-program", () => {
);
assert.strictEqual(myAccount.field, value);
});

it("Can use event utils", async () => {
await program.methods.eventUtils().rpc();
});
});
Loading