From a221aa4302b58c3ae7f3bf387c6adf8bc637bf3c Mon Sep 17 00:00:00 2001 From: acheron Date: Tue, 9 Apr 2024 17:07:18 +0200 Subject: [PATCH 1/5] lang: Add `Event` utility type to get events from bytes --- .../program/src/declare_program/mod.rs | 7 ++- .../program/src/declare_program/mods/mod.rs | 1 + .../program/src/declare_program/mods/utils.rs | 61 +++++++++++++++++++ 3 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 lang/attribute/program/src/declare_program/mods/utils.rs diff --git a/lang/attribute/program/src/declare_program/mod.rs b/lang/attribute/program/src/declare_program/mod.rs index ec60ad4468..a8fdf19d5a 100644 --- a/lang/attribute/program/src/declare_program/mod.rs +++ b/lang/attribute/program/src/declare_program/mod.rs @@ -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 { @@ -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 { @@ -82,6 +85,8 @@ fn gen_program(idl: &Idl, name: &syn::Ident) -> proc_macro2::TokenStream { #cpi_mod #client_mod #internal_mod + + #utils_mod } } } diff --git a/lang/attribute/program/src/declare_program/mods/mod.rs b/lang/attribute/program/src/declare_program/mods/mod.rs index 4c0d3bbe48..8c9c58a45c 100644 --- a/lang/attribute/program/src/declare_program/mods/mod.rs +++ b/lang/attribute/program/src/declare_program/mods/mod.rs @@ -6,5 +6,6 @@ pub mod events; pub mod internal; pub mod program; pub mod types; +pub mod utils; use super::common; diff --git a/lang/attribute/program/src/declare_program/mods/utils.rs b/lang/attribute/program/src/declare_program/mods/utils.rs new file mode 100644 index 0000000000..4b1fc648af --- /dev/null +++ b/lang/attribute/program/src/declare_program/mods/utils.rs @@ -0,0 +1,61 @@ +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) }; + 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]) -> std::io::Result { + Self::try_from(bytes) + } + } + + impl TryFrom<&[u8]> for Event { + type Error = std::io::Error; + + fn try_from(value: &[u8]) -> std::io::Result { + match &value[..8] { + #(#match_arms,)* + _ => Err(std::io::ErrorKind::NotFound.into()), + } + } + } + } +} From f5707d2dd0eb5146f9f7bfa099d720090e922c7a Mon Sep 17 00:00:00 2001 From: acheron Date: Tue, 9 Apr 2024 22:43:01 +0200 Subject: [PATCH 2/5] Validate discriminator length --- lang/attribute/program/src/declare_program/mods/utils.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lang/attribute/program/src/declare_program/mods/utils.rs b/lang/attribute/program/src/declare_program/mods/utils.rs index 4b1fc648af..3b07e8a0d2 100644 --- a/lang/attribute/program/src/declare_program/mods/utils.rs +++ b/lang/attribute/program/src/declare_program/mods/utils.rs @@ -51,6 +51,10 @@ fn gen_event(idl: &Idl) -> proc_macro2::TokenStream { type Error = std::io::Error; fn try_from(value: &[u8]) -> std::io::Result { + if value.len() < 8 { + return Err(std::io::ErrorKind::InvalidData.into()); + } + match &value[..8] { #(#match_arms,)* _ => Err(std::io::ErrorKind::NotFound.into()), From 3dd5232abed6e9facdc3bfc44c48ae1b45806f74 Mon Sep 17 00:00:00 2001 From: acheron Date: Tue, 9 Apr 2024 23:16:52 +0200 Subject: [PATCH 3/5] Use Anchor `Error` type --- .../program/src/declare_program/mods/utils.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lang/attribute/program/src/declare_program/mods/utils.rs b/lang/attribute/program/src/declare_program/mods/utils.rs index 3b07e8a0d2..a73502f39e 100644 --- a/lang/attribute/program/src/declare_program/mods/utils.rs +++ b/lang/attribute/program/src/declare_program/mods/utils.rs @@ -23,7 +23,11 @@ fn gen_event(idl: &Idl) -> proc_macro2::TokenStream { 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) }; + let event = quote! { + #name::try_from_slice(&value[8..]) + .map(Self::#name) + .map_err(Into::into) + }; quote! { #disc => #event } }); @@ -42,22 +46,22 @@ fn gen_event(idl: &Idl) -> proc_macro2::TokenStream { /// /// 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]) -> std::io::Result { + pub fn try_from_bytes(bytes: &[u8]) -> Result { Self::try_from(bytes) } } impl TryFrom<&[u8]> for Event { - type Error = std::io::Error; + type Error = anchor_lang::error::Error; - fn try_from(value: &[u8]) -> std::io::Result { + fn try_from(value: &[u8]) -> Result { if value.len() < 8 { - return Err(std::io::ErrorKind::InvalidData.into()); + return Err(ProgramError::InvalidArgument.into()); } match &value[..8] { #(#match_arms,)* - _ => Err(std::io::ErrorKind::NotFound.into()), + _ => Err(ProgramError::InvalidArgument.into()), } } } From a06c4f683f3092a2ce7f1d402681f84daaecef5c Mon Sep 17 00:00:00 2001 From: acheron Date: Tue, 9 Apr 2024 23:20:10 +0200 Subject: [PATCH 4/5] Add a test case --- tests/declare-program/idls/external.json | 27 +++++++++++++++++ .../programs/declare-program/src/lib.rs | 30 +++++++++++++++++++ .../programs/external/src/lib.rs | 5 ++++ .../declare-program/tests/declare-program.ts | 4 +++ 4 files changed, 66 insertions(+) diff --git a/tests/declare-program/idls/external.json b/tests/declare-program/idls/external.json index 60a9553061..7329c7c558 100644 --- a/tests/declare-program/idls/external.json +++ b/tests/declare-program/idls/external.json @@ -139,6 +139,21 @@ ] } ], + "events": [ + { + "name": "MyEvent", + "discriminator": [ + 96, + 184, + 197, + 243, + 139, + 2, + 90, + 148 + ] + } + ], "types": [ { "name": "MyAccount", @@ -151,6 +166,18 @@ } ] } + }, + { + "name": "MyEvent", + "type": { + "kind": "struct", + "fields": [ + { + "name": "value", + "type": "u32" + } + ] + } } ] } \ No newline at end of file diff --git a/tests/declare-program/programs/declare-program/src/lib.rs b/tests/declare-program/programs/declare-program/src/lib.rs index dff1fcee7c..5272dea559 100644 --- a/tests/declare-program/programs/declare-program/src/lib.rs +++ b/tests/declare-program/programs/declare-program/src/lib.rs @@ -48,6 +48,31 @@ pub mod declare_program { Ok(()) } + + pub fn event_utils(_ctx: Context) -> Result<()> { + use external::utils::Event; + + // Empty + if Event::try_from_bytes(&[]).is_ok() { + return Err(ProgramError::Custom(0).into()); + } + + const DISC: &[u8] = + &::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)] @@ -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>, +} diff --git a/tests/declare-program/programs/external/src/lib.rs b/tests/declare-program/programs/external/src/lib.rs index 6768569a32..4897ba8ad2 100644 --- a/tests/declare-program/programs/external/src/lib.rs +++ b/tests/declare-program/programs/external/src/lib.rs @@ -52,3 +52,8 @@ pub struct UpdateComposite<'info> { pub struct MyAccount { pub field: u32, } + +#[event] +pub struct MyEvent { + pub value: u32, +} diff --git a/tests/declare-program/tests/declare-program.ts b/tests/declare-program/tests/declare-program.ts index a2bf63a080..de73ab25ed 100644 --- a/tests/declare-program/tests/declare-program.ts +++ b/tests/declare-program/tests/declare-program.ts @@ -46,4 +46,8 @@ describe("declare-program", () => { ); assert.strictEqual(myAccount.field, value); }); + + it("Can use event utils", async () => { + await program.methods.eventUtils().rpc(); + }); }); From fc8b74c504934b16aa099d1fbbad5d06c0a8c65c Mon Sep 17 00:00:00 2001 From: acheron Date: Tue, 9 Apr 2024 23:21:13 +0200 Subject: [PATCH 5/5] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fce777476..f1b24e5722 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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