Skip to content

Commit

Permalink
feat: Changed abstraction api
Browse files Browse the repository at this point in the history
  • Loading branch information
coleFD committed Oct 13, 2022
1 parent 4d4842c commit bc1c77a
Show file tree
Hide file tree
Showing 8 changed files with 341 additions and 73 deletions.
75 changes: 75 additions & 0 deletions near-sdk-macros/src/core_impl/event/mod.rs
Original file line number Diff line number Diff line change
@@ -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_events, Event};
///
/// #[near_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::<ItemEnum>(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(),
)
}
}
3 changes: 3 additions & 0 deletions near-sdk-macros/src/core_impl/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
23 changes: 22 additions & 1 deletion near-sdk-macros/src/core_impl/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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<LitStr>, Option<LitStr>) {
let mut v: (Option<LitStr>, Option<LitStr>) = (None, None);
var.attrs.iter().for_each(|attr| {
let test = attr.parse_args_with(
syn::punctuated::Punctuated::<syn::MetaNameValue, syn::Token![,]>::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
}
133 changes: 133 additions & 0 deletions near-sdk-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{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.clone());
}

if let Ok(input) = syn::parse::<ItemStruct>(item.clone()) {
let ext_gen = generate_ext_structs(&input.ident, Some(&input.generics));
#[cfg(feature = "__abi-embed")]
Expand Down Expand Up @@ -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::<ItemEnum>(item) {
let name = &input.ident;
// get standard and version from each attribute macro
let mut attr_error: u8 = 0;
let mut event_meta: Vec<proc_macro2::TokenStream> = 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(),
)
}
}
80 changes: 9 additions & 71 deletions near-sdk/src/environment/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")))]
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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]}"}"#
);
}
}
2 changes: 1 addition & 1 deletion near-sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading

0 comments on commit bc1c77a

Please sign in to comment.