Przeglądaj źródła

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

acheron 1 rok temu
rodzic
commit
0be5b00a34

+ 1 - 0
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
 

+ 6 - 1
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
         }
     }
 }

+ 1 - 0
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;

+ 69 - 0
lang/attribute/program/src/declare_program/mods/utils.rs

@@ -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 - 0
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"
+          }
+        ]
+      }
     }
   ]
 }

+ 30 - 0
tests/declare-program/programs/declare-program/src/lib.rs

@@ -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)]
@@ -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 - 0
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,
+}

+ 4 - 0
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();
+  });
 });