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

Integrate NEP-145 with token standards #132

Merged
merged 4 commits into from
Oct 26, 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
2 changes: 2 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ jobs:
- uses: Swatinem/rust-cache@v2
- name: Run unit and integration tests
run: cargo nextest run --workspace --exclude workspaces-tests
- name: Run doctests
run: cargo test --doc
workspaces-test:
runs-on: ubuntu-latest
env:
Expand Down
4 changes: 2 additions & 2 deletions macros/src/standard/fungible_token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ pub fn expand(meta: FungibleTokenMeta) -> Result<TokenStream, darling::Error> {
let expand_nep141 = nep141::expand(nep141::Nep141Meta {
storage_key: core_storage_key,
all_hooks: Some(
syn::parse_quote! { (#all_hooks_or_unit, #me::standard::nep145::hooks::PredecessorStorageAccountingHook) },
syn::parse_quote! { (#all_hooks_or_unit, #me::standard::nep145::hooks::Nep141StorageAccountingHook) },
),
mint_hook,
transfer_hook,
Expand All @@ -79,7 +79,7 @@ pub fn expand(meta: FungibleTokenMeta) -> Result<TokenStream, darling::Error> {
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) },
syn::parse_quote! { (#force_unregister_hook_or_unit, #me::standard::nep141::hooks::BurnNep141OnForceUnregisterHook) },
),
generics: generics.clone(),
ident: ident.clone(),
Expand Down
41 changes: 35 additions & 6 deletions macros/src/standard/non_fungible_token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@ use syn::{parse_quote, Expr, Type};

use crate::unitify;

use super::{nep171, nep177, nep178, nep181};
use super::{nep145, nep171, nep177, nep178, nep181};

#[derive(Debug, FromDeriveInput)]
#[darling(attributes(non_fungible_token), supports(struct_named))]
pub struct NonFungibleTokenMeta {
pub all_hooks: Option<Type>,

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

// NEP-171 fields
pub core_storage_key: Option<Expr>,
pub mint_hook: Option<Type>,
Expand Down Expand Up @@ -45,7 +49,10 @@ pub fn expand(meta: NonFungibleTokenMeta) -> Result<TokenStream, darling::Error>
let NonFungibleTokenMeta {
all_hooks,

core_storage_key: storage_key,
storage_management_storage_key,
force_unregister_hook,

core_storage_key,
mint_hook,
transfer_hook,
burn_hook,
Expand All @@ -67,12 +74,32 @@ pub fn expand(meta: NonFungibleTokenMeta) -> Result<TokenStream, darling::Error>
} = meta;

let all_hooks_inner = unitify(all_hooks.clone());
let force_unregister_hook = unitify(force_unregister_hook);

let expand_nep171 = nep171::expand(nep171::Nep171Meta {
storage_key,
all_hooks: Some(
parse_quote! { (#all_hooks_inner, (#me::standard::nep178::TokenApprovals, #me::standard::nep181::TokenEnumeration)) },
let expand_nep145 = nep145::expand(nep145::Nep145Meta {
storage_key: storage_management_storage_key,
all_hooks: Some(all_hooks_inner.clone()),
force_unregister_hook: Some(
parse_quote! { (#force_unregister_hook, #me::standard::nep171::hooks::BurnNep171OnForceUnregisterHook) },
),
generics: generics.clone(),
ident: ident.clone(),
me: me.clone(),
near_sdk: near_sdk.clone(),
});

let expand_nep171 = nep171::expand(nep171::Nep171Meta {
storage_key: core_storage_key,
all_hooks: Some(parse_quote! { (
#all_hooks_inner,
(
#me::standard::nep145::hooks::Nep171StorageAccountingHook,
(
#me::standard::nep178::TokenApprovals,
#me::standard::nep181::TokenEnumeration,
),
),
) }),
mint_hook,
transfer_hook,
burn_hook,
Expand Down Expand Up @@ -122,12 +149,14 @@ pub fn expand(meta: NonFungibleTokenMeta) -> Result<TokenStream, darling::Error>

let mut e = darling::Error::accumulator();

let nep145 = e.handle(expand_nep145);
let nep171 = e.handle(expand_nep171);
let nep177 = e.handle(expand_nep177);
let nep178 = e.handle(expand_nep178);
let nep181 = e.handle(expand_nep181);

e.finish_with(quote! {
#nep145
#nep171
#nep177
#nep178
Expand Down
6 changes: 3 additions & 3 deletions src/standard/nep141/hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ use crate::{hook::Hook, standard::nep145::Nep145ForceUnregister};
use super::{Nep141Burn, Nep141Controller, Nep141ControllerInternal};

/// Hook that burns all tokens on NEP-145 force unregister.
pub struct BurnOnForceUnregisterHook;
pub struct BurnNep141OnForceUnregisterHook;

impl<C: Nep141Controller + Nep141ControllerInternal> Hook<C, Nep145ForceUnregister<'_>>
for BurnOnForceUnregisterHook
for BurnNep141OnForceUnregisterHook
{
fn hook<R>(
contract: &mut C,
Expand All @@ -21,7 +21,7 @@ impl<C: Nep141Controller + Nep141ControllerInternal> Hook<C, Nep145ForceUnregist
contract
.burn(&Nep141Burn {
amount: balance,
account_id: args.account_id,
owner_id: args.account_id,
memo: Some("storage forced unregistration"),
})
.unwrap_or_else(|e| {
Expand Down
46 changes: 16 additions & 30 deletions src/standard/nep141/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ pub struct Nep141Mint<'a> {
/// Amount to mint.
pub amount: u128,
/// Account ID to mint to.
pub account_id: &'a AccountId,
pub receiver_id: &'a AccountId,
/// Optional memo string.
pub memo: Option<&'a str>,
}
Expand All @@ -76,7 +76,7 @@ pub struct Nep141Burn<'a> {
/// Amount to burn.
pub amount: u128,
/// Account ID to burn from.
pub account_id: &'a AccountId,
pub owner_id: &'a AccountId,
/// Optional memo string.
pub memo: Option<&'a str>,
}
Expand Down Expand Up @@ -134,15 +134,15 @@ pub trait Nep141Controller {
fn total_supply(&self) -> u128;

/// Removes tokens from an account and decreases total supply. No event
/// emission.
/// emission or hook invocation.
fn withdraw_unchecked(
&mut self,
account_id: &AccountId,
amount: u128,
) -> Result<(), WithdrawError>;

/// Increases the token balance of an account. Updates total supply. No
/// event emission.
/// event emission or hook invocation.
fn deposit_unchecked(
&mut self,
account_id: &AccountId,
Expand All @@ -151,39 +151,25 @@ pub trait Nep141Controller {

/// Decreases the balance of `sender_account_id` by `amount` and increases
/// the balance of `receiver_account_id` by the same. No change to total
/// supply. No event emission.
///
/// # Panics
///
/// Panics if the balance of `sender_account_id` < `amount` or if the
/// balance of `receiver_account_id` plus `amount` >= `u128::MAX`.
/// supply. No event emission or hook invocation.
fn transfer_unchecked(
&mut self,
sender_account_id: &AccountId,
receiver_account_id: &AccountId,
amount: u128,
) -> Result<(), TransferError>;

/// Performs an NEP-141 token transfer, with event emission.
///
/// # Panics
///
/// See: [`Nep141Controller::transfer_unchecked`]
/// Performs an NEP-141 token transfer, with event emission. Invokes
/// [`Nep141Controller::TransferHook`].
fn transfer(&mut self, transfer: &Nep141Transfer<'_>) -> Result<(), TransferError>;

/// Performs an NEP-141 token mint, with event emission.
///
/// # Panics
///
/// See: [`Nep141Controller::deposit_unchecked`]
/// Performs an NEP-141 token mint, with event emission. Invokes
/// [`Nep141Controller::MintHook`].
fn mint(&mut self, mint: &Nep141Mint<'_>) -> Result<(), DepositError>;

/// Performs an NEP-141 token burn, with event emission.
///
/// # Panics
///
/// See: [`Nep141Controller::withdraw_unchecked`]
fn burn(&mut self, mint: &Nep141Burn<'_>) -> Result<(), WithdrawError>;
/// Performs an NEP-141 token burn, with event emission. Invokes
/// [`Nep141Controller::BurnHook`].
fn burn(&mut self, burn: &Nep141Burn<'_>) -> Result<(), WithdrawError>;
}

impl<T: Nep141ControllerInternal> Nep141Controller for T {
Expand Down Expand Up @@ -320,10 +306,10 @@ impl<T: Nep141ControllerInternal> Nep141Controller for T {

fn mint(&mut self, mint: &Nep141Mint) -> Result<(), DepositError> {
Self::MintHook::hook(self, mint, |contract| {
contract.deposit_unchecked(mint.account_id, mint.amount)?;
contract.deposit_unchecked(mint.receiver_id, mint.amount)?;

Nep141Event::FtMint(vec![FtMintData {
owner_id: mint.account_id.clone(),
owner_id: mint.receiver_id.clone(),
amount: mint.amount.into(),
memo: mint.memo.map(ToString::to_string),
}])
Expand All @@ -335,10 +321,10 @@ impl<T: Nep141ControllerInternal> Nep141Controller for T {

fn burn(&mut self, burn: &Nep141Burn) -> Result<(), WithdrawError> {
Self::BurnHook::hook(self, burn, |contract| {
contract.withdraw_unchecked(burn.account_id, burn.amount)?;
contract.withdraw_unchecked(burn.owner_id, burn.amount)?;

Nep141Event::FtBurn(vec![FtBurnData {
owner_id: burn.account_id.clone(),
owner_id: burn.owner_id.clone(),
amount: burn.amount.into(),
memo: burn.memo.map(ToString::to_string),
}])
Expand Down
84 changes: 71 additions & 13 deletions src/standard/nep145/hooks.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,87 @@
//! Hooks to integrate NEP-145 with other standards.
//! Hooks to integrate NEP-145 with other components.

use near_sdk::env;
use near_sdk::{env, AccountId};

use crate::hook::Hook;
use crate::{
hook::Hook,
standard::{
nep141::{Nep141Burn, Nep141Mint, Nep141Transfer},
nep171::action::{Nep171Burn, Nep171Mint, Nep171Transfer},
},
};

use super::Nep145Controller;

fn require_registration(contract: &impl Nep145Controller, account_id: &AccountId) {
contract
.get_storage_balance(account_id)
.unwrap_or_else(|e| env::panic_str(&e.to_string()));
}

fn apply_storage_accounting_hook<C: Nep145Controller, R>(
contract: &mut C,
account_id: &AccountId,
f: impl FnOnce(&mut C) -> R,
) -> R {
let storage_usage_start = env::storage_usage();
require_registration(contract, account_id);

let r = f(contract);

contract
.storage_accounting(account_id, storage_usage_start)
.unwrap_or_else(|e| env::panic_str(&format!("Storage accounting error: {}", e)));

r
}

/// Hook to perform storage accounting before and after a storage write.
pub struct PredecessorStorageAccountingHook;

impl<C: Nep145Controller, A> Hook<C, A> for PredecessorStorageAccountingHook {
fn hook<R>(contract: &mut C, _args: &A, f: impl FnOnce(&mut C) -> R) -> R {
let storage_usage_start = env::storage_usage();
let predecessor = env::predecessor_account_id();
apply_storage_accounting_hook(contract, &env::predecessor_account_id(), f)
}
}

contract
.get_storage_balance(&predecessor)
.unwrap_or_else(|e| env::panic_str(&e.to_string()));
/// NEP-141 support for NEP-145.
pub struct Nep141StorageAccountingHook;

let r = f(contract);
impl<C: Nep145Controller> Hook<C, Nep141Mint<'_>> for Nep141StorageAccountingHook {
fn hook<R>(contract: &mut C, action: &Nep141Mint<'_>, f: impl FnOnce(&mut C) -> R) -> R {
apply_storage_accounting_hook(contract, action.receiver_id, f)
}
}

contract
.storage_accounting(&predecessor, storage_usage_start)
.unwrap_or_else(|e| env::panic_str(&format!("Storage accounting error: {}", e)));
impl<C: Nep145Controller> Hook<C, Nep141Transfer<'_>> for Nep141StorageAccountingHook {
fn hook<R>(contract: &mut C, action: &Nep141Transfer<'_>, f: impl FnOnce(&mut C) -> R) -> R {
apply_storage_accounting_hook(contract, action.receiver_id, f)
}
}

impl<C: Nep145Controller> Hook<C, Nep141Burn<'_>> for Nep141StorageAccountingHook {
fn hook<R>(contract: &mut C, _action: &Nep141Burn<'_>, f: impl FnOnce(&mut C) -> R) -> R {
f(contract)
}
}

/// NEP-171 support for NEP-145.
pub struct Nep171StorageAccountingHook;

impl<C: Nep145Controller> Hook<C, Nep171Mint<'_>> for Nep171StorageAccountingHook {
fn hook<R>(contract: &mut C, action: &Nep171Mint<'_>, f: impl FnOnce(&mut C) -> R) -> R {
apply_storage_accounting_hook(contract, action.receiver_id, f)
}
}

impl<C: Nep145Controller> Hook<C, Nep171Transfer<'_>> for Nep171StorageAccountingHook {
fn hook<R>(contract: &mut C, action: &Nep171Transfer<'_>, f: impl FnOnce(&mut C) -> R) -> R {
apply_storage_accounting_hook(contract, action.receiver_id, f)
}
}

r
impl<C: Nep145Controller> Hook<C, Nep171Burn<'_>> for Nep171StorageAccountingHook {
fn hook<R>(contract: &mut C, _action: &Nep171Burn<'_>, f: impl FnOnce(&mut C) -> R) -> R {
f(contract)
}
}
41 changes: 41 additions & 0 deletions src/standard/nep171/hooks.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//! Hooks to integrate NEP-171 with other components.

use crate::{
hook::Hook,
standard::{nep145::Nep145ForceUnregister, nep181::Nep181Controller},
};

use super::{action::Nep171Burn, Nep171Controller};

/// Hook that burns all NEP-171 tokens held by an account when the account
/// performs an NEP-145 force unregister.
pub struct BurnNep171OnForceUnregisterHook;

impl<C> Hook<C, Nep145ForceUnregister<'_>> for BurnNep171OnForceUnregisterHook
where
C: Nep171Controller + Nep181Controller,
{
fn hook<R>(
contract: &mut C,
action: &Nep145ForceUnregister<'_>,
f: impl FnOnce(&mut C) -> R,
) -> R {
let token_ids = contract.with_tokens_for_owner(action.account_id, |t| {
t.into_iter().cloned().collect::<Vec<_>>()
});

contract
.burn(&Nep171Burn {
token_ids: &token_ids,
owner_id: action.account_id,
memo: Some("storage forced unregistration"),
})
.unwrap_or_else(|e| {
near_sdk::env::panic_str(&format!(
"Failed to burn tokens during forced unregistration: {e}",
))
});

f(contract)
}
}
1 change: 1 addition & 0 deletions src/standard/nep171/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ use event::*;
// separate module with re-export because ext_contract doesn't play well with #![warn(missing_docs)]
mod ext;
pub use ext::*;
pub mod hooks;

/// Minimum required gas for [`Nep171Resolver::nft_resolve_transfer`] call in promise chain during [`Nep171::nft_transfer_call`].
pub const GAS_FOR_RESOLVE_TRANSFER: Gas = Gas(5_000_000_000_000);
Expand Down
Loading