From 1fef2f6226b8fe54dca16a0ffbbde1c906264a36 Mon Sep 17 00:00:00 2001 From: Cole Roberts Date: Fri, 7 Oct 2022 08:59:21 -0400 Subject: [PATCH 01/11] feat: enforce dev compliance of logging standard by abstraction --- near-sdk/src/environment/env.rs | 80 +++++++++++++++++++++++++++++---- 1 file changed, 71 insertions(+), 9 deletions(-) diff --git a/near-sdk/src/environment/env.rs b/near-sdk/src/environment/env.rs index d2751bf5e..885912326 100644 --- a/near-sdk/src/environment/env.rs +++ b/near-sdk/src/environment/env.rs @@ -689,6 +689,40 @@ pub fn abort() -> ! { } } +#[derive(serde::Serialize, serde::Deserialize)] +struct Event<'a> { + standard: &'a str, + version: &'a str, + event: &'a str, + #[serde(skip_serializing_if = "Option::is_none")] + data: Option<&'a str>, +} + +pub enum EventSerializationFormat { + JSON, +} + +/// Logs an event according to NEP-297 (see https://github.com/near/NEPs/blob/master/neps/nep-0297.md). This message is stored on chain +pub fn event( + standard: &str, + version: &str, + event: &str, + data: Option<&str>, + serialization_fmt: EventSerializationFormat, +) { + let message = match serialization_fmt { + EventSerializationFormat::JSON => { + format!( + "EVENT_JSON:{}", + serde_json::to_string(&Event { standard, version, event, data }) + .unwrap_or_else(|_| String::from("Error deserializing event")) + ) + } + }; + + log_str(&message); +} + /// Logs the string message message. This message is stored on chain. pub fn log_str(message: &str) { #[cfg(all(debug_assertions, not(target_arch = "wasm32")))] @@ -920,15 +954,6 @@ mod tests { } } - #[test] - fn test_is_valid_account_id_binary() { - assert!(!is_valid_account_id(&[])); - assert!(!is_valid_account_id(&[0])); - assert!(!is_valid_account_id(&[0, 1])); - assert!(!is_valid_account_id(&[0, 1, 2])); - assert!(is_valid_account_id(b"near")); - } - #[cfg(not(target_arch = "wasm32"))] #[test] fn hash_smoke_tests() { @@ -1021,4 +1046,41 @@ mod tests { .build()); assert_eq!(super::signer_account_pk(), key); } + + #[test] + fn event_tests() { + use crate::test_utils::get_logs; + + // Event without data + event("nepXXX", "1.0.0", "event_name", None, EventSerializationFormat::JSON); + + // Event with string data + let data = "Swapped 1 token_in.near for 2 token_out.near"; + event("nepXXX", "1.0.0", "swap", Some(data), EventSerializationFormat::JSON); + + // Event with json data + let mut data = std::collections::HashMap::new(); + data.insert("vector", vec![1, 2, 3, 4]); + event( + "nepXXX", + "1.0.0", + "json_data_event", + Some(&(serde_json::to_string(&data).unwrap())), + EventSerializationFormat::JSON, + ); + + let logs = get_logs(); + + assert!( + logs[0] == r#"EVENT_JSON:{"standard":"nepXXX","version":"1.0.0","event":"event_name"}"# + ); + assert!( + logs[1] + == r#"EVENT_JSON:{"standard":"nepXXX","version":"1.0.0","event":"swap","data":"Swapped 1 token_in.near for 2 token_out.near"}"# + ); + assert!( + logs[2] + == r#"EVENT_JSON:{"standard":"nepXXX","version":"1.0.0","event":"json_data_event","data":"{\"vector\":[1,2,3,4]}"}"# + ); + } } From 3d2dc4196c082d9b410e0396b9408aae3c385c57 Mon Sep 17 00:00:00 2001 From: Cole Roberts Date: Mon, 10 Oct 2022 09:36:12 -0400 Subject: [PATCH 02/11] feat: Changed abstraction api --- near-sdk-macros/src/core_impl/event/mod.rs | 75 ++++++++++++ near-sdk-macros/src/core_impl/mod.rs | 3 + near-sdk-macros/src/core_impl/utils/mod.rs | 23 +++- near-sdk-macros/src/lib.rs | 133 +++++++++++++++++++++ near-sdk/src/environment/env.rs | 80 ++----------- near-sdk/src/lib.rs | 2 +- near-sdk/src/types/event.rs | 95 +++++++++++++++ near-sdk/src/types/mod.rs | 3 + 8 files changed, 341 insertions(+), 73 deletions(-) create mode 100644 near-sdk-macros/src/core_impl/event/mod.rs create mode 100644 near-sdk/src/types/event.rs diff --git a/near-sdk-macros/src/core_impl/event/mod.rs b/near-sdk-macros/src/core_impl/event/mod.rs new file mode 100644 index 000000000..3551f9c36 --- /dev/null +++ b/near-sdk-macros/src/core_impl/event/mod.rs @@ -0,0 +1,75 @@ +use proc_macro::TokenStream; + +use proc_macro2::Span; +use quote::quote; +use syn::{parse_quote, ItemEnum}; + +/// This attribute macro is used on a enum and its implementations +/// to generate the necessary code to format standard event logs. +/// +/// This macro will generate code to load and deserialize state if the `self` parameter is included +/// as well as saving it back to state if `&mut self` is used. +/// +/// For parameter serialization, this macro will generate a struct with all of the parameters as +/// fields and derive deserialization for it. By default this will be JSON deserialized with `serde` +/// but can be overwritten by using `#[serializer(borsh)]`. +/// +/// `#[near_bindgen]` will also handle serializing and setting the return value of the +/// function execution based on what type is returned by the function. By default, this will be +/// done through `serde` serialized as JSON, but this can be overwritten using +/// `#[result_serializer(borsh)]`. +/// +/// # Examples +/// +/// ```ignore +/// use near_sdk::{near_bindgen, Event}; +/// +/// #[near_bindgen(events)] +/// pub enum MyEvents { +/// #[event_standard("swap_standard")] +/// #[event_version("1.0.0")] +/// Swap { token_in: AccountId, token_out: AccountId, amount_in: u128, amount_out: u128 }, +/// +/// #[event_standard("string_standard")] +/// #[event_version("2.0.0")] +/// StringEvent(String), +/// +/// #[event_standard("empty_standard")] +/// #[event_version("3.0.0")] +/// EmptyEvent +/// } +/// +/// #[near_bindgen] +/// impl Contract { +/// pub fn some_function(&self) { +/// Event::emit ( +/// MyEvents::StringEvent(String::from("some_string")) +/// ) +/// } +/// +/// pub fn another_function(&self) { +/// MyEvents::StringEvent(String::from("another_string")).emit() +/// } +/// } +/// ``` +pub(crate) fn near_events(item: TokenStream) -> TokenStream { + if let Ok(mut input) = syn::parse::(item) { + // NearEvent Macro handles implementation + input.attrs.push(parse_quote! (#[derive(near_sdk::serde::Serialize, std::clone::Clone, near_sdk::EventMetadata)])); + input.attrs.push(parse_quote! (#[serde(crate="near_sdk::serde")])); + input.attrs.push(parse_quote! (#[serde(tag = "event", content = "data")])); + input.attrs.push(parse_quote! (#[serde(rename_all = "snake_case")])); + + TokenStream::from(quote! { + #input + }) + } else { + TokenStream::from( + syn::Error::new( + Span::call_site(), + "`near_events` can only be used as an attribute on enums.", + ) + .to_compile_error(), + ) + } +} diff --git a/near-sdk-macros/src/core_impl/mod.rs b/near-sdk-macros/src/core_impl/mod.rs index 47a0b33a9..bb5be7612 100644 --- a/near-sdk-macros/src/core_impl/mod.rs +++ b/near-sdk-macros/src/core_impl/mod.rs @@ -1,9 +1,12 @@ #[cfg(any(feature = "__abi-embed", feature = "__abi-generate"))] pub(crate) mod abi; mod code_generator; +mod event; mod info_extractor; mod metadata; mod utils; pub(crate) use code_generator::*; +pub(crate) use event::near_events; pub(crate) use info_extractor::*; pub(crate) use metadata::metadata_visitor::MetadataVisitor; +pub(crate) use utils::get_event_args; diff --git a/near-sdk-macros/src/core_impl/utils/mod.rs b/near-sdk-macros/src/core_impl/utils/mod.rs index b8d865f43..a273a7910 100644 --- a/near-sdk-macros/src/core_impl/utils/mod.rs +++ b/near-sdk-macros/src/core_impl/utils/mod.rs @@ -1,4 +1,4 @@ -use syn::{GenericArgument, Path, PathArguments, Type}; +use syn::{GenericArgument, LitStr, Path, PathArguments, Type}; /// Checks whether the given path is literally "Result". /// Note that it won't match a fully qualified name `core::result::Result` or a type alias like @@ -73,3 +73,24 @@ pub(crate) fn extract_vec_type(ty: &Type) -> Option<&Type> { _ => None, } } + +pub(crate) fn get_event_args(var: &syn::Variant) -> (Option, Option) { + let mut v: (Option, Option) = (None, None); + var.attrs.iter().for_each(|attr| { + let test = attr.parse_args_with( + syn::punctuated::Punctuated::::parse_terminated, + ); + if let Ok(item) = test { + item.iter().for_each(|kwargs| { + if let syn::MetaNameValue { path, lit: syn::Lit::Str(value), .. } = kwargs { + if path.is_ident("standard") { + v.0 = Some(value.clone()); + } else if path.is_ident("version") { + v.1 = Some(value.clone()); + } + }; + }) + } + }); + v +} diff --git a/near-sdk-macros/src/lib.rs b/near-sdk-macros/src/lib.rs index 420cf6b2d..461dc7f2a 100644 --- a/near-sdk-macros/src/lib.rs +++ b/near-sdk-macros/src/lib.rs @@ -43,8 +43,63 @@ use syn::{parse_quote, File, ItemEnum, ItemImpl, ItemStruct, ItemTrait, WhereCla /// pub fn some_function(&self) {} /// } /// ``` +/// +/// Events Standard: +/// +/// By passing `events` as an argument `near_bindgen` will generate the relevant code to format events +/// according to NEP-297 +/// +/// This macro will generate code to load and deserialize state if the `self` parameter is included +/// as well as saving it back to state if `&mut self` is used. +/// +/// For parameter serialization, this macro will generate a struct with all of the parameters as +/// fields and derive deserialization for it. By default this will be JSON deserialized with `serde` +/// but can be overwritten by using `#[serializer(borsh)]`. +/// +/// `#[near_bindgen]` will also handle serializing and setting the return value of the +/// function execution based on what type is returned by the function. By default, this will be +/// done through `serde` serialized as JSON, but this can be overwritten using +/// `#[result_serializer(borsh)]`. +/// +/// # Examples +/// +/// ```ignore +/// use near_sdk::{near_bindgen, Event}; +/// +/// #[near_bindgen(events)] +/// pub enum MyEvents { +/// #[event_standard("swap_standard")] +/// #[event_version("1.0.0")] +/// Swap { token_in: AccountId, token_out: AccountId, amount_in: u128, amount_out: u128 }, +/// +/// #[event_standard("string_standard")] +/// #[event_version("2.0.0")] +/// StringEvent(String), +/// +/// #[event_standard("empty_standard")] +/// #[event_version("3.0.0")] +/// EmptyEvent +/// } +/// +/// #[near_bindgen] +/// impl Contract { +/// pub fn some_function(&self) { +/// Event::emit ( +/// MyEvents::StringEvent(String::from("some_string")); +/// ) +/// } +/// +/// pub fn another_function(&self) { +/// MyEvents::StringEvent(String::from("another_string")).emit(); +/// } +/// } +/// ``` #[proc_macro_attribute] pub fn near_bindgen(_attr: TokenStream, item: TokenStream) -> TokenStream { + if _attr.to_string() == "events" { + return core_impl::near_events(item); + } + if let Ok(input) = syn::parse::(item.clone()) { let ext_gen = generate_ext_structs(&input.ident, Some(&input.generics)); #[cfg(feature = "__abi-embed")] @@ -316,3 +371,81 @@ pub fn function_error(item: TokenStream) -> TokenStream { } }) } + +/// The attribute macro `near_events` injects this macro and should be the used over this +/// +/// This derive macro is used to inject the necessary wrapper and logic to auto format +/// standard event logs. The other appropriate attribute macros are not injected with this macro. +/// Required attributes below: +/// ```ignore +/// #[derive(near_sdk::serde::Serialize, std::clone::Clone)] +/// #[serde(crate="near_sdk::serde")] +/// #[serde(tag = "event", content = "data")] +/// #[serde(rename_all="snake_case")] +/// pub enum MyEvent { +/// Event +/// } +/// ``` +#[proc_macro_derive(EventMetadata, attributes(event_meta))] +pub fn derive_event_attributes(item: TokenStream) -> TokenStream { + if let Ok(input) = syn::parse::(item) { + let name = &input.ident; + // get standard and version from each attribute macro + let mut attr_error: u8 = 0; + let mut event_meta: Vec = vec![]; + let _ = &input.variants.iter().for_each(|var| { + if let (Some(standard), Some(event)) = core_impl::get_event_args(var) { + let var_ident = &var.ident; + event_meta.push(quote! { + #name::#var_ident { .. } => {(#standard.to_string(), #event.to_string())} + }) + } else { + attr_error += 1; + } + }); + + // handle lifetimes, generics, and where clauses + let (impl_generics, type_generics, where_clause) = &input.generics.split_for_impl(); + + if attr_error > 0 { + return TokenStream::from( + syn::Error::new( + Span::call_site(), + "Near events must have `event_meta` attribute with `standard` and `version` fields for each event variant. Field values must be string literals", + ) + .to_compile_error(), + ); + } + + TokenStream::from(quote! { + + #[derive(near_sdk::serde::Serialize)] + #[serde(crate="near_sdk::serde")] + #[serde(rename_all="snake_case")] + struct EventBuilder #impl_generics #where_clause { + standard: String, + version: String, + #[serde(flatten)] + event_data: #name #type_generics + } + impl #impl_generics near_sdk::StandardEvent for #name #type_generics #where_clause { + fn format(&self) -> String { + let (standard, version): (String, String) = match self { + #(#event_meta),* + }; + let event = EventBuilder {standard, version, event_data: self.clone() }; + near_sdk::serde_json::to_string(&event).unwrap_or_else(|_| near_sdk::env::abort()) + } + + fn emit(&self) { + near_sdk::Event::emit(self.clone()); + } + } + }) + } else { + TokenStream::from( + syn::Error::new(Span::call_site(), "NearEvent can only be used as a derive on enums.") + .to_compile_error(), + ) + } +} diff --git a/near-sdk/src/environment/env.rs b/near-sdk/src/environment/env.rs index 885912326..d2751bf5e 100644 --- a/near-sdk/src/environment/env.rs +++ b/near-sdk/src/environment/env.rs @@ -689,40 +689,6 @@ pub fn abort() -> ! { } } -#[derive(serde::Serialize, serde::Deserialize)] -struct Event<'a> { - standard: &'a str, - version: &'a str, - event: &'a str, - #[serde(skip_serializing_if = "Option::is_none")] - data: Option<&'a str>, -} - -pub enum EventSerializationFormat { - JSON, -} - -/// Logs an event according to NEP-297 (see https://github.com/near/NEPs/blob/master/neps/nep-0297.md). This message is stored on chain -pub fn event( - standard: &str, - version: &str, - event: &str, - data: Option<&str>, - serialization_fmt: EventSerializationFormat, -) { - let message = match serialization_fmt { - EventSerializationFormat::JSON => { - format!( - "EVENT_JSON:{}", - serde_json::to_string(&Event { standard, version, event, data }) - .unwrap_or_else(|_| String::from("Error deserializing event")) - ) - } - }; - - log_str(&message); -} - /// Logs the string message message. This message is stored on chain. pub fn log_str(message: &str) { #[cfg(all(debug_assertions, not(target_arch = "wasm32")))] @@ -954,6 +920,15 @@ mod tests { } } + #[test] + fn test_is_valid_account_id_binary() { + assert!(!is_valid_account_id(&[])); + assert!(!is_valid_account_id(&[0])); + assert!(!is_valid_account_id(&[0, 1])); + assert!(!is_valid_account_id(&[0, 1, 2])); + assert!(is_valid_account_id(b"near")); + } + #[cfg(not(target_arch = "wasm32"))] #[test] fn hash_smoke_tests() { @@ -1046,41 +1021,4 @@ mod tests { .build()); assert_eq!(super::signer_account_pk(), key); } - - #[test] - fn event_tests() { - use crate::test_utils::get_logs; - - // Event without data - event("nepXXX", "1.0.0", "event_name", None, EventSerializationFormat::JSON); - - // Event with string data - let data = "Swapped 1 token_in.near for 2 token_out.near"; - event("nepXXX", "1.0.0", "swap", Some(data), EventSerializationFormat::JSON); - - // Event with json data - let mut data = std::collections::HashMap::new(); - data.insert("vector", vec![1, 2, 3, 4]); - event( - "nepXXX", - "1.0.0", - "json_data_event", - Some(&(serde_json::to_string(&data).unwrap())), - EventSerializationFormat::JSON, - ); - - let logs = get_logs(); - - assert!( - logs[0] == r#"EVENT_JSON:{"standard":"nepXXX","version":"1.0.0","event":"event_name"}"# - ); - assert!( - logs[1] - == r#"EVENT_JSON:{"standard":"nepXXX","version":"1.0.0","event":"swap","data":"Swapped 1 token_in.near for 2 token_out.near"}"# - ); - assert!( - logs[2] - == r#"EVENT_JSON:{"standard":"nepXXX","version":"1.0.0","event":"json_data_event","data":"{\"vector\":[1,2,3,4]}"}"# - ); - } } diff --git a/near-sdk/src/lib.rs b/near-sdk/src/lib.rs index 185f6700a..b305ec016 100644 --- a/near-sdk/src/lib.rs +++ b/near-sdk/src/lib.rs @@ -6,7 +6,7 @@ extern crate quickcheck; pub use near_sdk_macros::{ - ext_contract, near_bindgen, BorshStorageKey, FunctionError, PanicOnDefault, + ext_contract, near_bindgen, BorshStorageKey, EventMetadata, FunctionError, PanicOnDefault, }; pub mod store; diff --git a/near-sdk/src/types/event.rs b/near-sdk/src/types/event.rs new file mode 100644 index 000000000..47b82fa88 --- /dev/null +++ b/near-sdk/src/types/event.rs @@ -0,0 +1,95 @@ +pub trait StandardEvent { + fn format(&self) -> String; + fn emit(&self); +} + +pub struct Event {} +impl Event { + pub fn emit(standard_event: impl StandardEvent) { + crate::env::log_str(&format!("EVENT_JSON:{}", &standard_event.format())); + } +} + +#[cfg(test)] +pub mod tests { + + use crate::test_utils::get_logs; + use crate::{near_bindgen, AccountId}; + + use super::Event; + use super::StandardEvent; + use crate as near_sdk; + + #[near_bindgen(events)] + pub enum TestEvents<'a, 'b, T> + where + T: crate::serde::Serialize + Clone, + { + #[event_meta(standard = "swap_standard", version = "1.0.0")] + Swap { + token_in: AccountId, + token_out: AccountId, + amount_in: u128, + amount_out: u128, + test: T, + }, + + #[event_meta(standard = "string_standard", version = "2.0.0")] + StringEvent(String), + + #[event_meta(standard = "empty_standard", version = "3.0.0")] + EmptyEvent, + + #[event_meta(standard = "lifetime_std", version = "4.0.0")] + LifetimeTestA(&'a str), + + #[event_meta(standard = "lifetime_std", version = "4.0.0")] + LifetimeTestB(&'b str), + } + + #[test] + fn test_json_emit() { + let token_in: AccountId = "wrap.near".parse().unwrap(); + let token_out: AccountId = "test.near".parse().unwrap(); + let amount_in: u128 = 100; + let amount_out: u128 = 200; + Event::emit(TestEvents::Swap { + token_in, + token_out, + amount_in, + amount_out, + test: String::from("tst"), + }); + + Event::emit(TestEvents::StringEvent::(String::from("string"))); + + Event::emit(TestEvents::EmptyEvent::); + + Event::emit(TestEvents::LifetimeTestA::("lifetime")); + + TestEvents::LifetimeTestB::("lifetime_b").emit(); + + let logs = get_logs(); + + assert!( + logs[0] + == r#"EVENT_JSON:{"standard":"swap_standard","version":"1.0.0","event":"swap","data":{"token_in":"wrap.near","token_out":"test.near","amount_in":100,"amount_out":200,"test":"tst"}}"# + ); + assert!( + logs[1] + == r#"EVENT_JSON:{"standard":"string_standard","version":"2.0.0","event":"string_event","data":"string"}"# + ); + assert!( + logs[2] + == r#"EVENT_JSON:{"standard":"empty_standard","version":"3.0.0","event":"empty_event"}"# + ); + assert!( + logs[3] + == r#"EVENT_JSON:{"standard":"lifetime_std","version":"4.0.0","event":"lifetime_test_a","data":"lifetime"}"# + ); + assert!( + logs[4] + == r#"EVENT_JSON:{"standard":"lifetime_std","version":"4.0.0","event":"lifetime_test_b","data":"lifetime_b"}"# + ); + } +} diff --git a/near-sdk/src/types/mod.rs b/near-sdk/src/types/mod.rs index 6e1de22cf..774196f23 100644 --- a/near-sdk/src/types/mod.rs +++ b/near-sdk/src/types/mod.rs @@ -13,6 +13,9 @@ pub use self::account_id::{AccountId, ParseAccountIdError}; mod gas; pub use self::gas::Gas; +mod event; +pub use event::{Event, StandardEvent}; + mod error; pub use self::error::Abort; pub use self::error::FunctionError; From df36b38aca66858aed63c0633de464ad31c6d717 Mon Sep 17 00:00:00 2001 From: Cole Roberts Date: Fri, 21 Oct 2022 10:54:40 -0400 Subject: [PATCH 03/11] feat: 3rd iteration. Moved `standard` to enum declaration and minor fixes --- near-sdk-macros/src/core_impl/event/mod.rs | 114 +++++++++++---------- near-sdk-macros/src/core_impl/mod.rs | 3 +- near-sdk-macros/src/core_impl/utils/mod.rs | 23 +---- near-sdk-macros/src/lib.rs | 80 +++++++-------- near-sdk/src/types/event.rs | 53 ++++------ near-sdk/src/types/mod.rs | 2 +- 6 files changed, 125 insertions(+), 150 deletions(-) diff --git a/near-sdk-macros/src/core_impl/event/mod.rs b/near-sdk-macros/src/core_impl/event/mod.rs index 3551f9c36..244c8a3dc 100644 --- a/near-sdk-macros/src/core_impl/event/mod.rs +++ b/near-sdk-macros/src/core_impl/event/mod.rs @@ -1,75 +1,83 @@ use proc_macro::TokenStream; - use proc_macro2::Span; use quote::quote; -use syn::{parse_quote, ItemEnum}; +use syn::{parse_quote, ItemEnum, LitStr}; -/// This attribute macro is used on a enum and its implementations -/// to generate the necessary code to format standard event logs. -/// -/// This macro will generate code to load and deserialize state if the `self` parameter is included -/// as well as saving it back to state if `&mut self` is used. -/// -/// For parameter serialization, this macro will generate a struct with all of the parameters as -/// fields and derive deserialization for it. By default this will be JSON deserialized with `serde` -/// but can be overwritten by using `#[serializer(borsh)]`. -/// -/// `#[near_bindgen]` will also handle serializing and setting the return value of the -/// function execution based on what type is returned by the function. By default, this will be -/// done through `serde` serialized as JSON, but this can be overwritten using -/// `#[result_serializer(borsh)]`. -/// -/// # Examples -/// -/// ```ignore -/// use near_sdk::{near_bindgen, Event}; -/// -/// #[near_bindgen(events)] -/// pub enum MyEvents { -/// #[event_standard("swap_standard")] -/// #[event_version("1.0.0")] -/// Swap { token_in: AccountId, token_out: AccountId, amount_in: u128, amount_out: u128 }, -/// -/// #[event_standard("string_standard")] -/// #[event_version("2.0.0")] -/// StringEvent(String), -/// -/// #[event_standard("empty_standard")] -/// #[event_version("3.0.0")] -/// EmptyEvent -/// } -/// -/// #[near_bindgen] -/// impl Contract { -/// pub fn some_function(&self) { -/// Event::emit ( -/// MyEvents::StringEvent(String::from("some_string")) -/// ) -/// } -/// -/// pub fn another_function(&self) { -/// MyEvents::StringEvent(String::from("another_string")).emit() -/// } -/// } -/// ``` -pub(crate) fn near_events(item: TokenStream) -> TokenStream { +/// this function is used to inject Serialization Macros and the `near_sdk::EventMetadata` macro. +/// In addition, this function extracts the event's `standard` value and injects it as a constant to be used by +/// the `near_sdk::EventMetadata` derive macro +pub(crate) fn near_events(attr: TokenStream, item: TokenStream) -> TokenStream { + // get standard from attr args + let standard = get_standard_arg(&syn::parse_macro_input!(attr as syn::AttributeArgs)); + if standard.is_none() { + return TokenStream::from( + syn::Error::new( + Span::call_site(), + "Near events must have a `standard` value as an argument for `events` in the `near_bindgen` arguments. The value must be a string literal.", + ) + .to_compile_error(), + ); + } + if let Ok(mut input) = syn::parse::(item) { + let name = &input.ident; + let standard_name = format!("{}_event_standard", name); + let standard_ident = syn::Ident::new(&standard_name, Span::call_site()); // NearEvent Macro handles implementation - input.attrs.push(parse_quote! (#[derive(near_sdk::serde::Serialize, std::clone::Clone, near_sdk::EventMetadata)])); + input + .attrs + .push(parse_quote! (#[derive(near_sdk::serde::Serialize, near_sdk::EventMetadata)])); input.attrs.push(parse_quote! (#[serde(crate="near_sdk::serde")])); input.attrs.push(parse_quote! (#[serde(tag = "event", content = "data")])); input.attrs.push(parse_quote! (#[serde(rename_all = "snake_case")])); TokenStream::from(quote! { + const #standard_ident: &'static str = #standard; #input }) } else { TokenStream::from( syn::Error::new( Span::call_site(), - "`near_events` can only be used as an attribute on enums.", + "`#[near_bindgen(events(standard = \"nepXXX\"))]` can only be used as an attribute on enums.", ) .to_compile_error(), ) } } + +/// This function returns the `version` value from `#[event_version("x.x.x")]`. +/// used by `near_sdk::EventMetadata` +pub(crate) fn get_event_version(var: &syn::Variant) -> Option { + for attr in var.attrs.iter() { + if attr.path.is_ident("event_version") { + return attr.parse_args::().ok(); + } + } + None +} + +/// this function returns the `standard` value from `#[near_bindgen(events(standard = "nepXXX"))]` +fn get_standard_arg(args: &[syn::NestedMeta]) -> Option { + let mut standard: Option = None; + for arg in args.iter() { + if let syn::NestedMeta::Meta(syn::Meta::List(syn::MetaList { path, nested, .. })) = arg { + if path.is_ident("events") { + for event_arg in nested.iter() { + if let syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue { + path, + lit: syn::Lit::Str(value), + .. + })) = event_arg + { + if path.is_ident("standard") { + standard = Some(value.to_owned()); + break; + } + } + } + } + } + } + standard +} diff --git a/near-sdk-macros/src/core_impl/mod.rs b/near-sdk-macros/src/core_impl/mod.rs index bb5be7612..abe7439ac 100644 --- a/near-sdk-macros/src/core_impl/mod.rs +++ b/near-sdk-macros/src/core_impl/mod.rs @@ -6,7 +6,6 @@ mod info_extractor; mod metadata; mod utils; pub(crate) use code_generator::*; -pub(crate) use event::near_events; +pub(crate) use event::{get_event_version, near_events}; pub(crate) use info_extractor::*; pub(crate) use metadata::metadata_visitor::MetadataVisitor; -pub(crate) use utils::get_event_args; diff --git a/near-sdk-macros/src/core_impl/utils/mod.rs b/near-sdk-macros/src/core_impl/utils/mod.rs index a273a7910..b8d865f43 100644 --- a/near-sdk-macros/src/core_impl/utils/mod.rs +++ b/near-sdk-macros/src/core_impl/utils/mod.rs @@ -1,4 +1,4 @@ -use syn::{GenericArgument, LitStr, Path, PathArguments, Type}; +use syn::{GenericArgument, Path, PathArguments, Type}; /// Checks whether the given path is literally "Result". /// Note that it won't match a fully qualified name `core::result::Result` or a type alias like @@ -73,24 +73,3 @@ pub(crate) fn extract_vec_type(ty: &Type) -> Option<&Type> { _ => None, } } - -pub(crate) fn get_event_args(var: &syn::Variant) -> (Option, Option) { - let mut v: (Option, Option) = (None, None); - var.attrs.iter().for_each(|attr| { - let test = attr.parse_args_with( - syn::punctuated::Punctuated::::parse_terminated, - ); - if let Ok(item) = test { - item.iter().for_each(|kwargs| { - if let syn::MetaNameValue { path, lit: syn::Lit::Str(value), .. } = kwargs { - if path.is_ident("standard") { - v.0 = Some(value.clone()); - } else if path.is_ident("version") { - v.1 = Some(value.clone()); - } - }; - }) - } - }); - v -} diff --git a/near-sdk-macros/src/lib.rs b/near-sdk-macros/src/lib.rs index 461dc7f2a..2457f231e 100644 --- a/near-sdk-macros/src/lib.rs +++ b/near-sdk-macros/src/lib.rs @@ -49,34 +49,25 @@ use syn::{parse_quote, File, ItemEnum, ItemImpl, ItemStruct, ItemTrait, WhereCla /// By passing `events` as an argument `near_bindgen` will generate the relevant code to format events /// according to NEP-297 /// -/// This macro will generate code to load and deserialize state if the `self` parameter is included -/// as well as saving it back to state if `&mut self` is used. +/// For parameter serialization, this macro will generate a wrapper to include the NEP-297 standard fields `standard` and `version +/// as well as include serialization reformatting to include the `event` and `data` fields. +/// The `standard` and `version` values must be included in the enum and variant declaration (see example below). +/// By default this will be JSON deserialized with `serde` /// -/// For parameter serialization, this macro will generate a struct with all of the parameters as -/// fields and derive deserialization for it. By default this will be JSON deserialized with `serde` -/// but can be overwritten by using `#[serializer(borsh)]`. -/// -/// `#[near_bindgen]` will also handle serializing and setting the return value of the -/// function execution based on what type is returned by the function. By default, this will be -/// done through `serde` serialized as JSON, but this can be overwritten using -/// `#[result_serializer(borsh)]`. /// /// # Examples /// /// ```ignore -/// use near_sdk::{near_bindgen, Event}; +/// use near_sdk::near_bindgen; /// -/// #[near_bindgen(events)] +/// #[near_bindgen(events(standard = "nepXXX"))] /// pub enum MyEvents { -/// #[event_standard("swap_standard")] /// #[event_version("1.0.0")] /// Swap { token_in: AccountId, token_out: AccountId, amount_in: u128, amount_out: u128 }, /// -/// #[event_standard("string_standard")] /// #[event_version("2.0.0")] /// StringEvent(String), /// -/// #[event_standard("empty_standard")] /// #[event_version("3.0.0")] /// EmptyEvent /// } @@ -84,20 +75,17 @@ use syn::{parse_quote, File, ItemEnum, ItemImpl, ItemStruct, ItemTrait, WhereCla /// #[near_bindgen] /// impl Contract { /// pub fn some_function(&self) { -/// Event::emit ( -/// MyEvents::StringEvent(String::from("some_string")); -/// ) +/// MyEvents::StringEvent( +/// String::from("some_string") +/// ).emit(); /// } /// -/// pub fn another_function(&self) { -/// MyEvents::StringEvent(String::from("another_string")).emit(); -/// } /// } /// ``` #[proc_macro_attribute] pub fn near_bindgen(_attr: TokenStream, item: TokenStream) -> TokenStream { - if _attr.to_string() == "events" { - return core_impl::near_events(item); + if _attr.to_string().contains("events") { + return core_impl::near_events(_attr, item); } if let Ok(input) = syn::parse::(item.clone()) { @@ -372,7 +360,7 @@ pub fn function_error(item: TokenStream) -> TokenStream { }) } -/// The attribute macro `near_events` injects this macro and should be the used over this +/// Adding the `events` argument for the `#[near_bindgen]` macro injects this derive macro and should be the used instead of this /// /// This derive macro is used to inject the necessary wrapper and logic to auto format /// standard event logs. The other appropriate attribute macros are not injected with this macro. @@ -386,18 +374,20 @@ pub fn function_error(item: TokenStream) -> TokenStream { /// Event /// } /// ``` -#[proc_macro_derive(EventMetadata, attributes(event_meta))] +#[proc_macro_derive(EventMetadata, attributes(event_version))] pub fn derive_event_attributes(item: TokenStream) -> TokenStream { if let Ok(input) = syn::parse::(item) { let name = &input.ident; - // get standard and version from each attribute macro + let standard_name = format!("{}_event_standard", name); + let standard_ident = syn::Ident::new(&standard_name, Span::call_site()); + // version from each attribute macro let mut attr_error: u8 = 0; let mut event_meta: Vec = vec![]; let _ = &input.variants.iter().for_each(|var| { - if let (Some(standard), Some(event)) = core_impl::get_event_args(var) { + if let Some(version) = core_impl::get_event_version(var) { let var_ident = &var.ident; event_meta.push(quote! { - #name::#var_ident { .. } => {(#standard.to_string(), #event.to_string())} + #name::#var_ident { .. } => {(#standard_ident.to_string(), #version.to_string())} }) } else { attr_error += 1; @@ -406,12 +396,20 @@ pub fn derive_event_attributes(item: TokenStream) -> TokenStream { // handle lifetimes, generics, and where clauses let (impl_generics, type_generics, where_clause) = &input.generics.split_for_impl(); + // add 'near_event lifetime for user defined events + let mut generics = input.generics.clone(); + let event_lifetime = syn::Lifetime::new("'near_event", Span::call_site()); + generics.params.insert( + generics.params.len(), + syn::GenericParam::Lifetime(syn::LifetimeDef::new(event_lifetime.clone())), + ); + let (custom_impl_generics, ..) = generics.split_for_impl(); if attr_error > 0 { return TokenStream::from( syn::Error::new( Span::call_site(), - "Near events must have `event_meta` attribute with `standard` and `version` fields for each event variant. Field values must be string literals", + "Near events must have `event_version`. Must have a single string literal value.", ) .to_compile_error(), ); @@ -422,30 +420,32 @@ pub fn derive_event_attributes(item: TokenStream) -> TokenStream { #[derive(near_sdk::serde::Serialize)] #[serde(crate="near_sdk::serde")] #[serde(rename_all="snake_case")] - struct EventBuilder #impl_generics #where_clause { + struct EventBuilder #custom_impl_generics #where_clause { standard: String, version: String, #[serde(flatten)] - event_data: #name #type_generics + event_data: &#event_lifetime #name #type_generics } + impl #impl_generics near_sdk::StandardEvent for #name #type_generics #where_clause { - fn format(&self) -> String { + type EventString = String; + fn format(&self) -> Self::EventString { let (standard, version): (String, String) = match self { #(#event_meta),* }; - let event = EventBuilder {standard, version, event_data: self.clone() }; - near_sdk::serde_json::to_string(&event).unwrap_or_else(|_| near_sdk::env::abort()) - } - - fn emit(&self) { - near_sdk::Event::emit(self.clone()); + let event = EventBuilder {standard, version, event_data: self }; + near_sdk::serde_json::to_string(&event) + .unwrap_or_else(|_| near_sdk::env::abort()) } } }) } else { TokenStream::from( - syn::Error::new(Span::call_site(), "NearEvent can only be used as a derive on enums.") - .to_compile_error(), + syn::Error::new( + Span::call_site(), + "EventMetadata can only be used as a derive on enums.", + ) + .to_compile_error(), ) } } diff --git a/near-sdk/src/types/event.rs b/near-sdk/src/types/event.rs index 47b82fa88..9ab6874f1 100644 --- a/near-sdk/src/types/event.rs +++ b/near-sdk/src/types/event.rs @@ -1,31 +1,25 @@ pub trait StandardEvent { - fn format(&self) -> String; - fn emit(&self); -} - -pub struct Event {} -impl Event { - pub fn emit(standard_event: impl StandardEvent) { - crate::env::log_str(&format!("EVENT_JSON:{}", &standard_event.format())); + type EventString: std::fmt::Display; + fn format(&self) -> Self::EventString; + fn emit(&self) { + crate::env::log_str(&format!("EVENT_JSON:{}", self.format())); } } #[cfg(test)] pub mod tests { - use crate::test_utils::get_logs; use crate::{near_bindgen, AccountId}; - use super::Event; use super::StandardEvent; use crate as near_sdk; - #[near_bindgen(events)] + #[near_bindgen(events(standard = "test_standard", random = "random"), other_random)] pub enum TestEvents<'a, 'b, T> where - T: crate::serde::Serialize + Clone, + T: crate::serde::Serialize, { - #[event_meta(standard = "swap_standard", version = "1.0.0")] + #[event_version("1.0.0")] Swap { token_in: AccountId, token_out: AccountId, @@ -34,16 +28,16 @@ pub mod tests { test: T, }, - #[event_meta(standard = "string_standard", version = "2.0.0")] + #[event_version("2.0.0")] StringEvent(String), - #[event_meta(standard = "empty_standard", version = "3.0.0")] + #[event_version("3.0.0")] EmptyEvent, - #[event_meta(standard = "lifetime_std", version = "4.0.0")] + #[event_version("4.0.0")] LifetimeTestA(&'a str), - #[event_meta(standard = "lifetime_std", version = "4.0.0")] + #[event_version("5.0.0")] LifetimeTestB(&'b str), } @@ -53,19 +47,14 @@ pub mod tests { let token_out: AccountId = "test.near".parse().unwrap(); let amount_in: u128 = 100; let amount_out: u128 = 200; - Event::emit(TestEvents::Swap { - token_in, - token_out, - amount_in, - amount_out, - test: String::from("tst"), - }); + TestEvents::Swap { token_in, token_out, amount_in, amount_out, test: String::from("tst") } + .emit(); - Event::emit(TestEvents::StringEvent::(String::from("string"))); + TestEvents::StringEvent::(String::from("string")).emit(); - Event::emit(TestEvents::EmptyEvent::); + TestEvents::EmptyEvent::.emit(); - Event::emit(TestEvents::LifetimeTestA::("lifetime")); + TestEvents::LifetimeTestA::("lifetime").emit(); TestEvents::LifetimeTestB::("lifetime_b").emit(); @@ -73,23 +62,23 @@ pub mod tests { assert!( logs[0] - == r#"EVENT_JSON:{"standard":"swap_standard","version":"1.0.0","event":"swap","data":{"token_in":"wrap.near","token_out":"test.near","amount_in":100,"amount_out":200,"test":"tst"}}"# + == r#"EVENT_JSON:{"standard":"test_standard","version":"1.0.0","event":"swap","data":{"token_in":"wrap.near","token_out":"test.near","amount_in":100,"amount_out":200,"test":"tst"}}"# ); assert!( logs[1] - == r#"EVENT_JSON:{"standard":"string_standard","version":"2.0.0","event":"string_event","data":"string"}"# + == r#"EVENT_JSON:{"standard":"test_standard","version":"2.0.0","event":"string_event","data":"string"}"# ); assert!( logs[2] - == r#"EVENT_JSON:{"standard":"empty_standard","version":"3.0.0","event":"empty_event"}"# + == r#"EVENT_JSON:{"standard":"test_standard","version":"3.0.0","event":"empty_event"}"# ); assert!( logs[3] - == r#"EVENT_JSON:{"standard":"lifetime_std","version":"4.0.0","event":"lifetime_test_a","data":"lifetime"}"# + == r#"EVENT_JSON:{"standard":"test_standard","version":"4.0.0","event":"lifetime_test_a","data":"lifetime"}"# ); assert!( logs[4] - == r#"EVENT_JSON:{"standard":"lifetime_std","version":"4.0.0","event":"lifetime_test_b","data":"lifetime_b"}"# + == r#"EVENT_JSON:{"standard":"test_standard","version":"5.0.0","event":"lifetime_test_b","data":"lifetime_b"}"# ); } } diff --git a/near-sdk/src/types/mod.rs b/near-sdk/src/types/mod.rs index 774196f23..17b9909c2 100644 --- a/near-sdk/src/types/mod.rs +++ b/near-sdk/src/types/mod.rs @@ -14,7 +14,7 @@ mod gas; pub use self::gas::Gas; mod event; -pub use event::{Event, StandardEvent}; +pub use event::StandardEvent; mod error; pub use self::error::Abort; From b4158851291a35441c9a69a900ff708b369ba9ed Mon Sep 17 00:00:00 2001 From: Cole Roberts Date: Mon, 24 Oct 2022 09:03:14 -0400 Subject: [PATCH 04/11] rebase --- near-sdk-macros/src/core_impl/event/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/near-sdk-macros/src/core_impl/event/mod.rs b/near-sdk-macros/src/core_impl/event/mod.rs index 244c8a3dc..a27d30f62 100644 --- a/near-sdk-macros/src/core_impl/event/mod.rs +++ b/near-sdk-macros/src/core_impl/event/mod.rs @@ -18,7 +18,7 @@ pub(crate) fn near_events(attr: TokenStream, item: TokenStream) -> TokenStream { .to_compile_error(), ); } - + if let Ok(mut input) = syn::parse::(item) { let name = &input.ident; let standard_name = format!("{}_event_standard", name); From 51b59cff1c8bb3597454d982a39467edcb4160ad Mon Sep 17 00:00:00 2001 From: Cole Roberts Date: Mon, 24 Oct 2022 09:33:33 -0400 Subject: [PATCH 05/11] feat: changed StandardEvent to EventJson and Display type bound to AsRef --- near-sdk-macros/src/core_impl/event/mod.rs | 2 +- near-sdk-macros/src/lib.rs | 12 ++++++------ near-sdk/src/types/event.rs | 10 +++++----- near-sdk/src/types/mod.rs | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/near-sdk-macros/src/core_impl/event/mod.rs b/near-sdk-macros/src/core_impl/event/mod.rs index a27d30f62..3cec803de 100644 --- a/near-sdk-macros/src/core_impl/event/mod.rs +++ b/near-sdk-macros/src/core_impl/event/mod.rs @@ -62,7 +62,7 @@ fn get_standard_arg(args: &[syn::NestedMeta]) -> Option { let mut standard: Option = None; for arg in args.iter() { if let syn::NestedMeta::Meta(syn::Meta::List(syn::MetaList { path, nested, .. })) = arg { - if path.is_ident("events") { + if path.is_ident("event_json") { for event_arg in nested.iter() { if let syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue { path, diff --git a/near-sdk-macros/src/lib.rs b/near-sdk-macros/src/lib.rs index 2457f231e..bab294e6e 100644 --- a/near-sdk-macros/src/lib.rs +++ b/near-sdk-macros/src/lib.rs @@ -46,11 +46,11 @@ use syn::{parse_quote, File, ItemEnum, ItemImpl, ItemStruct, ItemTrait, WhereCla /// /// Events Standard: /// -/// By passing `events` as an argument `near_bindgen` will generate the relevant code to format events +/// By passing `event_json` as an argument `near_bindgen` will generate the relevant code to format events /// according to NEP-297 /// -/// For parameter serialization, this macro will generate a wrapper to include the NEP-297 standard fields `standard` and `version -/// as well as include serialization reformatting to include the `event` and `data` fields. +/// For parameter serialization, this macro will generate a wrapper struct to include the NEP-297 standard fields `standard` and `version +/// as well as include serialization reformatting to include the `event` and `data` fields automatically. /// The `standard` and `version` values must be included in the enum and variant declaration (see example below). /// By default this will be JSON deserialized with `serde` /// @@ -60,7 +60,7 @@ use syn::{parse_quote, File, ItemEnum, ItemImpl, ItemStruct, ItemTrait, WhereCla /// ```ignore /// use near_sdk::near_bindgen; /// -/// #[near_bindgen(events(standard = "nepXXX"))] +/// #[near_bindgen(event_json(standard = "nepXXX"))] /// pub enum MyEvents { /// #[event_version("1.0.0")] /// Swap { token_in: AccountId, token_out: AccountId, amount_in: u128, amount_out: u128 }, @@ -84,7 +84,7 @@ use syn::{parse_quote, File, ItemEnum, ItemImpl, ItemStruct, ItemTrait, WhereCla /// ``` #[proc_macro_attribute] pub fn near_bindgen(_attr: TokenStream, item: TokenStream) -> TokenStream { - if _attr.to_string().contains("events") { + if _attr.to_string().contains("event_json") { return core_impl::near_events(_attr, item); } @@ -427,7 +427,7 @@ pub fn derive_event_attributes(item: TokenStream) -> TokenStream { event_data: &#event_lifetime #name #type_generics } - impl #impl_generics near_sdk::StandardEvent for #name #type_generics #where_clause { + impl #impl_generics near_sdk::EventJson for #name #type_generics #where_clause { type EventString = String; fn format(&self) -> Self::EventString { let (standard, version): (String, String) = match self { diff --git a/near-sdk/src/types/event.rs b/near-sdk/src/types/event.rs index 9ab6874f1..fb13be0ea 100644 --- a/near-sdk/src/types/event.rs +++ b/near-sdk/src/types/event.rs @@ -1,8 +1,8 @@ -pub trait StandardEvent { - type EventString: std::fmt::Display; +pub trait EventJson { + type EventString: std::convert::AsRef; fn format(&self) -> Self::EventString; fn emit(&self) { - crate::env::log_str(&format!("EVENT_JSON:{}", self.format())); + crate::env::log_str(&format!("EVENT_JSON:{}", self.format().as_ref())); } } @@ -11,10 +11,10 @@ pub mod tests { use crate::test_utils::get_logs; use crate::{near_bindgen, AccountId}; - use super::StandardEvent; + use super::EventJson; use crate as near_sdk; - #[near_bindgen(events(standard = "test_standard", random = "random"), other_random)] + #[near_bindgen(event_json(standard = "test_standard", random = "random"), other_random)] pub enum TestEvents<'a, 'b, T> where T: crate::serde::Serialize, diff --git a/near-sdk/src/types/mod.rs b/near-sdk/src/types/mod.rs index 17b9909c2..c0d17b118 100644 --- a/near-sdk/src/types/mod.rs +++ b/near-sdk/src/types/mod.rs @@ -14,7 +14,7 @@ mod gas; pub use self::gas::Gas; mod event; -pub use event::StandardEvent; +pub use event::EventJson; mod error; pub use self::error::Abort; From 6eecebe13652462e536d9919708455958af2ba35 Mon Sep 17 00:00:00 2001 From: Cole Roberts Date: Mon, 31 Oct 2022 16:41:09 -0400 Subject: [PATCH 06/11] fix: add support for multiple event enums in the same file --- near-sdk-macros/src/core_impl/event/mod.rs | 8 ++-- near-sdk-macros/src/lib.rs | 52 +++++++++++----------- near-sdk/src/types/event.rs | 46 ++++++++++++------- 3 files changed, 58 insertions(+), 48 deletions(-) diff --git a/near-sdk-macros/src/core_impl/event/mod.rs b/near-sdk-macros/src/core_impl/event/mod.rs index 3cec803de..e9da4e6bc 100644 --- a/near-sdk-macros/src/core_impl/event/mod.rs +++ b/near-sdk-macros/src/core_impl/event/mod.rs @@ -3,7 +3,7 @@ use proc_macro2::Span; use quote::quote; use syn::{parse_quote, ItemEnum, LitStr}; -/// this function is used to inject Serialization Macros and the `near_sdk::EventMetadata` macro. +/// this function is used to inject serialization macros and the `near_sdk::EventMetadata` macro. /// In addition, this function extracts the event's `standard` value and injects it as a constant to be used by /// the `near_sdk::EventMetadata` derive macro pub(crate) fn near_events(attr: TokenStream, item: TokenStream) -> TokenStream { @@ -13,7 +13,7 @@ pub(crate) fn near_events(attr: TokenStream, item: TokenStream) -> TokenStream { return TokenStream::from( syn::Error::new( Span::call_site(), - "Near events must have a `standard` value as an argument for `events` in the `near_bindgen` arguments. The value must be a string literal.", + "Near events must have a `standard` value as an argument for `event_json` in the `near_bindgen` arguments. The value must be a string literal.", ) .to_compile_error(), ); @@ -39,7 +39,7 @@ pub(crate) fn near_events(attr: TokenStream, item: TokenStream) -> TokenStream { TokenStream::from( syn::Error::new( Span::call_site(), - "`#[near_bindgen(events(standard = \"nepXXX\"))]` can only be used as an attribute on enums.", + "`#[near_bindgen(event_json(standard = \"nepXXX\"))]` can only be used as an attribute on enums.", ) .to_compile_error(), ) @@ -57,7 +57,7 @@ pub(crate) fn get_event_version(var: &syn::Variant) -> Option { None } -/// this function returns the `standard` value from `#[near_bindgen(events(standard = "nepXXX"))]` +/// this function returns the `standard` value from `#[near_bindgen(event_json(standard = "nepXXX"))]` fn get_standard_arg(args: &[syn::NestedMeta]) -> Option { let mut standard: Option = None; for arg in args.iter() { diff --git a/near-sdk-macros/src/lib.rs b/near-sdk-macros/src/lib.rs index bab294e6e..db7f8ff3c 100644 --- a/near-sdk-macros/src/lib.rs +++ b/near-sdk-macros/src/lib.rs @@ -83,9 +83,9 @@ use syn::{parse_quote, File, ItemEnum, ItemImpl, ItemStruct, ItemTrait, WhereCla /// } /// ``` #[proc_macro_attribute] -pub fn near_bindgen(_attr: TokenStream, item: TokenStream) -> TokenStream { - if _attr.to_string().contains("event_json") { - return core_impl::near_events(_attr, item); +pub fn near_bindgen(attr: TokenStream, item: TokenStream) -> TokenStream { + if attr.to_string().contains("event_json") { + return core_impl::near_events(attr, item); } if let Ok(input) = syn::parse::(item.clone()) { @@ -360,7 +360,7 @@ pub fn function_error(item: TokenStream) -> TokenStream { }) } -/// Adding the `events` argument for the `#[near_bindgen]` macro injects this derive macro and should be the used instead of this +/// NOTE: This is an internal implementation for `#[near_bindgen(events(standard = ...))]` attribute. /// /// This derive macro is used to inject the necessary wrapper and logic to auto format /// standard event logs. The other appropriate attribute macros are not injected with this macro. @@ -378,49 +378,47 @@ pub fn function_error(item: TokenStream) -> TokenStream { pub fn derive_event_attributes(item: TokenStream) -> TokenStream { if let Ok(input) = syn::parse::(item) { let name = &input.ident; + // build wrapper name + let wrapper_name = format!("{}EventBuilder", name); + let wrapper_ident = syn::Ident::new(&wrapper_name, Span::call_site()); + // get `standard` const injected from `near_events` let standard_name = format!("{}_event_standard", name); let standard_ident = syn::Ident::new(&standard_name, Span::call_site()); // version from each attribute macro - let mut attr_error: u8 = 0; let mut event_meta: Vec = vec![]; - let _ = &input.variants.iter().for_each(|var| { + for var in &input.variants { if let Some(version) = core_impl::get_event_version(var) { let var_ident = &var.ident; event_meta.push(quote! { #name::#var_ident { .. } => {(#standard_ident.to_string(), #version.to_string())} }) } else { - attr_error += 1; + return TokenStream::from( + syn::Error::new( + Span::call_site(), + "Near events must have `event_version`. Must have a single string literal value.", + ) + .to_compile_error(), + ); } - }); + } // handle lifetimes, generics, and where clauses let (impl_generics, type_generics, where_clause) = &input.generics.split_for_impl(); - // add 'near_event lifetime for user defined events + // add `'near_event` lifetime for user defined events let mut generics = input.generics.clone(); let event_lifetime = syn::Lifetime::new("'near_event", Span::call_site()); - generics.params.insert( - generics.params.len(), - syn::GenericParam::Lifetime(syn::LifetimeDef::new(event_lifetime.clone())), - ); + generics + .params + .insert(0, syn::GenericParam::Lifetime(syn::LifetimeDef::new(event_lifetime.clone()))); let (custom_impl_generics, ..) = generics.split_for_impl(); - if attr_error > 0 { - return TokenStream::from( - syn::Error::new( - Span::call_site(), - "Near events must have `event_version`. Must have a single string literal value.", - ) - .to_compile_error(), - ); - } - TokenStream::from(quote! { #[derive(near_sdk::serde::Serialize)] #[serde(crate="near_sdk::serde")] #[serde(rename_all="snake_case")] - struct EventBuilder #custom_impl_generics #where_clause { + struct #wrapper_ident #custom_impl_generics #where_clause { standard: String, version: String, #[serde(flatten)] @@ -428,12 +426,12 @@ pub fn derive_event_attributes(item: TokenStream) -> TokenStream { } impl #impl_generics near_sdk::EventJson for #name #type_generics #where_clause { - type EventString = String; - fn format(&self) -> Self::EventString { + type EventMessage = String; + fn format(&self) -> Self::EventMessage { let (standard, version): (String, String) = match self { #(#event_meta),* }; - let event = EventBuilder {standard, version, event_data: self }; + let event = #wrapper_ident {standard, version, event_data: self }; near_sdk::serde_json::to_string(&event) .unwrap_or_else(|_| near_sdk::env::abort()) } diff --git a/near-sdk/src/types/event.rs b/near-sdk/src/types/event.rs index fb13be0ea..0a2dd68c6 100644 --- a/near-sdk/src/types/event.rs +++ b/near-sdk/src/types/event.rs @@ -1,6 +1,6 @@ pub trait EventJson { - type EventString: std::convert::AsRef; - fn format(&self) -> Self::EventString; + type EventMessage: std::convert::AsRef; + fn format(&self) -> Self::EventMessage; fn emit(&self) { crate::env::log_str(&format!("EVENT_JSON:{}", self.format().as_ref())); } @@ -41,6 +41,12 @@ pub mod tests { LifetimeTestB(&'b str), } + #[near_bindgen(event_json(standard = "another_standard"))] + pub enum AnotherEvent { + #[event_version("1.0.0")] + Test, + } + #[test] fn test_json_emit() { let token_in: AccountId = "wrap.near".parse().unwrap(); @@ -58,27 +64,33 @@ pub mod tests { TestEvents::LifetimeTestB::("lifetime_b").emit(); + AnotherEvent::Test.emit(); + let logs = get_logs(); - assert!( - logs[0] - == r#"EVENT_JSON:{"standard":"test_standard","version":"1.0.0","event":"swap","data":{"token_in":"wrap.near","token_out":"test.near","amount_in":100,"amount_out":200,"test":"tst"}}"# + assert_eq!( + logs[0], + r#"EVENT_JSON:{"standard":"test_standard","version":"1.0.0","event":"swap","data":{"token_in":"wrap.near","token_out":"test.near","amount_in":100,"amount_out":200,"test":"tst"}}"# + ); + assert_eq!( + logs[1], + r#"EVENT_JSON:{"standard":"test_standard","version":"2.0.0","event":"string_event","data":"string"}"# ); - assert!( - logs[1] - == r#"EVENT_JSON:{"standard":"test_standard","version":"2.0.0","event":"string_event","data":"string"}"# + assert_eq!( + logs[2], + r#"EVENT_JSON:{"standard":"test_standard","version":"3.0.0","event":"empty_event"}"# ); - assert!( - logs[2] - == r#"EVENT_JSON:{"standard":"test_standard","version":"3.0.0","event":"empty_event"}"# + assert_eq!( + logs[3], + r#"EVENT_JSON:{"standard":"test_standard","version":"4.0.0","event":"lifetime_test_a","data":"lifetime"}"# ); - assert!( - logs[3] - == r#"EVENT_JSON:{"standard":"test_standard","version":"4.0.0","event":"lifetime_test_a","data":"lifetime"}"# + assert_eq!( + logs[4], + r#"EVENT_JSON:{"standard":"test_standard","version":"5.0.0","event":"lifetime_test_b","data":"lifetime_b"}"# ); - assert!( - logs[4] - == r#"EVENT_JSON:{"standard":"test_standard","version":"5.0.0","event":"lifetime_test_b","data":"lifetime_b"}"# + assert_eq!( + logs[5], + r#"EVENT_JSON:{"standard":"another_standard","version":"1.0.0","event":"test"}"# ); } } From 1e6c09edf1778fb6e2ee1604965df09bcc11545c Mon Sep 17 00:00:00 2001 From: Austin Abell Date: Thu, 3 Nov 2022 12:59:31 -0400 Subject: [PATCH 07/11] refactor: move codegen type inside impl scope --- near-sdk-macros/src/lib.rs | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/near-sdk-macros/src/lib.rs b/near-sdk-macros/src/lib.rs index db7f8ff3c..1aa8dfbbd 100644 --- a/near-sdk-macros/src/lib.rs +++ b/near-sdk-macros/src/lib.rs @@ -378,9 +378,6 @@ pub fn function_error(item: TokenStream) -> TokenStream { pub fn derive_event_attributes(item: TokenStream) -> TokenStream { if let Ok(input) = syn::parse::(item) { let name = &input.ident; - // build wrapper name - let wrapper_name = format!("{}EventBuilder", name); - let wrapper_ident = syn::Ident::new(&wrapper_name, Span::call_site()); // get `standard` const injected from `near_events` let standard_name = format!("{}_event_standard", name); let standard_ident = syn::Ident::new(&standard_name, Span::call_site()); @@ -414,24 +411,23 @@ pub fn derive_event_attributes(item: TokenStream) -> TokenStream { let (custom_impl_generics, ..) = generics.split_for_impl(); TokenStream::from(quote! { - - #[derive(near_sdk::serde::Serialize)] - #[serde(crate="near_sdk::serde")] - #[serde(rename_all="snake_case")] - struct #wrapper_ident #custom_impl_generics #where_clause { - standard: String, - version: String, - #[serde(flatten)] - event_data: &#event_lifetime #name #type_generics - } - impl #impl_generics near_sdk::EventJson for #name #type_generics #where_clause { type EventMessage = String; fn format(&self) -> Self::EventMessage { let (standard, version): (String, String) = match self { #(#event_meta),* }; - let event = #wrapper_ident {standard, version, event_data: self }; + + #[derive(near_sdk::serde::Serialize)] + #[serde(crate="near_sdk::serde")] + #[serde(rename_all="snake_case")] + struct EventBuilder #custom_impl_generics #where_clause { + standard: String, + version: String, + #[serde(flatten)] + event_data: &#event_lifetime #name #type_generics + } + let event = EventBuilder { standard, version, event_data: self }; near_sdk::serde_json::to_string(&event) .unwrap_or_else(|_| near_sdk::env::abort()) } From 1cc5c19d134c5e72ef3ec5cfe7e1f5701b82ed7f Mon Sep 17 00:00:00 2001 From: Austin Abell Date: Thu, 3 Nov 2022 13:45:32 -0400 Subject: [PATCH 08/11] remove EventJson trait --- near-sdk-macros/src/lib.rs | 10 ++-- near-sdk/src/types/event.rs | 96 ------------------------------- near-sdk/src/types/event_tests.rs | 78 +++++++++++++++++++++++++ near-sdk/src/types/mod.rs | 4 +- 4 files changed, 85 insertions(+), 103 deletions(-) delete mode 100644 near-sdk/src/types/event.rs create mode 100644 near-sdk/src/types/event_tests.rs diff --git a/near-sdk-macros/src/lib.rs b/near-sdk-macros/src/lib.rs index 1aa8dfbbd..f6c37076a 100644 --- a/near-sdk-macros/src/lib.rs +++ b/near-sdk-macros/src/lib.rs @@ -411,9 +411,8 @@ pub fn derive_event_attributes(item: TokenStream) -> TokenStream { let (custom_impl_generics, ..) = generics.split_for_impl(); TokenStream::from(quote! { - impl #impl_generics near_sdk::EventJson for #name #type_generics #where_clause { - type EventMessage = String; - fn format(&self) -> Self::EventMessage { + impl #impl_generics #name #type_generics #where_clause { + fn emit(&self) { let (standard, version): (String, String) = match self { #(#event_meta),* }; @@ -428,8 +427,9 @@ pub fn derive_event_attributes(item: TokenStream) -> TokenStream { event_data: &#event_lifetime #name #type_generics } let event = EventBuilder { standard, version, event_data: self }; - near_sdk::serde_json::to_string(&event) - .unwrap_or_else(|_| near_sdk::env::abort()) + let json = near_sdk::serde_json::to_string(&event) + .unwrap_or_else(|_| near_sdk::env::abort()); + near_sdk::env::log_str(&format!("EVENT_JSON:{}", json)); } } }) diff --git a/near-sdk/src/types/event.rs b/near-sdk/src/types/event.rs deleted file mode 100644 index 0a2dd68c6..000000000 --- a/near-sdk/src/types/event.rs +++ /dev/null @@ -1,96 +0,0 @@ -pub trait EventJson { - type EventMessage: std::convert::AsRef; - fn format(&self) -> Self::EventMessage; - fn emit(&self) { - crate::env::log_str(&format!("EVENT_JSON:{}", self.format().as_ref())); - } -} - -#[cfg(test)] -pub mod tests { - use crate::test_utils::get_logs; - use crate::{near_bindgen, AccountId}; - - use super::EventJson; - use crate as near_sdk; - - #[near_bindgen(event_json(standard = "test_standard", random = "random"), other_random)] - pub enum TestEvents<'a, 'b, T> - where - T: crate::serde::Serialize, - { - #[event_version("1.0.0")] - Swap { - token_in: AccountId, - token_out: AccountId, - amount_in: u128, - amount_out: u128, - test: T, - }, - - #[event_version("2.0.0")] - StringEvent(String), - - #[event_version("3.0.0")] - EmptyEvent, - - #[event_version("4.0.0")] - LifetimeTestA(&'a str), - - #[event_version("5.0.0")] - LifetimeTestB(&'b str), - } - - #[near_bindgen(event_json(standard = "another_standard"))] - pub enum AnotherEvent { - #[event_version("1.0.0")] - Test, - } - - #[test] - fn test_json_emit() { - let token_in: AccountId = "wrap.near".parse().unwrap(); - let token_out: AccountId = "test.near".parse().unwrap(); - let amount_in: u128 = 100; - let amount_out: u128 = 200; - TestEvents::Swap { token_in, token_out, amount_in, amount_out, test: String::from("tst") } - .emit(); - - TestEvents::StringEvent::(String::from("string")).emit(); - - TestEvents::EmptyEvent::.emit(); - - TestEvents::LifetimeTestA::("lifetime").emit(); - - TestEvents::LifetimeTestB::("lifetime_b").emit(); - - AnotherEvent::Test.emit(); - - let logs = get_logs(); - - assert_eq!( - logs[0], - r#"EVENT_JSON:{"standard":"test_standard","version":"1.0.0","event":"swap","data":{"token_in":"wrap.near","token_out":"test.near","amount_in":100,"amount_out":200,"test":"tst"}}"# - ); - assert_eq!( - logs[1], - r#"EVENT_JSON:{"standard":"test_standard","version":"2.0.0","event":"string_event","data":"string"}"# - ); - assert_eq!( - logs[2], - r#"EVENT_JSON:{"standard":"test_standard","version":"3.0.0","event":"empty_event"}"# - ); - assert_eq!( - logs[3], - r#"EVENT_JSON:{"standard":"test_standard","version":"4.0.0","event":"lifetime_test_a","data":"lifetime"}"# - ); - assert_eq!( - logs[4], - r#"EVENT_JSON:{"standard":"test_standard","version":"5.0.0","event":"lifetime_test_b","data":"lifetime_b"}"# - ); - assert_eq!( - logs[5], - r#"EVENT_JSON:{"standard":"another_standard","version":"1.0.0","event":"test"}"# - ); - } -} diff --git a/near-sdk/src/types/event_tests.rs b/near-sdk/src/types/event_tests.rs new file mode 100644 index 000000000..2d94310b8 --- /dev/null +++ b/near-sdk/src/types/event_tests.rs @@ -0,0 +1,78 @@ +use crate::test_utils::get_logs; +use crate::{near_bindgen, AccountId}; + +use crate as near_sdk; + +#[near_bindgen(event_json(standard = "test_standard", random = "random"), other_random)] +pub enum TestEvents<'a, 'b, T> +where + T: crate::serde::Serialize, +{ + #[event_version("1.0.0")] + Swap { token_in: AccountId, token_out: AccountId, amount_in: u128, amount_out: u128, test: T }, + + #[event_version("2.0.0")] + StringEvent(String), + + #[event_version("3.0.0")] + EmptyEvent, + + #[event_version("4.0.0")] + LifetimeTestA(&'a str), + + #[event_version("5.0.0")] + LifetimeTestB(&'b str), +} + +#[near_bindgen(event_json(standard = "another_standard"))] +pub enum AnotherEvent { + #[event_version("1.0.0")] + Test, +} + +#[test] +fn test_json_emit() { + let token_in: AccountId = "wrap.near".parse().unwrap(); + let token_out: AccountId = "test.near".parse().unwrap(); + let amount_in: u128 = 100; + let amount_out: u128 = 200; + TestEvents::Swap { token_in, token_out, amount_in, amount_out, test: String::from("tst") } + .emit(); + + TestEvents::StringEvent::(String::from("string")).emit(); + + TestEvents::EmptyEvent::.emit(); + + TestEvents::LifetimeTestA::("lifetime").emit(); + + TestEvents::LifetimeTestB::("lifetime_b").emit(); + + AnotherEvent::Test.emit(); + + let logs = get_logs(); + + assert_eq!( + logs[0], + r#"EVENT_JSON:{"standard":"test_standard","version":"1.0.0","event":"swap","data":{"token_in":"wrap.near","token_out":"test.near","amount_in":100,"amount_out":200,"test":"tst"}}"# + ); + assert_eq!( + logs[1], + r#"EVENT_JSON:{"standard":"test_standard","version":"2.0.0","event":"string_event","data":"string"}"# + ); + assert_eq!( + logs[2], + r#"EVENT_JSON:{"standard":"test_standard","version":"3.0.0","event":"empty_event"}"# + ); + assert_eq!( + logs[3], + r#"EVENT_JSON:{"standard":"test_standard","version":"4.0.0","event":"lifetime_test_a","data":"lifetime"}"# + ); + assert_eq!( + logs[4], + r#"EVENT_JSON:{"standard":"test_standard","version":"5.0.0","event":"lifetime_test_b","data":"lifetime_b"}"# + ); + assert_eq!( + logs[5], + r#"EVENT_JSON:{"standard":"another_standard","version":"1.0.0","event":"test"}"# + ); +} diff --git a/near-sdk/src/types/mod.rs b/near-sdk/src/types/mod.rs index c0d17b118..19d3da9b3 100644 --- a/near-sdk/src/types/mod.rs +++ b/near-sdk/src/types/mod.rs @@ -13,8 +13,8 @@ pub use self::account_id::{AccountId, ParseAccountIdError}; mod gas; pub use self::gas::Gas; -mod event; -pub use event::EventJson; +#[cfg(test)] +mod event_tests; mod error; pub use self::error::Abort; From 9413fb49fd23f32a0c7f94ffddc9836f3a0342d9 Mon Sep 17 00:00:00 2001 From: Austin Abell Date: Mon, 7 Nov 2022 19:57:26 -0500 Subject: [PATCH 09/11] Update near-sdk-macros/src/lib.rs Co-authored-by: Vlad Frolov --- near-sdk-macros/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/near-sdk-macros/src/lib.rs b/near-sdk-macros/src/lib.rs index f6c37076a..2444085b9 100644 --- a/near-sdk-macros/src/lib.rs +++ b/near-sdk-macros/src/lib.rs @@ -75,9 +75,9 @@ use syn::{parse_quote, File, ItemEnum, ItemImpl, ItemStruct, ItemTrait, WhereCla /// #[near_bindgen] /// impl Contract { /// pub fn some_function(&self) { -/// MyEvents::StringEvent( -/// String::from("some_string") -/// ).emit(); +/// MyEvents::StringEvent( +/// String::from("some_string") +/// ).emit(); /// } /// /// } From f0b5fcf229b6267487ffaf741a5260e7e6ccd43b Mon Sep 17 00:00:00 2001 From: Austin Abell Date: Mon, 7 Nov 2022 19:57:38 -0500 Subject: [PATCH 10/11] Update near-sdk-macros/src/core_impl/event/mod.rs Co-authored-by: Vlad Frolov --- near-sdk-macros/src/core_impl/event/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/near-sdk-macros/src/core_impl/event/mod.rs b/near-sdk-macros/src/core_impl/event/mod.rs index e9da4e6bc..7bfc698e6 100644 --- a/near-sdk-macros/src/core_impl/event/mod.rs +++ b/near-sdk-macros/src/core_impl/event/mod.rs @@ -13,7 +13,7 @@ pub(crate) fn near_events(attr: TokenStream, item: TokenStream) -> TokenStream { return TokenStream::from( syn::Error::new( Span::call_site(), - "Near events must have a `standard` value as an argument for `event_json` in the `near_bindgen` arguments. The value must be a string literal.", + "Near events must have a `standard` value as an argument for `event_json` in the `near_bindgen` arguments. The value must be a string literal, e.g. \"nep999\", \"mintbase-marketplace\".", ) .to_compile_error(), ); From b74e2d6c942915e3caf905942d0b2a40c6f949a6 Mon Sep 17 00:00:00 2001 From: Austin Abell Date: Mon, 7 Nov 2022 20:00:36 -0500 Subject: [PATCH 11/11] move test --- near-sdk/src/types/mod.rs | 3 --- near-sdk/{src/types => tests}/event_tests.rs | 8 +++----- 2 files changed, 3 insertions(+), 8 deletions(-) rename near-sdk/{src/types => tests}/event_tests.rs (94%) diff --git a/near-sdk/src/types/mod.rs b/near-sdk/src/types/mod.rs index 19d3da9b3..6e1de22cf 100644 --- a/near-sdk/src/types/mod.rs +++ b/near-sdk/src/types/mod.rs @@ -13,9 +13,6 @@ pub use self::account_id::{AccountId, ParseAccountIdError}; mod gas; pub use self::gas::Gas; -#[cfg(test)] -mod event_tests; - mod error; pub use self::error::Abort; pub use self::error::FunctionError; diff --git a/near-sdk/src/types/event_tests.rs b/near-sdk/tests/event_tests.rs similarity index 94% rename from near-sdk/src/types/event_tests.rs rename to near-sdk/tests/event_tests.rs index 2d94310b8..62356dd51 100644 --- a/near-sdk/src/types/event_tests.rs +++ b/near-sdk/tests/event_tests.rs @@ -1,12 +1,10 @@ -use crate::test_utils::get_logs; -use crate::{near_bindgen, AccountId}; - -use crate as near_sdk; +use near_sdk::test_utils::get_logs; +use near_sdk::{near_bindgen, AccountId}; #[near_bindgen(event_json(standard = "test_standard", random = "random"), other_random)] pub enum TestEvents<'a, 'b, T> where - T: crate::serde::Serialize, + T: near_sdk::serde::Serialize, { #[event_version("1.0.0")] Swap { token_in: AccountId, token_out: AccountId, amount_in: u128, amount_out: u128, test: T },