From 1952ac197590a676a5ebde31c23d2976069bb89f Mon Sep 17 00:00:00 2001 From: Jacob Date: Thu, 22 Sep 2022 03:29:29 +0900 Subject: [PATCH 1/4] feat: batch events, fix #62 --- README.md | 24 ++-- macros/src/lib.rs | 15 ++- macros/src/standard/event.rs | 25 ++-- macros/src/standard/nep141.rs | 8 +- macros/src/standard/nep297.rs | 91 ++++++------- src/owner.rs | 49 ++++--- src/pause.rs | 33 +++-- src/standard/nep141.rs | 85 +++++++----- src/standard/nep297.rs | 149 ++++++++++++++------- tests/macros/event.rs | 73 +++++----- tests/macros/mod.rs | 36 +++-- tests/macros/standard/fungible_token.rs | 5 +- workspaces-tests/src/bin/fungible_token.rs | 2 +- 13 files changed, 353 insertions(+), 242 deletions(-) diff --git a/README.md b/README.md index 7eae596..985ceca 100644 --- a/README.md +++ b/README.md @@ -122,26 +122,24 @@ fn own_accept_owner(&mut self); ### Events ```rust -use serde::Serialize; use near_contract_tools::{event, standard::nep297::Event}; -#[derive(Serialize)] -pub struct Nep171NftMintData { +#[event(standard = "nep171", version = "1.0.0")] +pub struct MintEvent { pub owner_id: String, pub token_ids: Vec, } -#[event(standard = "nep171", version = "1.0.0")] -pub enum Nep171 { - NftMint(Vec), -} - -let my_event = Nep171::NftMint(vec![Nep171NftMintData { - owner_id: "owner".to_string(), - token_ids: vec!["token_1".to_string(), "token_2".to_string()], -}]); +let e = MintEvent { + owner_id: "account".to_string(), + token_ids: vec![ + "t1".to_string(), + "t2".to_string(), + ], +}; -my_event.emit(); // Emits event to the blockchain +// Emits the event to the blockchain +e.emit(); ``` ### Fungible Token diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 0740022..3e71076 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -2,7 +2,7 @@ use darling::{FromDeriveInput, FromMeta}; use proc_macro::TokenStream; -use syn::{parse_macro_input, AttributeArgs, DeriveInput}; +use syn::{parse_macro_input, AttributeArgs, DeriveInput, Item}; mod approval; mod migrate; @@ -47,7 +47,15 @@ where /// /// Specify event standard parameters: `#[nep297(standard = "...", version = "...")]` /// -/// Rename strategy for all variants (default: unchanged): `#[event(..., rename_all = "")]` +/// Optional: `#[nep297(name = "..."), batch]` +/// +/// The `batch` flag means that events will always be emitted as a homogenous array: +/// +/// ```ignore +/// [MyBatchableEvent("one"), MyBatchableEvent("two")].emit(); +/// ``` +/// +/// Rename strategy for all variants (default: unchanged): `#[event(rename = "")]` /// Options for ``: /// - `UpperCamelCase` /// - `lowerCamelCase` @@ -167,9 +175,10 @@ pub fn derive_simple_multisig(input: TokenStream) -> TokenStream { #[proc_macro_attribute] pub fn event(attr: TokenStream, item: TokenStream) -> TokenStream { let attr = parse_macro_input!(attr as AttributeArgs); + let item = parse_macro_input!(item as Item); standard::event::EventAttributeMeta::from_list(&attr) - .and_then(|meta| standard::event::event_attribute(meta, item.into())) + .and_then(|meta| standard::event::event_attribute(meta, item)) .map(Into::into) .unwrap_or_else(|e| e.write_errors().into()) } diff --git a/macros/src/standard/event.rs b/macros/src/standard/event.rs index 63b3627..b67ce0b 100644 --- a/macros/src/standard/event.rs +++ b/macros/src/standard/event.rs @@ -1,6 +1,7 @@ -use darling::FromMeta; +use darling::{util::Flag, FromMeta}; use proc_macro2::TokenStream; use quote::quote; +use syn::Item; use crate::rename::RenameStrategy; @@ -8,11 +9,10 @@ use crate::rename::RenameStrategy; pub struct EventAttributeMeta { pub standard: String, pub version: String, - pub rename_all: Option, + pub rename: Option, + pub name: Option, + pub batch: Flag, - // pub me: String, - // pub macros: String, - // pub serde: String, #[darling(rename = "crate", default = "crate::default_crate_name")] pub me: syn::Path, #[darling(default = "crate::default_macros")] @@ -23,26 +23,31 @@ pub struct EventAttributeMeta { pub fn event_attribute( attr: EventAttributeMeta, - item: TokenStream, + item: Item, ) -> Result { let EventAttributeMeta { standard, version, - rename_all, + rename, + name, + batch, serde, me, macros, } = attr; - let rename_all = rename_all.unwrap_or(RenameStrategy::SnakeCase).to_string(); + let rename = rename.unwrap_or(RenameStrategy::SnakeCase).to_string(); + let name = name.map(|n| quote! { , name = #n }); let serde_str = quote! { #serde }.to_string(); let me_str = quote! { #me }.to_string(); + let batch = batch.is_present().then_some(quote! {, batch}); + Ok(quote::quote! { #[derive(#macros::Nep297, #serde::Serialize)] - #[nep297(standard = #standard, version = #version, rename_all = #rename_all, crate = #me_str)] - #[serde(crate = #serde_str, untagged)] + #[nep297(standard = #standard, version = #version, rename = #rename, crate = #me_str #name #batch)] + #[serde(crate = #serde_str)] #item }) } diff --git a/macros/src/standard/nep141.rs b/macros/src/standard/nep141.rs index 8789de1..0bad864 100644 --- a/macros/src/standard/nep141.rs +++ b/macros/src/standard/nep141.rs @@ -51,16 +51,12 @@ pub fn expand(meta: Nep141Meta) -> Result { }); Ok(quote! { - use #me::standard::nep141::Nep141Controller; - impl #imp #me::standard::nep141::Nep141Controller for #ident #ty #wher { fn root() -> #me::slot::Slot<()> { #me::slot::Slot::root(#storage_key) } } - use #me::standard::nep141::Nep141; - #[#near_sdk::near_bindgen] impl #imp #me::standard::nep141::Nep141 for #ident #ty #wher { #[payable] @@ -72,7 +68,7 @@ pub fn expand(meta: Nep141Meta) -> Result { ) { use #me::{ standard::{ - nep141::{Nep141Controller, Nep141Event}, + nep141::{Nep141Controller, event}, nep297::Event, }, }; @@ -142,8 +138,6 @@ pub fn expand(meta: Nep141Meta) -> Result { } } - use #me::standard::nep141::Nep141Resolver; - #[#near_sdk::near_bindgen] impl #imp #me::standard::nep141::Nep141Resolver for #ident #ty #wher { #[private] diff --git a/macros/src/standard/nep297.rs b/macros/src/standard/nep297.rs index ba72702..2db102e 100644 --- a/macros/src/standard/nep297.rs +++ b/macros/src/standard/nep297.rs @@ -1,15 +1,17 @@ -use darling::{ast::Style, FromDeriveInput, FromVariant}; +use darling::{util::Flag, FromDeriveInput, FromVariant}; use proc_macro2::TokenStream; use quote::quote; use crate::rename::RenameStrategy; #[derive(Debug, FromDeriveInput)] -#[darling(attributes(nep297), supports(enum_any))] +#[darling(attributes(nep297), supports(struct_any))] pub struct Nep297Meta { pub standard: String, pub version: String, - pub rename_all: Option, + pub name: Option, + pub rename: Option, + pub batch: Flag, pub ident: syn::Ident, pub generics: syn::Generics, @@ -33,7 +35,9 @@ pub fn expand(meta: Nep297Meta) -> Result { let Nep297Meta { standard, version, - rename_all, + name, + rename, + batch, ident: type_name, generics, data, @@ -43,65 +47,48 @@ pub fn expand(meta: Nep297Meta) -> Result { let (imp, ty, wher) = generics.split_for_impl(); // Variant attributes - let arms = match &data { - darling::ast::Data::Enum(variants) => variants, - _ => unreachable!(), // Because of darling supports(enum_any) above - } - .iter() - .map( - |EventVariantReceiver { - ident, - fields, - rename, - name, - }| { + let event = match &data { + darling::ast::Data::Struct(_) => { let transformed_name = if let Some(name) = name { - name.to_string() + name } else if let Some(rename) = rename { - rename.transform(&ident.to_string()) - } else if let Some(rename_all) = &rename_all { - rename_all.transform(&ident.to_string()) + rename.transform(&type_name.to_string()) } else { - ident.to_string() + type_name.to_string() }; - match fields.style { - Style::Unit => quote! { #type_name :: #ident => #transformed_name , }, - Style::Tuple => { - quote! { #type_name :: #ident (..) => #transformed_name , } - } - Style::Struct => { - quote! { #type_name :: #ident {..} => #transformed_name , } - } - } - }, - ) - .collect::>(); + quote! { #transformed_name } + } + _ => unreachable!(), + }; - Ok(quote! { - impl #imp #me::standard::nep297::EventMetadata for #type_name #ty #wher { - fn standard(&self) -> &'static str { - #standard - } + Ok(if batch.is_present() { + quote! { + impl #imp #me::standard::nep297::BatchEvent for #type_name #ty #wher { + fn standard() -> &'static str { + #standard + } - fn version(&self) -> &'static str { - #version - } + fn version() -> &'static str { + #version + } - fn event(&self) -> &'static str { - match self { - #(#arms)* + fn event() -> &'static str { + #event } } } - - impl #imp ::std::fmt::Display for #type_name #ty #wher { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - write!( - f, - "{}", - #me::standard::nep297::Event::to_event_string(self), - ) + } else { + quote! { + impl #imp #me::standard::nep297::Event<#type_name #ty> for #type_name #ty #wher { + fn event_log<'geld>(&'geld self) -> #me::standard::nep297::EventLog<&'geld Self> { + #me::standard::nep297::EventLog { + standard: #standard, + version: #version, + event: #event, + data: self, + } + } } } }) diff --git a/src/owner.rs b/src/owner.rs index 6e6bf6c..c068604 100644 --- a/src/owner.rs +++ b/src/owner.rs @@ -3,7 +3,7 @@ use near_sdk::{env, ext_contract, require, AccountId}; -use crate::{event, slot::Slot, standard::nep297::Event}; +use crate::{slot::Slot, standard::nep297::Event}; const ONLY_OWNER_FAIL_MESSAGE: &str = "Owner only"; const OWNER_INIT_FAIL_MESSAGE: &str = "Owner already initialized"; @@ -12,27 +12,36 @@ const ONLY_PROPOSED_OWNER_FAIL_MESSAGE: &str = "Proposed owner only"; const NO_PROPOSED_OWNER_FAIL_MESSAGE: &str = "No proposed owner"; /// Events emitted by function calls on an ownable contract -#[event( - standard = "x-own", - version = "1.0.0", - crate = "crate", - macros = "near_contract_tools_macros" -)] -pub enum OwnerEvent { +pub mod event { + use near_sdk::AccountId; + + use crate::event; /// Emitted when the current owner of the contract changes - Transfer { + #[event( + standard = "x-own", + version = "1.0.0", + crate = "crate", + macros = "near_contract_tools_macros" + )] + pub struct Transfer { /// Former owner of the contract. Will be `None` if the contract is being initialized. - old: Option, + pub old: Option, /// The new owner of the contract. Will be `None` if ownership is renounced. - new: Option, - }, + pub new: Option, + } /// Emitted when the proposed owner of the contract changes - Propose { + #[event( + standard = "x-own", + version = "1.0.0", + crate = "crate", + macros = "near_contract_tools_macros" + )] + pub struct Propose { /// Old proposed owner. - old: Option, + pub old: Option, /// New proposed owner. - new: Option, - }, + pub new: Option, + } } /// A contract with an owner @@ -60,7 +69,7 @@ pub trait Owner { let owner = Self::slot_owner(); let old = owner.read(); if old != new { - OwnerEvent::Transfer { + event::Transfer { old, new: new.clone(), } @@ -74,7 +83,7 @@ pub trait Owner { let proposed_owner = Self::slot_proposed_owner(); let old = proposed_owner.read(); if old != new { - OwnerEvent::Propose { + event::Propose { old, new: new.clone(), } @@ -129,7 +138,7 @@ pub trait Owner { Self::slot_is_initialized().write(&true); Self::slot_owner().write(owner_id); - OwnerEvent::Transfer { + event::Transfer { old: None, new: Some(owner_id.clone()), } @@ -207,7 +216,7 @@ pub trait Owner { ONLY_PROPOSED_OWNER_FAIL_MESSAGE, ); - OwnerEvent::Propose { + event::Propose { old: Some(proposed_owner.clone()), new: None, } diff --git a/src/pause.rs b/src/pause.rs index 2e0484d..dd73264 100644 --- a/src/pause.rs +++ b/src/pause.rs @@ -1,24 +1,33 @@ //! Contract method pausing/unpausing #![allow(missing_docs)] // #[ext_contract(...)] does not play nicely with clippy -use crate::{event, slot::Slot, standard::nep297::Event}; +use crate::{slot::Slot, standard::nep297::Event}; use near_sdk::{ext_contract, require}; const UNPAUSED_FAIL_MESSAGE: &str = "Disallowed while contract is unpaused"; const PAUSED_FAIL_MESSAGE: &str = "Disallowed while contract is paused"; /// Events emitted when contract pause state is changed -#[event( - standard = "x-paus", - version = "1.0.0", - crate = "crate", - macros = "near_contract_tools_macros" -)] -pub enum PauseEvent { +pub mod event { + use crate::event; + /// Emitted when the contract is paused - Pause, + #[event( + standard = "x-paus", + version = "1.0.0", + crate = "crate", + macros = "near_contract_tools_macros" + )] + pub struct Pause; + /// Emitted when the contract is unpaused - Unpause, + #[event( + standard = "x-paus", + version = "1.0.0", + crate = "crate", + macros = "near_contract_tools_macros" + )] + pub struct Unpause; } /// Internal-only interactions for a pausable contract @@ -79,7 +88,7 @@ pub trait Pause { fn pause(&mut self) { Self::require_unpaused(); self.set_is_paused(true); - PauseEvent::Pause.emit(); + event::Pause.emit(); } /// Unpauses the contract if it is currently paused, panics otherwise. @@ -87,7 +96,7 @@ pub trait Pause { fn unpause(&mut self) { Self::require_paused(); self.set_is_paused(false); - PauseEvent::Unpause.emit(); + event::Unpause.emit(); } /// Rejects if the contract is unpaused diff --git a/src/standard/nep141.rs b/src/standard/nep141.rs index fbbd9bd..d23dc24 100644 --- a/src/standard/nep141.rs +++ b/src/standard/nep141.rs @@ -10,7 +10,7 @@ use near_sdk::{ }; use serde::{Deserialize, Serialize}; -use crate::{event, slot::Slot, standard::nep297::Event}; +use crate::{slot::Slot, standard::nep297::*}; /// Gas value required for ft_resolve_transfer calls pub const GAS_FOR_RESOLVE_TRANSFER: Gas = Gas(5_000_000_000_000); @@ -20,49 +20,72 @@ pub const GAS_FOR_FT_TRANSFER_CALL: Gas = Gas(25_000_000_000_000 + GAS_FOR_RESOL const MORE_GAS_FAIL_MESSAGE: &str = "More gas is required"; /// NEP-141 standard events for minting, burning, and transferring tokens -#[event( - crate = "crate", - macros = "crate", - serde = "serde", - standard = "nep141", - version = "1.0.0" -)] -pub enum Nep141Event<'a> { +pub mod event { + use near_sdk::{json_types::U128, AccountId}; + + use crate::event; + /// Token mint event. Emitted when tokens are created and total_supply is /// increased. - FtMint { + #[event( + crate = "crate", + macros = "crate", + serde = "serde", + standard = "nep141", + version = "1.0.0", + batch + )] + pub struct FtMint<'a> { /// Address to which new tokens were minted - owner_id: &'a AccountId, + pub owner_id: &'a AccountId, /// Amount of minted tokens - amount: &'a U128, + pub amount: &'a U128, /// Optional note #[serde(skip_serializing_if = "Option::is_none")] - memo: Option<&'a str>, - }, + pub memo: Option<&'a str>, + } + /// Token transfer event. Emitted when tokens are transferred between two /// accounts. No change to total_supply. - FtTransfer { + #[event( + crate = "crate", + macros = "crate", + serde = "serde", + standard = "nep141", + version = "1.0.0", + batch + )] + pub struct FtTransfer<'a> { /// Account ID of the sender - old_owner_id: &'a AccountId, + pub old_owner_id: &'a AccountId, /// Account ID of the receiver - new_owner_id: &'a AccountId, + pub new_owner_id: &'a AccountId, /// Amount of transferred tokens - amount: &'a U128, + pub amount: &'a U128, /// Optional note #[serde(skip_serializing_if = "Option::is_none")] - memo: Option<&'a str>, - }, + pub memo: Option<&'a str>, + } + /// Token burn event. Emitted when tokens are burned (removed from supply). /// Decrease in total_supply. - FtBurn { + #[event( + crate = "crate", + macros = "crate", + serde = "serde", + standard = "nep141", + version = "1.0.0", + batch + )] + pub struct FtBurn<'a> { /// Account ID from which tokens were burned - owner_id: &'a AccountId, + pub owner_id: &'a AccountId, /// Amount of burned tokens - amount: &'a U128, + pub amount: &'a U128, /// Optional note #[serde(skip_serializing_if = "Option::is_none")] - memo: Option<&'a str>, - }, + pub memo: Option<&'a str>, + } } #[derive(BorshSerialize, BorshStorageKey)] @@ -233,12 +256,12 @@ pub trait Nep141Controller { ) { self.transfer_unchecked(sender_account_id, receiver_account_id, amount); - Nep141Event::FtTransfer { + [event::FtTransfer { old_owner_id: sender_account_id, new_owner_id: receiver_account_id, amount: &amount.into(), memo, - } + }] .emit(); } @@ -250,11 +273,11 @@ pub trait Nep141Controller { fn mint(&mut self, account_id: &AccountId, amount: u128, memo: Option<&str>) { self.deposit_unchecked(account_id, amount); - Nep141Event::FtMint { + [event::FtMint { owner_id: account_id, amount: &amount.into(), memo, - } + }] .emit(); } @@ -266,11 +289,11 @@ pub trait Nep141Controller { fn burn(&mut self, account_id: &AccountId, amount: u128, memo: Option<&str>) { self.withdraw_unchecked(account_id, amount); - Nep141Event::FtBurn { + [event::FtBurn { owner_id: account_id, amount: &amount.into(), memo, - } + }] .emit(); } diff --git a/src/standard/nep297.rs b/src/standard/nep297.rs index b16e91c..1ca7827 100644 --- a/src/standard/nep297.rs +++ b/src/standard/nep297.rs @@ -5,73 +5,128 @@ use near_sdk::serde::Serialize; /// Emit events according to the [NEP-297 event standard](https://nomicon.io/Standards/EventsFormat). /// /// # Examples +/// +/// ## Normal events +/// /// ``` -/// use near_contract_tools::standard::nep297::*; -/// use near_contract_tools::Nep297; -/// use serde::Serialize; +/// use near_contract_tools::event; /// -/// #[derive(Serialize)] -/// pub struct Nep171NftMintData { +/// #[event(standard = "nep171", version = "1.0.0")] +/// pub struct MintEvent { /// pub owner_id: String, /// pub token_ids: Vec, /// } /// -/// #[derive(Nep297, Serialize)] -/// #[nep297(standard = "nep171", version = "1.0.0")] -/// #[serde(untagged)] -/// pub enum Nep171 { -/// #[nep297(name = "nft_mint")] -/// NftMint(Vec), -/// } +/// let e = MintEvent { +/// owner_id: "account".to_string(), +/// token_ids: vec![ "t1".to_string(), "t2".to_string() ], +/// }; +/// +/// use near_contract_tools::standard::nep297::Event; +/// +/// e.emit(); +/// ``` +/// +/// ## Batchable events +/// +/// ``` +/// use near_contract_tools::{event, standard::nep297::Event}; +/// +/// // Note the `batch` flag +/// #[event(standard = "batch", version = "1", batch /* here */)] +/// pub struct BatchableEvent(pub &'static str); +/// +/// [BatchableEvent("one"), BatchableEvent("two")].emit(); /// ``` -pub trait Event { - /// Returns an `EVENT_JSON:{}`-formatted log string - fn to_event_string(&self) -> String; - /// Consumes the event and emits it to the NEAR blockchain - fn emit(&self); +pub trait Event { + /// Retrieves the event log before serialization + fn event_log(&self) -> EventLog<&T>; + + /// Converts the event into an NEP-297 event-formatted string + fn to_event_string(&self) -> String { + format!( + "EVENT_JSON:{}", + serde_json::to_string(&self.event_log()).unwrap_or_else(|_| near_sdk::env::abort()), + ) + } + + /// Emits the event string to the blockchain + fn emit(&self) { + near_sdk::env::log_str(&self.to_event_string()); + } } -/// Metadata for NEP-297-compliant events & variants -pub trait EventMetadata { +/// Multiple batch events can be emitted in a single log +pub trait BatchEvent { /// The name of the event standard, e.g. "nep171" - fn standard(&self) -> &'static str; + fn standard() -> &'static str; /// Version of the standard, e.g. "1.0.0" - fn version(&self) -> &'static str; + fn version() -> &'static str; /// What type of event within the event standard, e.g. "nft_mint" - fn event(&self) -> &'static str; + fn event() -> &'static str; +} + +impl> Event<[T]> for V { + fn event_log(&self) -> EventLog<&[T]> { + EventLog { + standard: T::standard(), + version: T::version(), + event: T::event(), + data: self.as_ref(), + } + } } /// NEP-297 Event Log Data /// -#[derive(Serialize, Debug)] -struct EventLogData<'a, T> { - pub standard: &'a str, - pub version: &'a str, - pub event: &'a str, - pub data: &'a T, +#[derive(Serialize, Clone, Debug)] +pub struct EventLog { + /// Name of the event standard, e.g. "nep171" + pub standard: &'static str, + /// Version of the standard, e.g. "1.0.0" + pub version: &'static str, + /// Name of the particular event, e.g. "nft_mint", "ft_transfer" + pub event: &'static str, + /// Data type of the event metadata + pub data: T, } -impl<'a, T: EventMetadata> From<&'a T> for EventLogData<'a, T> { - fn from(m: &'a T) -> Self { - Self { - standard: m.standard(), - version: m.version(), - event: m.event(), - data: m, +#[cfg(test)] +mod tests { + use near_contract_tools_macros::event; + + use crate::standard::nep297::*; + + #[test] + fn test() { + #[event( + standard = "my_evt", + version = "1.0.0", + crate = "crate", + macros = "near_contract_tools_macros", + batch + )] + struct TestEvent { + pub foo: &'static str, } - } -} -impl Event for T { - fn to_event_string(&self) -> String { - format!( - "EVENT_JSON:{}", - serde_json::to_string(&Into::>::into(self)) - .unwrap_or_else(|_| near_sdk::env::abort()), - ) - } + let x = TestEvent { foo: "bar" }; + let y = &[x]; - fn emit(&self) { - near_sdk::env::log_str(&self.to_event_string()); + fn test_emit>(m: T) { + m.emit(); + } + + test_emit(y); + + y.emit(); + + assert_eq!(TestEvent::standard(), "my_evt"); + assert_eq!(TestEvent::version(), "1.0.0"); + assert_eq!(TestEvent::event(), "test_event"); + assert_eq!( + y.to_event_string(), + r#"EVENT_JSON:{"standard":"my_evt","version":"1.0.0","event":"test_event","data":[{"foo":"bar"}]}"#, + ); } } diff --git a/tests/macros/event.rs b/tests/macros/event.rs index 8d8da82..9f14667 100644 --- a/tests/macros/event.rs +++ b/tests/macros/event.rs @@ -1,32 +1,36 @@ -use near_contract_tools::{standard::nep297::*, Nep297}; -use serde::Serialize; +use near_contract_tools::standard::nep297::Event; -#[derive(Serialize)] -pub struct Nep171NftMintData { - pub owner_id: String, - pub token_ids: Vec, -} +use crate::macros::event::test_events::Nep171NftMintData; + +mod test_events { + use near_contract_tools::Nep297; + use serde::Serialize; -#[derive(Nep297, Serialize)] -// Required fields -#[nep297(standard = "nep171", version = "1.0.0")] -// Optional. Default event name is the untransformed variant name, e.g. NftMint, AnotherEvent, CustomEvent -#[nep297(rename_all = "snake_case")] -// Variant name will not appear in the serialized output -#[serde(untagged)] -pub enum Nep171 { - NftMint(Vec), // Name will be "nft_mint" because rename_all = snake_case + #[derive(Serialize)] + pub struct Nep171NftMintData { + pub owner_id: String, + pub token_ids: Vec, + } - #[nep297(name = "sneaky_event")] - AnotherEvent, // Name will be "sneaky_event" + #[derive(Nep297, Serialize)] + // Required fields + #[nep297(standard = "nep171", version = "1.0.0")] + // Optional. Default event name is the untransformed variant name, e.g. NftMint, AnotherEvent, CustomEvent + #[nep297(rename = "snake_case")] + pub struct NftMint(pub Vec); // Name will be "nft_mint" because rename = snake_case - #[nep297(rename = "SHOUTY-KEBAB-CASE")] - CustomEvent, // Name will be "CUSTOM-EVENT" + #[derive(Nep297, Serialize)] + #[nep297(standard = "nep171", version = "1.0.0", name = "sneaky_event")] + pub struct AnotherEvent; // Name will be "sneaky_event" + + #[derive(Nep297, Serialize)] + #[nep297(standard = "nep171", version = "1.0.0", rename = "SHOUTY-KEBAB-CASE")] + pub struct CustomEvent; // Name will be "CUSTOM-EVENT" } #[test] fn derive_event() { - let e = Nep171::NftMint(vec![Nep171NftMintData { + let e = test_events::NftMint(vec![Nep171NftMintData { owner_id: "owner".to_string(), token_ids: vec!["token_1".to_string(), "token_2".to_string()], }]); @@ -36,28 +40,33 @@ fn derive_event() { r#"EVENT_JSON:{"standard":"nep171","version":"1.0.0","event":"nft_mint","data":[{"owner_id":"owner","token_ids":["token_1","token_2"]}]}"# ); - assert_eq!(Nep171::AnotherEvent.event(), "sneaky_event"); + assert_eq!(test_events::AnotherEvent.event_log().event, "sneaky_event"); - assert_eq!(Nep171::CustomEvent.event(), "CUSTOM-EVENT"); + assert_eq!(test_events::CustomEvent.event_log().event, "CUSTOM-EVENT"); } mod event_attribute_macro { - use near_contract_tools::{event, standard::nep297::Event}; + use near_contract_tools::standard::nep297::Event; + + mod my_event { + use near_contract_tools::event; - #[event(standard = "my_event_standard", version = "1")] - #[allow(unused)] - enum MyEvent { - One, - ThreePointFive { foo: &'static str }, - Six, + #[event(standard = "my_event_standard", version = "1")] + pub struct One; + #[event(standard = "my_event_standard", version = "1")] + pub struct ThreePointFive { + pub foo: &'static str, + } + #[event(standard = "my_event_standard", version = "1")] + pub struct Six; } #[test] fn test() { - let e = MyEvent::ThreePointFive { foo: "hello" }; + let e = my_event::ThreePointFive { foo: "hello" }; e.emit(); assert_eq!( - e.to_string(), + e.to_event_string(), r#"EVENT_JSON:{"standard":"my_event_standard","version":"1","event":"three_point_five","data":{"foo":"hello"}}"#, ); } diff --git a/tests/macros/mod.rs b/tests/macros/mod.rs index 99991a4..c3254e3 100644 --- a/tests/macros/mod.rs +++ b/tests/macros/mod.rs @@ -4,7 +4,7 @@ use near_contract_tools::{ pause::Pause, rbac::Rbac, standard::nep297::Event, - Migrate, Nep297, Owner, Pause, Rbac, + Migrate, Owner, Pause, Rbac, }; use near_sdk::{ borsh::{self, BorshDeserialize, BorshSerialize}, @@ -12,7 +12,6 @@ use near_sdk::{ test_utils::VMContextBuilder, testing_env, AccountId, BorshStorageKey, }; -use serde::Serialize; mod event; mod migrate; @@ -20,12 +19,23 @@ mod owner; mod pause; mod standard; -#[derive(Serialize, Nep297)] -#[nep297(standard = "x-myevent", version = "1.0.0", rename_all = "snake_case")] -#[serde(untagged)] -enum MyEvent { - ValueChanged { from: u32, to: u32 }, - PermissionGranted { to: AccountId }, +mod my_event { + use near_contract_tools::Nep297; + use near_sdk::AccountId; + use serde::Serialize; + + #[derive(Serialize, Nep297)] + #[nep297(standard = "x-myevent", version = "1.0.0", rename = "snake_case")] + pub struct ValueChanged { + pub from: u32, + pub to: u32, + } + + #[derive(Serialize, Nep297)] + #[nep297(standard = "x-myevent", version = "1.0.0", rename = "snake_case")] + pub struct PermissionGranted { + pub to: AccountId, + } } #[derive(BorshSerialize, BorshStorageKey)] @@ -68,7 +78,7 @@ impl Integration { self.add_role(&account_id, &Role::CanSetValue); - MyEvent::PermissionGranted { to: account_id }.emit(); + my_event::PermissionGranted { to: account_id }.emit(); } pub fn set_value(&mut self, value: u32) { @@ -79,7 +89,7 @@ impl Integration { self.value = value; - MyEvent::ValueChanged { + my_event::ValueChanged { from: old, to: value, } @@ -131,7 +141,7 @@ impl MigrateIntegration { self.add_role(&account_id, &Role::CanSetValue); - MyEvent::PermissionGranted { to: account_id }.emit(); + my_event::PermissionGranted { to: account_id }.emit(); } pub fn set_value(&mut self, value: u32) { @@ -142,7 +152,7 @@ impl MigrateIntegration { self.moved_value = value; - MyEvent::ValueChanged { + my_event::ValueChanged { from: old, to: value, } @@ -345,7 +355,7 @@ fn integration_fail_migrate_paused() { mod pausable_fungible_token { use near_contract_tools::{ pause::Pause, - standard::nep141::{Nep141Hook, Nep141Transfer}, + standard::nep141::{Nep141, Nep141Controller, Nep141Hook, Nep141Transfer}, FungibleToken, Pause, }; use near_sdk::{ diff --git a/tests/macros/standard/fungible_token.rs b/tests/macros/standard/fungible_token.rs index 4e6005e..7180213 100644 --- a/tests/macros/standard/fungible_token.rs +++ b/tests/macros/standard/fungible_token.rs @@ -1,4 +1,7 @@ -use near_contract_tools::FungibleToken; +use near_contract_tools::{ + standard::nep141::{Nep141, Nep141Controller}, + FungibleToken, +}; use near_sdk::{ json_types::Base64VecU8, near_bindgen, test_utils::VMContextBuilder, testing_env, AccountId, }; diff --git a/workspaces-tests/src/bin/fungible_token.rs b/workspaces-tests/src/bin/fungible_token.rs index afbe808..f7757f4 100644 --- a/workspaces-tests/src/bin/fungible_token.rs +++ b/workspaces-tests/src/bin/fungible_token.rs @@ -3,7 +3,7 @@ // Ignore pub fn main() {} -use near_contract_tools::FungibleToken; +use near_contract_tools::{standard::nep141::Nep141Controller, FungibleToken}; use near_sdk::{ borsh::{self, BorshDeserialize, BorshSerialize}, env, From 2409c59e2bcfee9b20f52051c0c8e5c67f074d8d Mon Sep 17 00:00:00 2001 From: Jacob Date: Thu, 22 Sep 2022 03:52:02 +0900 Subject: [PATCH 2/4] fix: correct imports for ft workspaces contract --- workspaces-tests/src/bin/fungible_token.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workspaces-tests/src/bin/fungible_token.rs b/workspaces-tests/src/bin/fungible_token.rs index f7757f4..60053fc 100644 --- a/workspaces-tests/src/bin/fungible_token.rs +++ b/workspaces-tests/src/bin/fungible_token.rs @@ -3,7 +3,7 @@ // Ignore pub fn main() {} -use near_contract_tools::{standard::nep141::Nep141Controller, FungibleToken}; +use near_contract_tools::{standard::nep141::*, FungibleToken}; use near_sdk::{ borsh::{self, BorshDeserialize, BorshSerialize}, env, From 918005f7ae3f645055f3a23982f8e5fc36ac15aa Mon Sep 17 00:00:00 2001 From: Jacob Date: Thu, 22 Sep 2022 04:20:52 +0900 Subject: [PATCH 3/4] chore: move trait bounds closer to required location --- src/standard/nep297.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/standard/nep297.rs b/src/standard/nep297.rs index 1ca7827..de9ba0d 100644 --- a/src/standard/nep297.rs +++ b/src/standard/nep297.rs @@ -38,12 +38,15 @@ use near_sdk::serde::Serialize; /// /// [BatchableEvent("one"), BatchableEvent("two")].emit(); /// ``` -pub trait Event { +pub trait Event { /// Retrieves the event log before serialization fn event_log(&self) -> EventLog<&T>; /// Converts the event into an NEP-297 event-formatted string - fn to_event_string(&self) -> String { + fn to_event_string(&self) -> String + where + T: Serialize, + { format!( "EVENT_JSON:{}", serde_json::to_string(&self.event_log()).unwrap_or_else(|_| near_sdk::env::abort()), @@ -51,7 +54,10 @@ pub trait Event { } /// Emits the event string to the blockchain - fn emit(&self) { + fn emit(&self) + where + T: Serialize, + { near_sdk::env::log_str(&self.to_event_string()); } } From f8d692fe665e24b51e93359c731037ab119f9b0d Mon Sep 17 00:00:00 2001 From: Jacob Date: Thu, 22 Sep 2022 14:58:15 +0900 Subject: [PATCH 4/4] chore: remove batch flag in favor of better-composed event types --- README.md | 9 +-- macros/src/lib.rs | 8 +-- macros/src/standard/event.rs | 8 +-- macros/src/standard/nep297.rs | 39 +++---------- src/standard/nep141.rs | 102 +++++++++++++++++++++++++++------- src/standard/nep297.rs | 79 +------------------------- 6 files changed, 99 insertions(+), 146 deletions(-) diff --git a/README.md b/README.md index 985ceca..b406cb8 100644 --- a/README.md +++ b/README.md @@ -124,18 +124,15 @@ fn own_accept_owner(&mut self); ```rust use near_contract_tools::{event, standard::nep297::Event}; -#[event(standard = "nep171", version = "1.0.0")] +#[event(standard = "nft", version = "1.0.0")] pub struct MintEvent { pub owner_id: String, - pub token_ids: Vec, + pub token_id: String, } let e = MintEvent { owner_id: "account".to_string(), - token_ids: vec![ - "t1".to_string(), - "t2".to_string(), - ], + token_id: "token_1".to_string(), }; // Emits the event to the blockchain diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 3e71076..7922d3f 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -47,13 +47,7 @@ where /// /// Specify event standard parameters: `#[nep297(standard = "...", version = "...")]` /// -/// Optional: `#[nep297(name = "..."), batch]` -/// -/// The `batch` flag means that events will always be emitted as a homogenous array: -/// -/// ```ignore -/// [MyBatchableEvent("one"), MyBatchableEvent("two")].emit(); -/// ``` +/// Optional: `#[nep297(name = "...")]` /// /// Rename strategy for all variants (default: unchanged): `#[event(rename = "")]` /// Options for ``: diff --git a/macros/src/standard/event.rs b/macros/src/standard/event.rs index b67ce0b..bbdaa2d 100644 --- a/macros/src/standard/event.rs +++ b/macros/src/standard/event.rs @@ -1,4 +1,4 @@ -use darling::{util::Flag, FromMeta}; +use darling::FromMeta; use proc_macro2::TokenStream; use quote::quote; use syn::Item; @@ -11,7 +11,6 @@ pub struct EventAttributeMeta { pub version: String, pub rename: Option, pub name: Option, - pub batch: Flag, #[darling(rename = "crate", default = "crate::default_crate_name")] pub me: syn::Path, @@ -30,7 +29,6 @@ pub fn event_attribute( version, rename, name, - batch, serde, me, macros, @@ -42,11 +40,9 @@ pub fn event_attribute( let serde_str = quote! { #serde }.to_string(); let me_str = quote! { #me }.to_string(); - let batch = batch.is_present().then_some(quote! {, batch}); - Ok(quote::quote! { #[derive(#macros::Nep297, #serde::Serialize)] - #[nep297(standard = #standard, version = #version, rename = #rename, crate = #me_str #name #batch)] + #[nep297(standard = #standard, version = #version, rename = #rename, crate = #me_str #name)] #[serde(crate = #serde_str)] #item }) diff --git a/macros/src/standard/nep297.rs b/macros/src/standard/nep297.rs index 2db102e..a29ba70 100644 --- a/macros/src/standard/nep297.rs +++ b/macros/src/standard/nep297.rs @@ -1,4 +1,4 @@ -use darling::{util::Flag, FromDeriveInput, FromVariant}; +use darling::{FromDeriveInput, FromVariant}; use proc_macro2::TokenStream; use quote::quote; @@ -11,8 +11,6 @@ pub struct Nep297Meta { pub version: String, pub name: Option, pub rename: Option, - pub batch: Flag, - pub ident: syn::Ident, pub generics: syn::Generics, pub data: darling::ast::Data, @@ -37,7 +35,6 @@ pub fn expand(meta: Nep297Meta) -> Result { version, name, rename, - batch, ident: type_name, generics, data, @@ -62,32 +59,14 @@ pub fn expand(meta: Nep297Meta) -> Result { _ => unreachable!(), }; - Ok(if batch.is_present() { - quote! { - impl #imp #me::standard::nep297::BatchEvent for #type_name #ty #wher { - fn standard() -> &'static str { - #standard - } - - fn version() -> &'static str { - #version - } - - fn event() -> &'static str { - #event - } - } - } - } else { - quote! { - impl #imp #me::standard::nep297::Event<#type_name #ty> for #type_name #ty #wher { - fn event_log<'geld>(&'geld self) -> #me::standard::nep297::EventLog<&'geld Self> { - #me::standard::nep297::EventLog { - standard: #standard, - version: #version, - event: #event, - data: self, - } + Ok(quote! { + impl #imp #me::standard::nep297::Event<#type_name #ty> for #type_name #ty #wher { + fn event_log<'geld>(&'geld self) -> #me::standard::nep297::EventLog<&'geld Self> { + #me::standard::nep297::EventLog { + standard: #standard, + version: #version, + event: #event, + data: self, } } } diff --git a/src/standard/nep141.rs b/src/standard/nep141.rs index d23dc24..a6e0284 100644 --- a/src/standard/nep141.rs +++ b/src/standard/nep141.rs @@ -22,6 +22,7 @@ const MORE_GAS_FAIL_MESSAGE: &str = "More gas is required"; /// NEP-141 standard events for minting, burning, and transferring tokens pub mod event { use near_sdk::{json_types::U128, AccountId}; + use serde::Serialize; use crate::event; @@ -32,14 +33,16 @@ pub mod event { macros = "crate", serde = "serde", standard = "nep141", - version = "1.0.0", - batch + version = "1.0.0" )] - pub struct FtMint<'a> { + pub struct FtMint<'a>(pub &'a [FtMintData<'a>]); + + #[derive(Serialize, Debug, Clone)] + pub struct FtMintData<'a> { /// Address to which new tokens were minted pub owner_id: &'a AccountId, /// Amount of minted tokens - pub amount: &'a U128, + pub amount: U128, /// Optional note #[serde(skip_serializing_if = "Option::is_none")] pub memo: Option<&'a str>, @@ -52,16 +55,18 @@ pub mod event { macros = "crate", serde = "serde", standard = "nep141", - version = "1.0.0", - batch + version = "1.0.0" )] - pub struct FtTransfer<'a> { + pub struct FtTransfer<'a>(pub &'a [FtTransferData<'a>]); + + #[derive(Serialize, Debug, Clone)] + pub struct FtTransferData<'a> { /// Account ID of the sender pub old_owner_id: &'a AccountId, /// Account ID of the receiver pub new_owner_id: &'a AccountId, /// Amount of transferred tokens - pub amount: &'a U128, + pub amount: U128, /// Optional note #[serde(skip_serializing_if = "Option::is_none")] pub memo: Option<&'a str>, @@ -74,18 +79,73 @@ pub mod event { macros = "crate", serde = "serde", standard = "nep141", - version = "1.0.0", - batch + version = "1.0.0" )] - pub struct FtBurn<'a> { + pub struct FtBurn<'a>(pub &'a [FtBurnData<'a>]); + + #[derive(Serialize, Debug, Clone)] + pub struct FtBurnData<'a> { /// Account ID from which tokens were burned pub owner_id: &'a AccountId, /// Amount of burned tokens - pub amount: &'a U128, + pub amount: U128, /// Optional note #[serde(skip_serializing_if = "Option::is_none")] pub memo: Option<&'a str>, } + + #[cfg(test)] + mod tests { + use super::{super::Event, *}; + + #[test] + fn mint() { + assert_eq!( + FtMint(&[FtMintData { + owner_id: &"foundation.near".parse().unwrap(), + amount: 500u128.into(), + memo: None, + }]) + .to_event_string(), + r#"EVENT_JSON:{"standard":"nep141","version":"1.0.0","event":"ft_mint","data":[{"owner_id":"foundation.near","amount":"500"}]}"#, + ); + } + + #[test] + fn transfer() { + assert_eq!( + FtTransfer(&[ + FtTransferData { + old_owner_id: &"from.near".parse().unwrap(), + new_owner_id: &"to.near".parse().unwrap(), + amount: 42u128.into(), + memo: Some("hi hello bonjour"), + }, + FtTransferData { + old_owner_id: &"user1.near".parse().unwrap(), + new_owner_id: &"user2.near".parse().unwrap(), + amount: 7500u128.into(), + memo: None + }, + ]) + .to_event_string(), + r#"EVENT_JSON:{"standard":"nep141","version":"1.0.0","event":"ft_transfer","data":[{"old_owner_id":"from.near","new_owner_id":"to.near","amount":"42","memo":"hi hello bonjour"},{"old_owner_id":"user1.near","new_owner_id":"user2.near","amount":"7500"}]}"#, + ); + } + + #[test] + fn burn() { + assert_eq!( + FtBurn(&[FtBurnData { + owner_id: &"foundation.near".parse().unwrap(), + amount: 100u128.into(), + memo: None, + }]) + .to_event_string(), + r#"EVENT_JSON:{"standard":"nep141","version":"1.0.0","event":"ft_burn","data":[{"owner_id":"foundation.near","amount":"100"}]}"#, + ); + } + } } #[derive(BorshSerialize, BorshStorageKey)] @@ -256,12 +316,12 @@ pub trait Nep141Controller { ) { self.transfer_unchecked(sender_account_id, receiver_account_id, amount); - [event::FtTransfer { + event::FtTransfer(&[event::FtTransferData { old_owner_id: sender_account_id, new_owner_id: receiver_account_id, - amount: &amount.into(), + amount: amount.into(), memo, - }] + }]) .emit(); } @@ -273,11 +333,11 @@ pub trait Nep141Controller { fn mint(&mut self, account_id: &AccountId, amount: u128, memo: Option<&str>) { self.deposit_unchecked(account_id, amount); - [event::FtMint { + event::FtMint(&[event::FtMintData { owner_id: account_id, - amount: &amount.into(), + amount: amount.into(), memo, - }] + }]) .emit(); } @@ -289,11 +349,11 @@ pub trait Nep141Controller { fn burn(&mut self, account_id: &AccountId, amount: u128, memo: Option<&str>) { self.withdraw_unchecked(account_id, amount); - [event::FtBurn { + event::FtBurn(&[event::FtBurnData { owner_id: account_id, - amount: &amount.into(), + amount: amount.into(), memo, - }] + }]) .emit(); } diff --git a/src/standard/nep297.rs b/src/standard/nep297.rs index de9ba0d..202c575 100644 --- a/src/standard/nep297.rs +++ b/src/standard/nep297.rs @@ -11,33 +11,21 @@ use near_sdk::serde::Serialize; /// ``` /// use near_contract_tools::event; /// -/// #[event(standard = "nep171", version = "1.0.0")] +/// #[event(standard = "nft", version = "1.0.0")] /// pub struct MintEvent { /// pub owner_id: String, -/// pub token_ids: Vec, +/// pub token_id: String, /// } /// /// let e = MintEvent { /// owner_id: "account".to_string(), -/// token_ids: vec![ "t1".to_string(), "t2".to_string() ], +/// token_id: "token_1".to_string(), /// }; /// /// use near_contract_tools::standard::nep297::Event; /// /// e.emit(); /// ``` -/// -/// ## Batchable events -/// -/// ``` -/// use near_contract_tools::{event, standard::nep297::Event}; -/// -/// // Note the `batch` flag -/// #[event(standard = "batch", version = "1", batch /* here */)] -/// pub struct BatchableEvent(pub &'static str); -/// -/// [BatchableEvent("one"), BatchableEvent("two")].emit(); -/// ``` pub trait Event { /// Retrieves the event log before serialization fn event_log(&self) -> EventLog<&T>; @@ -62,27 +50,6 @@ pub trait Event { } } -/// Multiple batch events can be emitted in a single log -pub trait BatchEvent { - /// The name of the event standard, e.g. "nep171" - fn standard() -> &'static str; - /// Version of the standard, e.g. "1.0.0" - fn version() -> &'static str; - /// What type of event within the event standard, e.g. "nft_mint" - fn event() -> &'static str; -} - -impl> Event<[T]> for V { - fn event_log(&self) -> EventLog<&[T]> { - EventLog { - standard: T::standard(), - version: T::version(), - event: T::event(), - data: self.as_ref(), - } - } -} - /// NEP-297 Event Log Data /// #[derive(Serialize, Clone, Debug)] @@ -96,43 +63,3 @@ pub struct EventLog { /// Data type of the event metadata pub data: T, } - -#[cfg(test)] -mod tests { - use near_contract_tools_macros::event; - - use crate::standard::nep297::*; - - #[test] - fn test() { - #[event( - standard = "my_evt", - version = "1.0.0", - crate = "crate", - macros = "near_contract_tools_macros", - batch - )] - struct TestEvent { - pub foo: &'static str, - } - - let x = TestEvent { foo: "bar" }; - let y = &[x]; - - fn test_emit>(m: T) { - m.emit(); - } - - test_emit(y); - - y.emit(); - - assert_eq!(TestEvent::standard(), "my_evt"); - assert_eq!(TestEvent::version(), "1.0.0"); - assert_eq!(TestEvent::event(), "test_event"); - assert_eq!( - y.to_event_string(), - r#"EVENT_JSON:{"standard":"my_evt","version":"1.0.0","event":"test_event","data":[{"foo":"bar"}]}"#, - ); - } -}