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..226a4104df --- /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).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()), + } + } + } + } +}