Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BREAKING: Refactor Fungible Token #128

Merged
merged 20 commits into from
Oct 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@ use near_sdk_contract_tools::ft::*;
use near_sdk::near_bindgen;

#[derive(FungibleToken)]
#[fungible_token(no_hooks)]
#[near_bindgen]
struct FungibleToken {}

Expand Down Expand Up @@ -149,22 +148,23 @@ pub struct MyNft {}
One may wish to combine the features of multiple macros in one contract. All of the macros are written such that they will work in a standalone manner, so this should largely work without issue. However, sometimes it may be desirable for the macros to work in _combination_ with each other. For example, to make a fungible token pausable, use the fungible token hooks to require that a contract be unpaused before making a token transfer:

```rust
use near_sdk_contract_tools::{ft::*, pause::Pause, Pause};
use near_sdk::near_bindgen;
use near_sdk_contract_tools::{
ft::*,
pause::{*, hooks::PausableHook},
Pause,
};
use near_sdk::{
borsh::{self, BorshSerialize, BorshDeserialize},
PanicOnDefault,
near_bindgen,
};

#[derive(FungibleToken, Pause)]
#[derive(BorshSerialize, BorshDeserialize, PanicOnDefault, FungibleToken, Pause)]
#[fungible_token(all_hooks = "PausableHook")]
#[near_bindgen]
struct Contract {}

impl SimpleNep141Hook for Contract {
fn before_transfer(&self, _transfer: &Nep141Transfer) {
Contract::require_unpaused();
}
}
```

Note: Hooks can be disabled using `#[nep141(no_hooks)]` or `#[fungible_token(no_hooks)]`.

### Custom Crates

If you are a library developer, have modified a crate that one of the `near-sdk-contract-tools` macros uses (like `serde` or `near-sdk`), or are otherwise using a crate under a different name, you can specify crate names in macros like so:
Expand Down
53 changes: 46 additions & 7 deletions macros/src/standard/fungible_token.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
use darling::{util::Flag, FromDeriveInput};
use darling::FromDeriveInput;
use proc_macro2::TokenStream;
use quote::quote;
use syn::Expr;
use syn::{Expr, Type};

use super::{nep141, nep148};
use super::{nep141, nep145, nep148};

#[derive(Debug, FromDeriveInput)]
#[darling(attributes(fungible_token), supports(struct_named))]
pub struct FungibleTokenMeta {
// NEP-141 fields
pub core_storage_key: Option<Expr>,
pub no_hooks: Flag,
pub all_hooks: Option<Type>,
pub mint_hook: Option<Type>,
pub transfer_hook: Option<Type>,
pub burn_hook: Option<Type>,

// NEP-148 fields
pub metadata_storage_key: Option<Expr>,

// NEP-145 fields
pub storage_management_storage_key: Option<Expr>,
pub force_unregister_hook: Option<Type>,

// darling
pub generics: syn::Generics,
pub ident: syn::Ident,
Expand All @@ -29,8 +36,15 @@ pub struct FungibleTokenMeta {
pub fn expand(meta: FungibleTokenMeta) -> Result<TokenStream, darling::Error> {
let FungibleTokenMeta {
core_storage_key,
all_hooks,
mint_hook,
transfer_hook,
burn_hook,

metadata_storage_key,
no_hooks,

storage_management_storage_key,
force_unregister_hook,

generics,
ident,
Expand All @@ -39,11 +53,34 @@ pub fn expand(meta: FungibleTokenMeta) -> Result<TokenStream, darling::Error> {
near_sdk,
} = meta;

let all_hooks_or_unit = all_hooks
.clone()
.unwrap_or_else(|| syn::parse_quote! { () });
let force_unregister_hook_or_unit =
force_unregister_hook.unwrap_or_else(|| syn::parse_quote! { () });

let expand_nep141 = nep141::expand(nep141::Nep141Meta {
storage_key: core_storage_key,
no_hooks,
extension_hooks: None,
all_hooks: Some(
syn::parse_quote! { (#all_hooks_or_unit, #me::standard::nep145::hooks::PredecessorStorageAccountingHook) },
),
mint_hook,
transfer_hook,
burn_hook,

generics: generics.clone(),
ident: ident.clone(),

me: me.clone(),
near_sdk: near_sdk.clone(),
});

let expand_nep145 = nep145::expand(nep145::Nep145Meta {
storage_key: storage_management_storage_key,
all_hooks,
force_unregister_hook: Some(
syn::parse_quote! { (#force_unregister_hook_or_unit, #me::standard::nep141::hooks::BurnOnForceUnregisterHook) },
),
generics: generics.clone(),
ident: ident.clone(),

Expand All @@ -63,10 +100,12 @@ pub fn expand(meta: FungibleTokenMeta) -> Result<TokenStream, darling::Error> {
let mut e = darling::Error::accumulator();

let nep141 = e.handle(expand_nep141);
let nep145 = e.handle(expand_nep145);
let nep148 = e.handle(expand_nep148);

e.finish_with(quote! {
#nep141
#nep145
#nep148
})
}
43 changes: 26 additions & 17 deletions macros/src/standard/nep141.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
use darling::{util::Flag, FromDeriveInput};
use darling::FromDeriveInput;
use proc_macro2::TokenStream;
use quote::quote;
use syn::Expr;
use syn::{Expr, Type};

#[derive(Debug, FromDeriveInput)]
#[darling(attributes(nep141), supports(struct_named))]
pub struct Nep141Meta {
pub storage_key: Option<Expr>,
pub no_hooks: Flag,
pub extension_hooks: Option<syn::Type>,
pub all_hooks: Option<Type>,
pub mint_hook: Option<Type>,
pub transfer_hook: Option<Type>,
pub burn_hook: Option<Type>,
pub generics: syn::Generics,
pub ident: syn::Ident,

Expand All @@ -22,8 +24,10 @@ pub struct Nep141Meta {
pub fn expand(meta: Nep141Meta) -> Result<TokenStream, darling::Error> {
let Nep141Meta {
storage_key,
no_hooks,
extension_hooks,
all_hooks,
mint_hook,
transfer_hook,
burn_hook,
generics,
ident,

Expand All @@ -41,21 +45,26 @@ pub fn expand(meta: Nep141Meta) -> Result<TokenStream, darling::Error> {
}
});

let self_hook = if no_hooks.is_present() {
quote! { () }
} else {
quote! { Self }
};
let mint_hook = mint_hook
.map(|h| quote! { #h })
.unwrap_or_else(|| quote! { () });
let transfer_hook = transfer_hook
.map(|h| quote! { #h })
.unwrap_or_else(|| quote! { () });
let burn_hook = burn_hook
.map(|h| quote! { #h })
.unwrap_or_else(|| quote! { () });

let hook = if let Some(extension_hooks) = extension_hooks {
quote! { (#self_hook, #extension_hooks) }
} else {
self_hook
};
let default_hook = all_hooks
.map(|h| quote! { #h })
.unwrap_or_else(|| quote! { () });

Ok(quote! {
impl #imp #me::standard::nep141::Nep141ControllerInternal for #ident #ty #wher {
type Hook = #hook;
type MintHook = (#mint_hook, #default_hook);
type TransferHook = (#transfer_hook, #default_hook);
type BurnHook = (#burn_hook, #default_hook);

#root
}

Expand Down
26 changes: 15 additions & 11 deletions macros/src/standard/nep145.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use darling::{util::Flag, FromDeriveInput};
use darling::FromDeriveInput;
use proc_macro2::TokenStream;
use quote::quote;
use syn::Expr;
use syn::{Expr, Type};

#[derive(Debug, FromDeriveInput)]
#[darling(attributes(nep145), supports(struct_named))]
pub struct Nep145Meta {
pub storage_key: Option<Expr>,
pub no_hooks: Flag,
pub all_hooks: Option<Type>,
pub force_unregister_hook: Option<Type>,
pub generics: syn::Generics,
pub ident: syn::Ident,

Expand All @@ -21,7 +22,8 @@ pub struct Nep145Meta {
pub fn expand(meta: Nep145Meta) -> Result<TokenStream, darling::Error> {
let Nep145Meta {
storage_key,
no_hooks,
all_hooks,
force_unregister_hook,
generics,
ident,

Expand All @@ -39,14 +41,16 @@ pub fn expand(meta: Nep145Meta) -> Result<TokenStream, darling::Error> {
}
});

let hook = no_hooks
.is_present()
.then(|| quote! { () })
.unwrap_or_else(|| quote! { Self });
let all_hooks = all_hooks
.map(|h| quote! { #h })
.unwrap_or_else(|| quote! { () });
let force_unregister_hook = force_unregister_hook
.map(|h| quote! { #h })
.unwrap_or_else(|| quote! { () });

Ok(quote! {
impl #imp #me::standard::nep145::Nep145ControllerInternal for #ident #ty #wher {
type Hook = #hook;
type ForceUnregisterHook = (#force_unregister_hook, #all_hooks);

#root
}
Expand Down Expand Up @@ -104,7 +108,7 @@ pub fn expand(meta: Nep145Meta) -> Result<TokenStream, darling::Error> {
let predecessor = env::predecessor_account_id();

let balance = Nep145Controller::get_storage_balance(self, &predecessor)
.unwrap_or_else(|| env::panic_str("Account is not registered"));
.unwrap_or_else(|e| env::panic_str(&e.to_string()));

let amount = amount.unwrap_or(balance.available);

Expand Down Expand Up @@ -150,7 +154,7 @@ pub fn expand(meta: Nep145Meta) -> Result<TokenStream, darling::Error> {
}

fn storage_balance_of(&self, account_id: #near_sdk::AccountId) -> Option<#me::standard::nep145::StorageBalance> {
#me::standard::nep145::Nep145Controller::get_storage_balance(self, &account_id)
#me::standard::nep145::Nep145Controller::get_storage_balance(self, &account_id).ok()
}

fn storage_balance_bounds(&self) -> #me::standard::nep145::StorageBalanceBounds {
Expand Down
61 changes: 61 additions & 0 deletions src/hook.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//! # Hooks
//!
//! Hooks are a way to inject code before and after contract functions.
//!
//! Most of the time, hooks are used to implement cross-cutting concerns, such as
//! logging, accounting, or integration with other standards.
//!
//! ## Example
//!
//! ```
//! use near_sdk::{log, near_bindgen};
//! use near_sdk_contract_tools::{hook::Hook, standard::nep141::*, Nep141};
//!
//! pub struct MyTransferHook;
//!
//! impl Hook<MyContract, Nep141Transfer> for MyTransferHook {
//! fn before(contract: &MyContract, transfer: &Nep141Transfer) -> Self {
//! // Perform some sort of check before the transfer, e.g.:
//! // contract.require_registration(&transfer.receiver_id);
//! Self
//! }
//!
//! fn after(_contract: &mut MyContract, transfer: &Nep141Transfer, _: Self) {
//! log!("NEP-141 transfer from {} to {} of {} tokens", transfer.sender_id, transfer.receiver_id, transfer.amount);
//! }
//! }
//!
//! #[derive(Nep141)]
//! #[nep141(transfer_hook = "MyTransferHook")]
//! #[near_bindgen]
//! struct MyContract {}
//! ```

/// Generic hook trait for injecting code before and after component functions.
pub trait Hook<C, A = ()> {
/// Before hook. Returns state to be passed to after hook.
fn before(contract: &C, args: &A) -> Self;

/// After hook. Receives state from before hook.
fn after(contract: &mut C, args: &A, state: Self);
}

impl<C, A> Hook<C, A> for () {
fn before(_contract: &C, _args: &A) {}
fn after(_contract: &mut C, _args: &A, _: ()) {}
}

impl<C, A, T, U> Hook<C, A> for (T, U)
where
T: Hook<C, A>,
U: Hook<C, A>,
{
fn before(contract: &C, args: &A) -> Self {
(T::before(contract, args), U::before(contract, args))
}

fn after(contract: &mut C, args: &A, (t_state, u_state): Self) {
T::after(contract, args, t_state);
U::after(contract, args, u_state);
}
}
Loading