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 12, 2022
1 parent 4d4842c commit e486714
Show file tree
Hide file tree
Showing 7 changed files with 281 additions and 73 deletions.
1 change: 1 addition & 0 deletions near-sdk-macros/src/core_impl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ mod utils;
pub(crate) use code_generator::*;
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
}
149 changes: 149 additions & 0 deletions near-sdk-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,3 +316,152 @@ 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(),
)
}
}

/// 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()
/// }
/// }
/// ```
#[proc_macro_attribute]
pub fn near_events(_attr: TokenStream, 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(),
)
}
}
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]}"}"#
);
}
}
3 changes: 2 additions & 1 deletion near-sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
extern crate quickcheck;

pub use near_sdk_macros::{
ext_contract, near_bindgen, BorshStorageKey, FunctionError, PanicOnDefault,
ext_contract, near_bindgen, near_events, BorshStorageKey, EventMetadata, FunctionError,
PanicOnDefault,
};

pub mod store;
Expand Down
95 changes: 95 additions & 0 deletions near-sdk/src/types/event.rs
Original file line number Diff line number Diff line change
@@ -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_events, AccountId};

use super::Event;
use super::StandardEvent;
use crate as near_sdk;

#[near_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>(String::from("string")));

Event::emit(TestEvents::EmptyEvent::<String>);

Event::emit(TestEvents::LifetimeTestA::<String>("lifetime"));

TestEvents::LifetimeTestB::<String>("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"}"#
);
}
}
Loading

0 comments on commit e486714

Please sign in to comment.