From 0d1ae85b1a4348b0639f5dab6dbf211a540b1a21 Mon Sep 17 00:00:00 2001 From: Jacob Lindahl Date: Wed, 8 May 2024 19:13:51 +0900 Subject: [PATCH] BREAKING: Token standards ergonomics improvements (#148) * feat: FungibleTokenMetadata -> ContractMetadata rename for consistency with nft; improved reference ergos * chore: use AccountIdRef * feat: convenience constructor functions * feat: deserializable events, fixes #147 * fix: revert removing clone derive --- README.md | 44 +++-- macros/src/standard/event.rs | 2 +- macros/src/standard/fungible_token.rs | 9 +- macros/src/standard/nep141.rs | 26 +-- macros/src/standard/nep148.rs | 2 +- macros/src/standard/nep171.rs | 45 +++-- macros/src/standard/nep178.rs | 16 +- macros/src/standard/nep297.rs | 8 +- src/lib.rs | 2 +- src/slot.rs | 15 +- src/standard/nep141/event.rs | 59 ++++--- src/standard/nep141/hooks.rs | 13 +- src/standard/nep141/mod.rs | 159 +++++++++++++----- src/standard/nep145/hooks.rs | 14 +- src/standard/nep145/mod.rs | 75 ++++----- src/standard/nep148.rs | 18 +- src/standard/nep171/action.rs | 114 ++++++++++--- src/standard/nep171/event.rs | 69 ++++---- src/standard/nep171/hooks.rs | 11 +- src/standard/nep171/mod.rs | 58 +++---- src/standard/nep177.rs | 75 ++++----- src/standard/nep178/action.rs | 32 ++-- src/standard/nep178/mod.rs | 90 +++++----- src/standard/nep181.rs | 40 ++--- src/standard/nep297.rs | 105 ++++++++++-- tests/macros/mod.rs | 8 +- tests/macros/standard/fungible_token.rs | 2 +- tests/macros/standard/nep141.rs | 16 +- tests/macros/standard/nep148.rs | 2 +- tests/macros/standard/nep171/hooks.rs | 2 +- .../standard/nep171/manual_integration.rs | 8 +- tests/macros/standard/nep171/mod.rs | 21 +-- tests/macros/standard/nep171/no_hooks.rs | 15 +- .../standard/nep171/non_fungible_token.rs | 8 +- workspaces-tests/src/bin/fungible_token.rs | 8 +- .../src/bin/non_fungible_token_full.rs | 10 +- .../src/bin/non_fungible_token_nep171.rs | 14 +- workspaces-tests/tests/fungible_token.rs | 34 ++-- workspaces-tests/tests/native_multisig.rs | 4 +- workspaces-tests/tests/non_fungible_token.rs | 119 ++++++------- 40 files changed, 780 insertions(+), 592 deletions(-) diff --git a/README.md b/README.md index fd8b815..fc8afed 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,11 @@ ## NFT -```diff -use near_sdk::{near, PanicOnDefault}; -+ use near_sdk_contract_tools::nft::*; +```rust +use near_sdk::near; +use near_sdk_contract_tools::nft::*; -#[derive(PanicOnDefault)] -+ #[derive(NonFungibleToken)] +#[derive(Default, NonFungibleToken)] #[near(contract_state)] pub struct MyNftContract {} @@ -17,11 +16,11 @@ impl MyNftContract { pub fn new() -> Self { let mut contract = Self {}; -+ contract.set_contract_metadata(ContractMetadata::new( -+ "My NFT".to_string(), -+ "MNFT".to_string(), -+ None, -+ )); + contract.set_contract_metadata(&ContractMetadata::new( + "My NFT".to_string(), + "MNFT".to_string(), + None, + )); contract } @@ -30,12 +29,11 @@ impl MyNftContract { ## FT -```diff -use near_sdk::{near, PanicOnDefault}; -+ use near_sdk_contract_tools::ft::*; +```rust +use near_sdk::near; +use near_sdk_contract_tools::ft::*; -#[derive(PanicOnDefault)] -+ #[derive(FungibleToken)] +#[derive(Default, FungibleToken)] #[near(contract_state)] pub struct MyFtContract {} @@ -45,11 +43,11 @@ impl MyFtContract { pub fn new() -> Self { let mut contract = Self {}; -+ contract.set_metadata(&FungibleTokenMetadata::new( -+ "My Fungible Token".into(), -+ "MYFT".into(), -+ 24, -+ )); + contract.set_metadata(&ContractMetadata::new( + "My Fungible Token".into(), + "MYFT".into(), + 24, + )); contract } @@ -157,10 +155,10 @@ e.emit(); To create a contract that is compatible with the [NEP-141][nep141], [NEP-145][nep145], and [NEP-148][nep148] standards, that emits standard-compliant ([NEP-297][nep297]) events. ```rust +use near_sdk::near; use near_sdk_contract_tools::ft::*; -use near_sdk::{near, PanicOnDefault}; -#[derive(FungibleToken, PanicOnDefault)] +#[derive(Default, FungibleToken)] #[near(contract_state)] struct MyFt {} @@ -170,7 +168,7 @@ impl MyFt { pub fn new() -> Self { let mut contract = Self {}; - contract.set_metadata(&FungibleTokenMetadata::new( + contract.set_metadata(&ContractMetadata::new( "My Fungible Token".to_string(), "MYFT".to_string(), 24, diff --git a/macros/src/standard/event.rs b/macros/src/standard/event.rs index ca5e8f4..2fcf2a4 100644 --- a/macros/src/standard/event.rs +++ b/macros/src/standard/event.rs @@ -63,7 +63,7 @@ pub fn event_attribute( let me_str = quote! { #me }.to_string(); Ok(quote::quote! { - #[derive(#macros::Nep297, #serde::Serialize)] + #[derive(#macros::Nep297, #serde::Serialize, #serde::Deserialize)] #[nep297( crate = #me_str, standard = #standard, diff --git a/macros/src/standard/fungible_token.rs b/macros/src/standard/fungible_token.rs index f4c53da..bfb1131 100644 --- a/macros/src/standard/fungible_token.rs +++ b/macros/src/standard/fungible_token.rs @@ -3,6 +3,8 @@ use proc_macro2::TokenStream; use quote::quote; use syn::{Expr, Type}; +use crate::unitify; + use super::{nep141, nep145, nep148}; #[derive(Debug, FromDeriveInput)] @@ -53,11 +55,8 @@ pub fn expand(meta: FungibleTokenMeta) -> Result { 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 all_hooks_or_unit = unitify(all_hooks.clone()); + let force_unregister_hook_or_unit = unitify(force_unregister_hook); let expand_nep141 = nep141::expand(nep141::Nep141Meta { storage_key: core_storage_key, diff --git a/macros/src/standard/nep141.rs b/macros/src/standard/nep141.rs index 88bae50..b04dea9 100644 --- a/macros/src/standard/nep141.rs +++ b/macros/src/standard/nep141.rs @@ -84,10 +84,10 @@ pub fn expand(meta: Nep141Meta) -> Result { let amount: u128 = amount.into(); let transfer = Nep141Transfer { - sender_id: &sender_id, - receiver_id: &receiver_id, + sender_id: sender_id.into(), + receiver_id: receiver_id.into(), amount, - memo: memo.as_deref(), + memo: memo.map(Into::into), msg: None, revert: false, }; @@ -118,11 +118,11 @@ pub fn expand(meta: Nep141Meta) -> Result { let amount: u128 = amount.into(); let transfer = Nep141Transfer { - sender_id: &sender_id, - receiver_id: &receiver_id, + sender_id: sender_id.into(), + receiver_id: receiver_id.into(), amount, - memo: memo.as_deref(), - msg: Some(&msg), + memo: memo.map(Into::into), + msg: Some(msg.clone().into()), revert: false, }; @@ -134,15 +134,15 @@ pub fn expand(meta: Nep141Meta) -> Result { .unwrap_or_else(|| #near_sdk::env::panic_str("Prepaid gas underflow.")); // Initiating receiver's call and the callback - ext_nep141_receiver::ext(transfer.receiver_id.clone()) + ext_nep141_receiver::ext(transfer.receiver_id.clone().into()) .with_static_gas(receiver_gas) - .ft_on_transfer(transfer.sender_id.clone(), transfer.amount.into(), msg.clone()) + .ft_on_transfer(transfer.sender_id.clone().into(), transfer.amount.into(), msg) .then( ext_nep141_resolver::ext(#near_sdk::env::current_account_id()) .with_static_gas(GAS_FOR_RESOLVE_TRANSFER) .ft_resolve_transfer( - transfer.sender_id.clone(), - transfer.receiver_id.clone(), + transfer.sender_id.clone().into(), + transfer.receiver_id.clone().into(), transfer.amount.into(), ), ) @@ -190,8 +190,8 @@ pub fn expand(meta: Nep141Meta) -> Result { if receiver_balance > 0 { let refund_amount = std::cmp::min(receiver_balance, unused_amount); let transfer = Nep141Transfer { - sender_id: &receiver_id, - receiver_id: &sender_id, + sender_id: receiver_id.into(), + receiver_id: sender_id.into(), amount: refund_amount, memo: None, msg: None, diff --git a/macros/src/standard/nep148.rs b/macros/src/standard/nep148.rs index d55945a..96cc53e 100644 --- a/macros/src/standard/nep148.rs +++ b/macros/src/standard/nep148.rs @@ -44,7 +44,7 @@ pub fn expand(meta: Nep148Meta) -> Result { #[#near_sdk::near] impl #imp #me::standard::nep148::Nep148 for #ident #ty #wher { - fn ft_metadata(&self) -> #me::standard::nep148::FungibleTokenMetadata { + fn ft_metadata(&self) -> #me::standard::nep148::ContractMetadata { #me::standard::nep148::Nep148Controller::get_metadata(self) } } diff --git a/macros/src/standard/nep171.rs b/macros/src/standard/nep171.rs index f7db612..6e90cd4 100644 --- a/macros/src/standard/nep171.rs +++ b/macros/src/standard/nep171.rs @@ -104,13 +104,11 @@ pub fn expand(meta: Nep171Meta) -> Result { }; if should_revert { - let token_ids = [token_id]; - let transfer = action::Nep171Transfer { - token_id: &token_ids[0], + token_id, authorization: Nep171TransferAuthorization::Owner, - sender_id: &receiver_id, - receiver_id: &previous_owner_id, + sender_id: receiver_id.into(), + receiver_id: previous_owner_id.into(), memo: None, msg: None, revert: true, @@ -140,14 +138,12 @@ pub fn expand(meta: Nep171Meta) -> Result { let sender_id = #near_sdk::env::predecessor_account_id(); - let token_ids = [token_id]; - let transfer = action::Nep171Transfer { - token_id: &token_ids[0], + token_id, authorization: approval_id.map(Nep171TransferAuthorization::ApprovalId).unwrap_or(Nep171TransferAuthorization::Owner), - sender_id: &sender_id, - receiver_id: &receiver_id, - memo: memo.as_deref(), + sender_id: sender_id.into(), + receiver_id: receiver_id.into(), + memo: memo.map(Into::into), msg: None, revert: false, }; @@ -176,35 +172,36 @@ pub fn expand(meta: Nep171Meta) -> Result { let sender_id = #near_sdk::env::predecessor_account_id(); - let token_ids = [token_id]; - let transfer = action::Nep171Transfer { - token_id: &token_ids[0], + token_id: token_id.clone(), authorization: approval_id.map(Nep171TransferAuthorization::ApprovalId).unwrap_or(Nep171TransferAuthorization::Owner), - sender_id: &sender_id, - receiver_id: &receiver_id, - memo: memo.as_deref(), - msg: Some(&msg), + sender_id: sender_id.clone().into(), + receiver_id: receiver_id.clone().into(), + memo: memo.map(Into::into), + msg: Some(msg.clone().into()), revert: false, }; ::external_transfer(self, &transfer) .unwrap_or_else(|e| #near_sdk::env::panic_str(&e.to_string())); - let [token_id] = token_ids; - - ext_nep171_receiver::ext(receiver_id.clone()) + ext_nep171_receiver::ext(receiver_id.clone().into()) .with_static_gas(#near_sdk::env::prepaid_gas().saturating_sub(GAS_FOR_NFT_TRANSFER_CALL)) .nft_on_transfer( - sender_id.clone(), - sender_id.clone(), + sender_id.clone().into(), + sender_id.clone().into(), token_id.clone(), msg.clone(), ) .then( ext_nep171_resolver::ext(#near_sdk::env::current_account_id()) .with_static_gas(GAS_FOR_RESOLVE_TRANSFER) - .nft_resolve_transfer(sender_id.clone(), receiver_id.clone(), token_id.clone(), None), + .nft_resolve_transfer( + sender_id.clone().into(), + receiver_id.clone().into(), + token_id.clone(), + None, + ), ) .into() } diff --git a/macros/src/standard/nep178.rs b/macros/src/standard/nep178.rs index dab08f1..118a8a9 100644 --- a/macros/src/standard/nep178.rs +++ b/macros/src/standard/nep178.rs @@ -79,9 +79,9 @@ pub fn expand(meta: Nep178Meta) -> Result { let predecessor = #near_sdk::env::predecessor_account_id(); let action = action::Nep178Approve { - token_id: &token_id, - current_owner_id: &predecessor, - account_id: &account_id, + token_id: token_id.clone(), + current_owner_id: predecessor.clone().into(), + account_id: account_id.clone().into(), }; let approval_id = Nep178Controller::approve(self, &action) @@ -107,9 +107,9 @@ pub fn expand(meta: Nep178Meta) -> Result { let predecessor = #near_sdk::env::predecessor_account_id(); let action = action::Nep178Revoke { - token_id: &token_id, - current_owner_id: &predecessor, - account_id: &account_id, + token_id, + current_owner_id: predecessor.into(), + account_id: account_id.into(), }; Nep178Controller::revoke(self, &action) @@ -125,8 +125,8 @@ pub fn expand(meta: Nep178Meta) -> Result { let predecessor = #near_sdk::env::predecessor_account_id(); let action = action::Nep178RevokeAll { - token_id: &token_id, - current_owner_id: &predecessor, + token_id, + current_owner_id: predecessor.into(), }; Nep178Controller::revoke_all(self, &action) diff --git a/macros/src/standard/nep297.rs b/macros/src/standard/nep297.rs index 2b260bc..dd1997a 100644 --- a/macros/src/standard/nep297.rs +++ b/macros/src/standard/nep297.rs @@ -153,11 +153,11 @@ pub fn expand(meta: Nep297Meta) -> Result { impl #imp #me::standard::nep297::ToEventLog for #ident #ty #wher { type Data = #ident #ty; - fn to_event_log<'__el>(&'__el self) -> #me::standard::nep297::EventLog<&'__el Self> { + fn to_event_log<'__el>(&'__el self) -> #me::standard::nep297::EventLog<&'__el Self::Data> { #me::standard::nep297::EventLog { - standard: #standard, - version: #version, - event: #event, + standard: #standard.into(), + version: #version.into(), + event: #event.into(), data: self, } } diff --git a/src/lib.rs b/src/lib.rs index e95716d..f94a4aa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -109,7 +109,7 @@ pub mod ft { StorageBalance, StorageBalanceBounds, }, nep148::{ - self, ext_nep148, FungibleTokenMetadata, Nep148, Nep148Controller, + self, ext_nep148, ContractMetadata, Nep148, Nep148Controller, Nep148ControllerInternal, }, }, diff --git a/src/slot.rs b/src/slot.rs index a939d92..a15d74c 100644 --- a/src/slot.rs +++ b/src/slot.rs @@ -2,7 +2,7 @@ //! //! Makes it easy to create and manage storage keys and avoid unnecessary //! writes to contract storage. This reduces transaction IO and saves on gas. -use std::marker::PhantomData; +use std::{marker::PhantomData, ops::Deref}; use near_sdk::{ borsh::{self, BorshDeserialize, BorshSerialize}, @@ -95,9 +95,18 @@ impl Slot { } impl Slot { - /// Writes a value to the managed storage slot + /// Writes a value to the managed storage slot. pub fn write(&mut self, value: &T) -> bool { - self.write_raw(&{ borsh::to_vec(&value) }.unwrap()) + self.write_raw(&borsh::to_vec(value).unwrap()) + } + + /// Writes a value to the managed storage slot which is dereferenced from + /// the target type. + pub fn write_deref(&mut self, value: &U) -> bool + where + T: Deref, + { + self.write_raw(&borsh::to_vec(value).unwrap()) } /// If the given value is `Some(T)`, writes `T` to storage. Otherwise, diff --git a/src/standard/nep141/event.rs b/src/standard/nep141/event.rs index 7f6a8bb..e4c85de 100644 --- a/src/standard/nep141/event.rs +++ b/src/standard/nep141/event.rs @@ -1,5 +1,13 @@ //! NEP-141 standard events for minting, burning, and transferring tokens. +use std::borrow::Cow; + +use near_sdk::{ + json_types::U128, + serde::{Deserialize, Serialize}, + AccountIdRef, +}; + use near_sdk_contract_tools_macros::event; /// NEP-141 standard events for minting, burning, and transferring tokens. @@ -10,60 +18,59 @@ use near_sdk_contract_tools_macros::event; version = "1.0.0" )] #[derive(Debug, Clone)] -pub enum Nep141Event { +pub enum Nep141Event<'a> { /// Token mint event. Emitted when tokens are created and total_supply is /// increased. - FtMint(Vec), + FtMint(Vec>), /// Token transfer event. Emitted when tokens are transferred between two /// accounts. No change to total_supply. - FtTransfer(Vec), + FtTransfer(Vec>), /// Token burn event. Emitted when tokens are burned (removed from supply). /// Decrease in total_supply. - FtBurn(Vec), + FtBurn(Vec>), } -use near_sdk::{json_types::U128, serde::Serialize, AccountId}; /// Individual mint metadata -#[derive(Serialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(crate = "near_sdk::serde")] -pub struct FtMintData { +pub struct FtMintData<'a> { /// Address to which new tokens were minted - pub owner_id: AccountId, + pub owner_id: Cow<'a, AccountIdRef>, /// Amount of minted tokens pub amount: U128, /// Optional note #[serde(skip_serializing_if = "Option::is_none")] - pub memo: Option, + pub memo: Option>, } /// Individual transfer metadata -#[derive(Serialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(crate = "near_sdk::serde")] -pub struct FtTransferData { +pub struct FtTransferData<'a> { /// Account ID of the sender - pub old_owner_id: AccountId, + pub old_owner_id: Cow<'a, AccountIdRef>, /// Account ID of the receiver - pub new_owner_id: AccountId, + pub new_owner_id: Cow<'a, AccountIdRef>, /// Amount of transferred tokens pub amount: U128, /// Optional note #[serde(skip_serializing_if = "Option::is_none")] - pub memo: Option, + pub memo: Option>, } /// Individual burn metadata -#[derive(Serialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(crate = "near_sdk::serde")] -pub struct FtBurnData { +pub struct FtBurnData<'a> { /// Account ID from which tokens were burned - pub owner_id: AccountId, + pub owner_id: Cow<'a, AccountIdRef>, /// Amount of burned tokens pub amount: U128, /// Optional note #[serde(skip_serializing_if = "Option::is_none")] - pub memo: Option, + pub memo: Option>, } #[cfg(test)] @@ -75,7 +82,7 @@ mod tests { fn mint() { assert_eq!( Nep141Event::FtMint(vec![FtMintData { - owner_id: "foundation.near".parse().unwrap(), + owner_id: AccountIdRef::new_or_panic("foundation.near").into(), amount: 500u128.into(), memo: None, }]) @@ -89,16 +96,16 @@ mod tests { assert_eq!( Nep141Event::FtTransfer(vec![ FtTransferData { - old_owner_id: "from.near".parse().unwrap(), - new_owner_id: "to.near".parse().unwrap(), + old_owner_id: AccountIdRef::new_or_panic("from.near").into(), + new_owner_id: AccountIdRef::new_or_panic("to.near").into(), amount: 42u128.into(), - memo: Some("hi hello bonjour".to_string()), + memo: Some("hi hello bonjour".into()), }, FtTransferData { - old_owner_id: "user1.near".parse().unwrap(), - new_owner_id: "user2.near".parse().unwrap(), + old_owner_id: AccountIdRef::new_or_panic("user1.near").into(), + new_owner_id: AccountIdRef::new_or_panic("user2.near").into(), amount: 7500u128.into(), - memo: None + memo: None, }, ]) .to_event_string(), @@ -110,7 +117,7 @@ mod tests { fn burn() { assert_eq!( Nep141Event::FtBurn(vec![FtBurnData { - owner_id: "foundation.near".parse().unwrap(), + owner_id: AccountIdRef::new_or_panic("foundation.near").into(), amount: 100u128.into(), memo: None, }]) diff --git a/src/standard/nep141/hooks.rs b/src/standard/nep141/hooks.rs index ffc7869..f7e6a67 100644 --- a/src/standard/nep141/hooks.rs +++ b/src/standard/nep141/hooks.rs @@ -17,20 +17,19 @@ impl Hook R { let r = f(contract); - let balance = contract.balance_of(args.account_id); + let balance = contract.balance_of(&args.account_id); contract - .burn(&Nep141Burn { - amount: balance, - owner_id: args.account_id, - memo: Some("storage forced unregistration"), - }) + .burn( + &Nep141Burn::new(balance, args.account_id.clone()) + .memo("storage forced unregistration"), + ) .unwrap_or_else(|e| { near_sdk::env::panic_str(&format!( "Failed to burn tokens during forced unregistration: {e}", )) }); - ::slot_account(args.account_id).remove(); + ::slot_account(&args.account_id).remove(); r } diff --git a/src/standard/nep141/mod.rs b/src/standard/nep141/mod.rs index a32648a..73059e9 100644 --- a/src/standard/nep141/mod.rs +++ b/src/standard/nep141/mod.rs @@ -1,7 +1,9 @@ //! NEP-141 fungible token core implementation //! -use near_sdk::{borsh::BorshSerialize, near, serde::Serialize, AccountId, BorshStorageKey, Gas}; +use std::borrow::Cow; + +use near_sdk::{borsh::BorshSerialize, near, AccountIdRef, BorshStorageKey, Gas}; use crate::{hook::Hook, slot::Slot, standard::nep297::*, DefaultStorageKey}; @@ -24,34 +26,65 @@ pub const GAS_FOR_FT_TRANSFER_CALL: Gas = /// Error message for insufficient gas. pub const MORE_GAS_FAIL_MESSAGE: &str = "Insufficient gas attached."; -#[derive(BorshStorageKey)] -#[near] -enum StorageKey { +#[derive(BorshSerialize, BorshStorageKey)] +#[borsh(crate = "near_sdk::borsh")] +enum StorageKey<'a> { TotalSupply, - Account(AccountId), + Account(&'a AccountIdRef), } /// Transfer metadata generic over both types of transfer (`ft_transfer` and /// `ft_transfer_call`). -#[derive(Serialize, BorshSerialize, PartialEq, Eq, Clone, Debug)] -#[serde(crate = "near_sdk::serde")] -#[borsh(crate = "near_sdk::borsh")] +#[derive(PartialEq, Eq, Clone, Debug)] +#[near] pub struct Nep141Transfer<'a> { /// Sender's account ID. - pub sender_id: &'a AccountId, + pub sender_id: Cow<'a, AccountIdRef>, /// Receiver's account ID. - pub receiver_id: &'a AccountId, + pub receiver_id: Cow<'a, AccountIdRef>, /// Transferred amount. pub amount: u128, /// Optional memo string. - pub memo: Option<&'a str>, + pub memo: Option>, /// Message passed to contract located at `receiver_id`. - pub msg: Option<&'a str>, + pub msg: Option>, /// Is this transfer a revert as a result of a [`Nep141::ft_transfer_call`] -> [`Nep141Receiver::ft_on_transfer`] call? pub revert: bool, } impl<'a> Nep141Transfer<'a> { + /// Create a new transfer action. + pub fn new( + amount: u128, + sender_id: impl Into>, + receiver_id: impl Into>, + ) -> Self { + Self { + sender_id: sender_id.into(), + receiver_id: receiver_id.into(), + amount, + memo: None, + msg: None, + revert: false, + } + } + + /// Add a memo string. + pub fn memo(self, memo: impl Into>) -> Self { + Self { + memo: Some(memo.into()), + ..self + } + } + + /// Add a message string. + pub fn msg(self, msg: impl Into>) -> Self { + Self { + msg: Some(msg.into()), + ..self + } + } + /// Returns `true` if this transfer comes from a `ft_transfer_call` /// call, `false` otherwise. pub fn is_transfer_call(&self) -> bool { @@ -60,29 +93,65 @@ impl<'a> Nep141Transfer<'a> { } /// Describes a mint operation. -#[derive(Serialize, BorshSerialize, Clone, Debug, PartialEq, Eq)] -#[serde(crate = "near_sdk::serde")] -#[borsh(crate = "near_sdk::borsh")] +#[derive(Clone, Debug, PartialEq, Eq)] +#[near] pub struct Nep141Mint<'a> { /// Amount to mint. pub amount: u128, /// Account ID to mint to. - pub receiver_id: &'a AccountId, + pub receiver_id: Cow<'a, AccountIdRef>, /// Optional memo string. - pub memo: Option<&'a str>, + pub memo: Option>, +} + +impl<'a> Nep141Mint<'a> { + /// Create a new mint action. + pub fn new(amount: u128, receiver_id: impl Into>) -> Self { + Self { + amount, + receiver_id: receiver_id.into(), + memo: None, + } + } + + /// Add a memo string. + pub fn memo(self, memo: impl Into>) -> Self { + Self { + memo: Some(memo.into()), + ..self + } + } } /// Describes a burn operation. -#[derive(Serialize, BorshSerialize, Clone, Debug, PartialEq, Eq)] -#[serde(crate = "near_sdk::serde")] -#[borsh(crate = "near_sdk::borsh")] +#[derive(Clone, Debug, PartialEq, Eq)] +#[near] pub struct Nep141Burn<'a> { /// Amount to burn. pub amount: u128, /// Account ID to burn from. - pub owner_id: &'a AccountId, + pub owner_id: Cow<'a, AccountIdRef>, /// Optional memo string. - pub memo: Option<&'a str>, + pub memo: Option>, +} + +impl<'a> Nep141Burn<'a> { + /// Create a new burn action. + pub fn new(amount: u128, owner_id: impl Into>) -> Self { + Self { + amount, + owner_id: owner_id.into(), + memo: None, + } + } + + /// Add a memo string. + pub fn memo(self, memo: impl Into>) -> Self { + Self { + memo: Some(memo.into()), + ..self + } + } } /// Internal functions for [`Nep141Controller`]. Using these methods may result in unexpected behavior. @@ -106,8 +175,8 @@ pub trait Nep141ControllerInternal { } /// Slot for account data. - fn slot_account(account_id: &AccountId) -> Slot { - Self::root().field(StorageKey::Account(account_id.clone())) + fn slot_account(account_id: &AccountIdRef) -> Slot { + Self::root().field(StorageKey::Account(account_id)) } /// Slot for storing total supply. @@ -132,7 +201,7 @@ pub trait Nep141Controller { Self: Sized; /// Get the balance of an account. Returns 0 if the account does not exist. - fn balance_of(&self, account_id: &AccountId) -> u128; + fn balance_of(&self, account_id: &AccountIdRef) -> u128; /// Get the total circulating supply of the token. fn total_supply(&self) -> u128; @@ -141,7 +210,7 @@ pub trait Nep141Controller { /// emission or hook invocation. fn withdraw_unchecked( &mut self, - account_id: &AccountId, + account_id: &AccountIdRef, amount: u128, ) -> Result<(), WithdrawError>; @@ -149,7 +218,7 @@ pub trait Nep141Controller { /// event emission or hook invocation. fn deposit_unchecked( &mut self, - account_id: &AccountId, + account_id: &AccountIdRef, amount: u128, ) -> Result<(), DepositError>; @@ -158,8 +227,8 @@ pub trait Nep141Controller { /// supply. No event emission or hook invocation. fn transfer_unchecked( &mut self, - sender_account_id: &AccountId, - receiver_account_id: &AccountId, + sender_account_id: &AccountIdRef, + receiver_account_id: &AccountIdRef, amount: u128, ) -> Result<(), TransferError>; @@ -181,7 +250,7 @@ impl Nep141Controller for T { type TransferHook = T::TransferHook; type BurnHook = T::BurnHook; - fn balance_of(&self, account_id: &AccountId) -> u128 { + fn balance_of(&self, account_id: &AccountIdRef) -> u128 { Self::slot_account(account_id).read().unwrap_or(0) } @@ -191,7 +260,7 @@ impl Nep141Controller for T { fn withdraw_unchecked( &mut self, - account_id: &AccountId, + account_id: &AccountIdRef, amount: u128, ) -> Result<(), WithdrawError> { if amount != 0 { @@ -200,7 +269,7 @@ impl Nep141Controller for T { Self::slot_account(account_id).write(&balance); } else { return Err(BalanceUnderflowError { - account_id: account_id.clone(), + account_id: account_id.to_owned(), balance, amount, } @@ -224,7 +293,7 @@ impl Nep141Controller for T { fn deposit_unchecked( &mut self, - account_id: &AccountId, + account_id: &AccountIdRef, amount: u128, ) -> Result<(), DepositError> { if amount != 0 { @@ -233,7 +302,7 @@ impl Nep141Controller for T { Self::slot_account(account_id).write(&balance); } else { return Err(BalanceOverflowError { - account_id: account_id.clone(), + account_id: account_id.to_owned(), balance, amount, } @@ -257,8 +326,8 @@ impl Nep141Controller for T { fn transfer_unchecked( &mut self, - sender_account_id: &AccountId, - receiver_account_id: &AccountId, + sender_account_id: &AccountIdRef, + receiver_account_id: &AccountIdRef, amount: u128, ) -> Result<(), TransferError> { let sender_balance = self.balance_of(sender_account_id); @@ -270,7 +339,7 @@ impl Nep141Controller for T { Self::slot_account(receiver_account_id).write(&receiver_balance); } else { return Err(BalanceOverflowError { - account_id: receiver_account_id.clone(), + account_id: receiver_account_id.to_owned(), balance: receiver_balance, amount, } @@ -278,7 +347,7 @@ impl Nep141Controller for T { } } else { return Err(BalanceUnderflowError { - account_id: sender_account_id.clone(), + account_id: sender_account_id.to_owned(), balance: sender_balance, amount, } @@ -291,8 +360,8 @@ impl Nep141Controller for T { fn transfer(&mut self, transfer: &Nep141Transfer<'_>) -> Result<(), TransferError> { Self::TransferHook::hook(self, transfer, |contract| { contract.transfer_unchecked( - transfer.sender_id, - transfer.receiver_id, + &transfer.sender_id, + &transfer.receiver_id, transfer.amount, )?; @@ -300,7 +369,7 @@ impl Nep141Controller for T { old_owner_id: transfer.sender_id.clone(), new_owner_id: transfer.receiver_id.clone(), amount: transfer.amount.into(), - memo: transfer.memo.map(ToString::to_string), + memo: transfer.memo.clone(), }]) .emit(); @@ -310,12 +379,12 @@ impl Nep141Controller for T { fn mint(&mut self, mint: &Nep141Mint) -> Result<(), DepositError> { Self::MintHook::hook(self, mint, |contract| { - contract.deposit_unchecked(mint.receiver_id, mint.amount)?; + contract.deposit_unchecked(&mint.receiver_id, mint.amount)?; Nep141Event::FtMint(vec![FtMintData { owner_id: mint.receiver_id.clone(), amount: mint.amount.into(), - memo: mint.memo.map(ToString::to_string), + memo: mint.memo.clone(), }]) .emit(); @@ -325,12 +394,12 @@ impl Nep141Controller for T { fn burn(&mut self, burn: &Nep141Burn) -> Result<(), WithdrawError> { Self::BurnHook::hook(self, burn, |contract| { - contract.withdraw_unchecked(burn.owner_id, burn.amount)?; + contract.withdraw_unchecked(&burn.owner_id, burn.amount)?; Nep141Event::FtBurn(vec![FtBurnData { owner_id: burn.owner_id.clone(), amount: burn.amount.into(), - memo: burn.memo.map(ToString::to_string), + memo: burn.memo.clone(), }]) .emit(); diff --git a/src/standard/nep145/hooks.rs b/src/standard/nep145/hooks.rs index 9c5cddf..13506e4 100644 --- a/src/standard/nep145/hooks.rs +++ b/src/standard/nep145/hooks.rs @@ -1,6 +1,6 @@ //! Hooks to integrate NEP-145 with other components. -use near_sdk::{env, AccountId}; +use near_sdk::{env, AccountIdRef}; use crate::{ hook::Hook, @@ -12,7 +12,7 @@ use crate::{ use super::Nep145Controller; -fn require_registration(contract: &impl Nep145Controller, account_id: &AccountId) { +fn require_registration(contract: &impl Nep145Controller, account_id: &AccountIdRef) { contract .get_storage_balance(account_id) .unwrap_or_else(|e| env::panic_str(&e.to_string())); @@ -20,7 +20,7 @@ fn require_registration(contract: &impl Nep145Controller, account_id: &AccountId fn apply_storage_accounting_hook( contract: &mut C, - account_id: &AccountId, + account_id: &AccountIdRef, f: impl FnOnce(&mut C) -> R, ) -> R { let storage_usage_start = env::storage_usage(); @@ -49,13 +49,13 @@ pub struct Nep141StorageAccountingHook; impl Hook> for Nep141StorageAccountingHook { fn hook(contract: &mut C, action: &Nep141Mint<'_>, f: impl FnOnce(&mut C) -> R) -> R { - apply_storage_accounting_hook(contract, action.receiver_id, f) + apply_storage_accounting_hook(contract, &action.receiver_id, f) } } impl Hook> for Nep141StorageAccountingHook { fn hook(contract: &mut C, action: &Nep141Transfer<'_>, f: impl FnOnce(&mut C) -> R) -> R { - apply_storage_accounting_hook(contract, action.receiver_id, f) + apply_storage_accounting_hook(contract, &action.receiver_id, f) } } @@ -70,13 +70,13 @@ pub struct Nep171StorageAccountingHook; impl Hook> for Nep171StorageAccountingHook { fn hook(contract: &mut C, action: &Nep171Mint<'_>, f: impl FnOnce(&mut C) -> R) -> R { - apply_storage_accounting_hook(contract, action.receiver_id, f) + apply_storage_accounting_hook(contract, &action.receiver_id, f) } } impl Hook> for Nep171StorageAccountingHook { fn hook(contract: &mut C, action: &Nep171Transfer<'_>, f: impl FnOnce(&mut C) -> R) -> R { - apply_storage_accounting_hook(contract, action.receiver_id, f) + apply_storage_accounting_hook(contract, &action.receiver_id, f) } } diff --git a/src/standard/nep145/mod.rs b/src/standard/nep145/mod.rs index c8de00e..6f6ad39 100644 --- a/src/standard/nep145/mod.rs +++ b/src/standard/nep145/mod.rs @@ -1,11 +1,9 @@ //! NEP-145 Storage Management //! -use std::cmp::Ordering; +use std::{borrow::Cow, cmp::Ordering}; -use near_sdk::{ - borsh::BorshSerialize, env, near, serde::Serialize, AccountId, BorshStorageKey, NearToken, -}; +use near_sdk::{borsh::BorshSerialize, env, near, AccountIdRef, BorshStorageKey, NearToken}; use crate::{hook::Hook, slot::Slot, DefaultStorageKey}; @@ -79,16 +77,15 @@ impl Default for StorageBalanceBounds { #[borsh(crate = "near_sdk::borsh")] enum StorageKey<'a> { BalanceBounds, - Account(&'a AccountId), + Account(&'a AccountIdRef), } /// Describes a force unregister action. -#[derive(Serialize, BorshSerialize, Clone, Debug, PartialEq, Eq)] -#[serde(crate = "near_sdk::serde")] -#[borsh(crate = "near_sdk::borsh")] +#[derive(Clone, Debug, PartialEq, Eq)] +#[near] pub struct Nep145ForceUnregister<'a> { /// The account to be unregistered. - pub account_id: &'a AccountId, + pub account_id: Cow<'a, AccountIdRef>, /// The account's balance at the time of unregistration. pub balance: StorageBalance, } @@ -111,7 +108,7 @@ pub trait Nep145ControllerInternal { } /// Storage slot for individual account balance. - fn slot_account(account_id: &AccountId) -> Slot { + fn slot_account(account_id: &AccountIdRef) -> Slot { Slot::new(StorageKey::Account(account_id)) } } @@ -127,34 +124,34 @@ pub trait Nep145Controller { /// Returns the storage balance of the given account. fn get_storage_balance( &self, - account_id: &AccountId, + account_id: &AccountIdRef, ) -> Result; /// Locks the given amount of storage balance for the given account. fn lock_storage( &mut self, - account_id: &AccountId, + account_id: &AccountIdRef, amount: NearToken, ) -> Result; /// Unlocks the given amount of storage balance for the given account. fn unlock_storage( &mut self, - account_id: &AccountId, + account_id: &AccountIdRef, amount: NearToken, ) -> Result; /// Deposits the given amount of storage balance for the given account. fn deposit_to_storage_account( &mut self, - account_id: &AccountId, + account_id: &AccountIdRef, amount: NearToken, ) -> Result; /// Withdraws the given amount of storage balance for the given account. fn withdraw_from_storage_account( &mut self, - account_id: &AccountId, + account_id: &AccountIdRef, amount: NearToken, ) -> Result; @@ -162,14 +159,14 @@ pub trait Nep145Controller { /// that should be refunded. fn unregister_storage_account( &mut self, - account_id: &AccountId, + account_id: &AccountIdRef, ) -> Result; /// Force unregisters the given account, returning the amount of storage balance /// that should be refunded. fn force_unregister_storage_account( &mut self, - account_id: &AccountId, + account_id: &AccountIdRef, ) -> Result; /// Returns the storage balance bounds for the contract. @@ -182,7 +179,7 @@ pub trait Nep145Controller { /// storage writes that are to be debited from the account's balance. fn storage_accounting( &mut self, - account_id: &AccountId, + account_id: &AccountIdRef, storage_usage_start: u64, ) -> Result<(), StorageAccountingError> { let storage_usage_end = env::storage_usage(); @@ -216,29 +213,29 @@ impl Nep145Controller for T { fn get_storage_balance( &self, - account_id: &AccountId, + account_id: &AccountIdRef, ) -> Result { Self::slot_account(account_id) .read() - .ok_or_else(|| AccountNotRegisteredError(account_id.clone())) + .ok_or_else(|| AccountNotRegisteredError(account_id.to_owned())) } fn lock_storage( &mut self, - account_id: &AccountId, + account_id: &AccountIdRef, amount: NearToken, ) -> Result { let mut account_slot = Self::slot_account(account_id); let mut balance = account_slot .read() - .ok_or(AccountNotRegisteredError(account_id.clone()))?; + .ok_or(AccountNotRegisteredError(account_id.to_owned()))?; balance.available = balance .available .checked_sub(amount) .ok_or(InsufficientBalanceError { - account_id: account_id.clone(), + account_id: account_id.to_owned(), attempted_to_use: amount, available: balance.available, })?; @@ -250,14 +247,14 @@ impl Nep145Controller for T { fn unlock_storage( &mut self, - account_id: &AccountId, + account_id: &AccountIdRef, amount: NearToken, ) -> Result { let mut account_slot = Self::slot_account(account_id); let mut balance = account_slot .read() - .ok_or(AccountNotRegisteredError(account_id.clone()))?; + .ok_or(AccountNotRegisteredError(account_id.to_owned()))?; balance.available = { let new_available = balance @@ -266,7 +263,7 @@ impl Nep145Controller for T { .unwrap_or_else(|| env::panic_str(PANIC_MESSAGE_STORAGE_AVAILABLE_OVERFLOW)); if new_available > balance.total { - return Err(ExcessiveUnlockError(account_id.clone()).into()); + return Err(ExcessiveUnlockError(account_id.to_owned()).into()); } new_available @@ -279,7 +276,7 @@ impl Nep145Controller for T { fn deposit_to_storage_account( &mut self, - account_id: &AccountId, + account_id: &AccountIdRef, amount: NearToken, ) -> Result { let mut account_slot = Self::slot_account(account_id); @@ -296,7 +293,7 @@ impl Nep145Controller for T { if new_total < bounds.min { return Err(MinimumBalanceUnderrunError { - account_id: account_id.clone(), + account_id: account_id.to_owned(), minimum_balance: bounds.min, } .into()); @@ -305,7 +302,7 @@ impl Nep145Controller for T { if let Some(maximum_balance) = bounds.max { if new_total > maximum_balance { return Err(MaximumBalanceOverrunError { - account_id: account_id.clone(), + account_id: account_id.to_owned(), maximum_balance, } .into()); @@ -327,21 +324,21 @@ impl Nep145Controller for T { fn withdraw_from_storage_account( &mut self, - account_id: &AccountId, + account_id: &AccountIdRef, amount: NearToken, ) -> Result { let mut account_slot = Self::slot_account(account_id); let mut balance = account_slot .read() - .ok_or_else(|| AccountNotRegisteredError(account_id.clone()))?; + .ok_or_else(|| AccountNotRegisteredError(account_id.to_owned()))?; balance.available = balance .available .checked_sub(amount) .ok_or_else(|| InsufficientBalanceError { - account_id: account_id.clone(), + account_id: account_id.to_owned(), available: balance.available, attempted_to_use: amount, })?; @@ -354,7 +351,7 @@ impl Nep145Controller for T { .checked_sub(amount) .filter(|&new_total| new_total >= bounds.min) .ok_or(MinimumBalanceUnderrunError { - account_id: account_id.clone(), + account_id: account_id.to_owned(), minimum_balance: bounds.min, })? }; @@ -366,18 +363,18 @@ impl Nep145Controller for T { fn unregister_storage_account( &mut self, - account_id: &AccountId, + account_id: &AccountIdRef, ) -> Result { let mut account_slot = Self::slot_account(account_id); let balance = account_slot .read() - .ok_or_else(|| AccountNotRegisteredError(account_id.clone()))?; + .ok_or_else(|| AccountNotRegisteredError(account_id.to_owned()))?; match balance.total.checked_sub(balance.available) { Some(locked_balance) if !locked_balance.is_zero() => { return Err(UnregisterWithLockedBalanceError { - account_id: account_id.clone(), + account_id: account_id.to_owned(), locked_balance, } .into()) @@ -393,16 +390,16 @@ impl Nep145Controller for T { fn force_unregister_storage_account( &mut self, - account_id: &AccountId, + account_id: &AccountIdRef, ) -> Result { let mut account_slot = Self::slot_account(account_id); let balance = account_slot .read() - .ok_or_else(|| AccountNotRegisteredError(account_id.clone()))?; + .ok_or_else(|| AccountNotRegisteredError(account_id.to_owned()))?; let action = Nep145ForceUnregister { - account_id, + account_id: account_id.into(), balance, }; diff --git a/src/standard/nep148.rs b/src/standard/nep148.rs index e5274c3..0672732 100644 --- a/src/standard/nep148.rs +++ b/src/standard/nep148.rs @@ -15,7 +15,7 @@ pub const ERR_METADATA_UNSET: &str = "NEP-148 metadata is not set"; /// NEP-148-compatible metadata struct #[derive(Eq, PartialEq, Clone, Debug)] #[near(serializers = [borsh, json])] -pub struct FungibleTokenMetadata { +pub struct ContractMetadata { /// Version of the NEP-148 spec pub spec: String, /// Human-friendly name of the token contract @@ -35,7 +35,7 @@ pub struct FungibleTokenMetadata { pub decimals: u8, } -impl FungibleTokenMetadata { +impl ContractMetadata { /// Creates a new metadata struct. pub fn new(name: String, symbol: String, decimals: u8) -> Self { Self { @@ -106,7 +106,7 @@ pub trait Nep148ControllerInternal { } /// Returns the storage slot for NEP-148 metadata. - fn metadata() -> Slot { + fn metadata() -> Slot { Self::root().field(StorageKey::Metadata) } } @@ -118,20 +118,20 @@ pub trait Nep148Controller { /// # Panics /// /// Panics if the metadata has not been set. - fn get_metadata(&self) -> FungibleTokenMetadata; + fn get_metadata(&self) -> ContractMetadata; /// Sets the metadata struct for this contract. - fn set_metadata(&mut self, metadata: &FungibleTokenMetadata); + fn set_metadata(&mut self, metadata: &ContractMetadata); } impl Nep148Controller for T { - fn get_metadata(&self) -> FungibleTokenMetadata { + fn get_metadata(&self) -> ContractMetadata { Self::metadata() .read() .unwrap_or_else(|| env::panic_str(ERR_METADATA_UNSET)) } - fn set_metadata(&mut self, metadata: &FungibleTokenMetadata) { + fn set_metadata(&mut self, metadata: &ContractMetadata) { Self::metadata().set(Some(metadata)); } } @@ -141,12 +141,12 @@ mod ext { use near_sdk::ext_contract; - use super::FungibleTokenMetadata; + use super::ContractMetadata; /// Contract that supports the NEP-148 metadata standard #[ext_contract(ext_nep148)] pub trait Nep148 { /// Returns the metadata struct for this contract. - fn ft_metadata(&self) -> FungibleTokenMetadata; + fn ft_metadata(&self) -> ContractMetadata; } } diff --git a/src/standard/nep171/action.rs b/src/standard/nep171/action.rs index a7ac0eb..6f6d055 100644 --- a/src/standard/nep171/action.rs +++ b/src/standard/nep171/action.rs @@ -3,53 +3,125 @@ //! Used when calling various functions on [`Nep171Controller`]. Also used when //! implementing [`Hook`]s for the NEP-171 component. +use std::borrow::Cow; + use super::*; -use near_sdk::{borsh::BorshSerialize, serde::Serialize}; /// NEP-171 mint action. -#[derive(Serialize, BorshSerialize, Clone, Debug, PartialEq, Eq)] -#[serde(crate = "near_sdk::serde")] -#[borsh(crate = "near_sdk::borsh")] +#[derive(Clone, Debug, PartialEq, Eq)] +#[near] pub struct Nep171Mint<'a> { /// Token IDs to mint. - pub token_ids: &'a [TokenId], + pub token_ids: Vec, /// Account ID of the receiver. - pub receiver_id: &'a AccountId, + pub receiver_id: Cow<'a, AccountIdRef>, /// Optional memo string. - pub memo: Option<&'a str>, + pub memo: Option>, +} + +impl<'a> Nep171Mint<'a> { + /// Create a new mint action. + pub fn new(token_ids: Vec, receiver_id: impl Into>) -> Self { + Self { + token_ids, + receiver_id: receiver_id.into(), + memo: None, + } + } + + /// Add a memo string. + pub fn memo(self, memo: impl Into>) -> Self { + Self { + memo: Some(memo.into()), + ..self + } + } } /// NEP-171 burn action. -#[derive(Serialize, BorshSerialize, Clone, Debug, PartialEq, Eq)] -#[serde(crate = "near_sdk::serde")] -#[borsh(crate = "near_sdk::borsh")] +#[derive(Clone, Debug, PartialEq, Eq)] +#[near] pub struct Nep171Burn<'a> { /// Token IDs to burn. - pub token_ids: &'a [TokenId], + pub token_ids: Vec, /// Account ID of the owner. - pub owner_id: &'a AccountId, + pub owner_id: Cow<'a, AccountIdRef>, /// Optional memo string. - pub memo: Option<&'a str>, + pub memo: Option>, +} + +impl<'a> Nep171Burn<'a> { + /// Create a new burn action. + pub fn new(token_ids: Vec, owner_id: impl Into>) -> Self { + Self { + token_ids, + owner_id: owner_id.into(), + memo: None, + } + } + + /// Add a memo string. + pub fn memo(self, memo: impl Into>) -> Self { + Self { + memo: Some(memo.into()), + ..self + } + } } /// Transfer metadata generic over both types of transfer (`nft_transfer` and /// `nft_transfer_call`). -#[derive(Serialize, BorshSerialize, PartialEq, Eq, Clone, Debug, Hash)] -#[serde(crate = "near_sdk::serde")] -#[borsh(crate = "near_sdk::borsh")] +#[derive(PartialEq, Eq, Clone, Debug, Hash)] +#[near] pub struct Nep171Transfer<'a> { /// Why is this sender allowed to perform this transfer? pub authorization: Nep171TransferAuthorization, /// Sending account ID. - pub sender_id: &'a AccountId, + pub sender_id: Cow<'a, AccountIdRef>, /// Receiving account ID. - pub receiver_id: &'a AccountId, + pub receiver_id: Cow<'a, AccountIdRef>, /// Token ID. - pub token_id: &'a TokenId, + pub token_id: TokenId, /// Optional memo string. - pub memo: Option<&'a str>, + pub memo: Option>, /// Message passed to contract located at `receiver_id` in the case of `nft_transfer_call`. - pub msg: Option<&'a str>, + pub msg: Option>, /// `true` if the transfer is a revert for a `nft_transfer_call`. pub revert: bool, } + +impl<'a> Nep171Transfer<'a> { + /// Create a new transfer action. + pub fn new( + token_id: TokenId, + sender_id: impl Into>, + receiver_id: impl Into>, + authorization: Nep171TransferAuthorization, + ) -> Self { + Self { + authorization, + sender_id: sender_id.into(), + receiver_id: receiver_id.into(), + token_id, + memo: None, + msg: None, + revert: false, + } + } + + /// Add a memo string. + pub fn memo(self, memo: impl Into>) -> Self { + Self { + memo: Some(memo.into()), + ..self + } + } + + /// Add a message string. + pub fn msg(self, msg: impl Into>) -> Self { + Self { + msg: Some(msg.into()), + ..self + } + } +} diff --git a/src/standard/nep171/event.rs b/src/standard/nep171/event.rs index ee6cd35..5e1b753 100644 --- a/src/standard/nep171/event.rs +++ b/src/standard/nep171/event.rs @@ -1,6 +1,11 @@ //! Event log metadata & associated structures. -use near_sdk::{serde::Serialize, AccountId}; +use std::borrow::Cow; + +use near_sdk::{ + serde::{Deserialize, Serialize}, + AccountIdRef, +}; use near_sdk_contract_tools_macros::event; /// NEP-171 standard events. @@ -11,82 +16,82 @@ use near_sdk_contract_tools_macros::event; version = "1.2.0" )] #[derive(Debug, Clone)] -pub enum Nep171Event { +pub enum Nep171Event<'a> { /// Emitted when a token is newly minted. - NftMint(Vec), + NftMint(Vec>), /// Emitted when a token is transferred between two parties. - NftTransfer(Vec), + NftTransfer(Vec>), /// Emitted when a token is burned. - NftBurn(Vec), + NftBurn(Vec>), /// Emitted when the metadata associated with an NFT contract is updated. - NftMetadataUpdate(Vec), + NftMetadataUpdate(Vec>), /// Emitted when the metadata associated with an NFT contract is updated. - ContractMetadataUpdate(Vec), + ContractMetadataUpdate(Vec>), } /// Tokens minted to a single owner. -#[derive(Serialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(crate = "near_sdk::serde")] -pub struct NftMintLog { +pub struct NftMintLog<'a> { /// To whom were the new tokens minted? - pub owner_id: AccountId, + pub owner_id: Cow<'a, AccountIdRef>, /// Which tokens were minted? - pub token_ids: Vec, + pub token_ids: Vec>, /// Additional mint information. #[serde(skip_serializing_if = "Option::is_none")] - pub memo: Option, + pub memo: Option>, } /// Tokens are transferred from one account to another. -#[derive(Serialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(crate = "near_sdk::serde")] -pub struct NftTransferLog { +pub struct NftTransferLog<'a> { /// NEP-178 authorized account ID. #[serde(skip_serializing_if = "Option::is_none")] - pub authorized_id: Option, + pub authorized_id: Option>, /// Account ID of the previous owner. - pub old_owner_id: AccountId, + pub old_owner_id: Cow<'a, AccountIdRef>, /// Account ID of the new owner. - pub new_owner_id: AccountId, + pub new_owner_id: Cow<'a, AccountIdRef>, /// IDs of the transferred tokens. - pub token_ids: Vec, + pub token_ids: Vec>, /// Additional transfer information. #[serde(skip_serializing_if = "Option::is_none")] - pub memo: Option, + pub memo: Option>, } /// Tokens are burned from a single holder. -#[derive(Serialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(crate = "near_sdk::serde")] -pub struct NftBurnLog { +pub struct NftBurnLog<'a> { /// What is the ID of the account from which the tokens were burned? - pub owner_id: AccountId, + pub owner_id: Cow<'a, AccountIdRef>, /// IDs of the burned tokens. - pub token_ids: Vec, + pub token_ids: Vec>, /// NEP-178 authorized account ID. #[serde(skip_serializing_if = "Option::is_none")] - pub authorized_id: Option, + pub authorized_id: Option>, /// Additional burn information. #[serde(skip_serializing_if = "Option::is_none")] - pub memo: Option, + pub memo: Option>, } /// Token metadata update. -#[derive(Serialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(crate = "near_sdk::serde")] -pub struct NftMetadataUpdateLog { +pub struct NftMetadataUpdateLog<'a> { /// IDs of the updated tokens. - pub token_ids: Vec, + pub token_ids: Vec>, /// Additional update information. #[serde(skip_serializing_if = "Option::is_none")] - pub memo: Option, + pub memo: Option>, } /// Contract metadata update. -#[derive(Serialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(crate = "near_sdk::serde")] -pub struct NftContractMetadataUpdateLog { +pub struct NftContractMetadataUpdateLog<'a> { /// Additional update information. #[serde(skip_serializing_if = "Option::is_none")] - pub memo: Option, + pub memo: Option>, } diff --git a/src/standard/nep171/hooks.rs b/src/standard/nep171/hooks.rs index 2ee14cb..2348d08 100644 --- a/src/standard/nep171/hooks.rs +++ b/src/standard/nep171/hooks.rs @@ -21,14 +21,13 @@ where f: impl FnOnce(&mut C) -> R, ) -> R { let token_ids = - contract.with_tokens_for_owner(action.account_id, |t| t.iter().collect::>()); + contract.with_tokens_for_owner(&action.account_id, |t| t.iter().collect::>()); contract - .burn(&Nep171Burn { - token_ids: &token_ids, - owner_id: action.account_id, - memo: Some("storage forced unregistration"), - }) + .burn( + &Nep171Burn::new(token_ids, action.account_id.clone()) + .memo("storage forced unregistration"), + ) .unwrap_or_else(|e| { near_sdk::env::panic_str(&format!( "Failed to burn tokens during forced unregistration: {e}", diff --git a/src/standard/nep171/mod.rs b/src/standard/nep171/mod.rs index 290614c..6183801 100644 --- a/src/standard/nep171/mod.rs +++ b/src/standard/nep171/mod.rs @@ -43,7 +43,7 @@ use near_sdk::{ borsh::BorshSerialize, near, serde::{Deserialize, Serialize}, - AccountId, BorshStorageKey, Gas, + AccountId, AccountIdRef, BorshStorageKey, Gas, }; use crate::{hook::Hook, slot::Slot, standard::nep297::Event, DefaultStorageKey}; @@ -158,7 +158,7 @@ pub trait Nep171Controller { /// - Transferring a token "from" an account that does not own it. /// - Creating token IDs that did not previously exist. /// - Transferring a token to the account that already owns it. - fn transfer_unchecked(&mut self, token_ids: &[TokenId], receiver_id: &AccountId); + fn transfer_unchecked(&mut self, token_ids: &[TokenId], receiver_id: &AccountIdRef); /// Mints a new token `token_id` to `owner_id`. Emits events and runs /// relevant hooks. @@ -166,7 +166,7 @@ pub trait Nep171Controller { /// Mints a new token `token_id` to `owner_id` without checking if the /// token already exists. Does not emit events or run hooks. - fn mint_unchecked(&mut self, token_ids: &[TokenId], owner_id: &AccountId); + fn mint_unchecked(&mut self, token_ids: &[TokenId], owner_id: &AccountIdRef); /// Burns tokens `token_ids` owned by `current_owner_id`. Emits events and /// runs relevant hooks. @@ -214,16 +214,16 @@ impl CheckExternalTransfer for DefaultCheckExternalTrans ) -> Result { let owner_id = contract - .token_owner(transfer.token_id) + .token_owner(&transfer.token_id) .ok_or_else(|| TokenDoesNotExistError { token_id: transfer.token_id.clone(), })?; match transfer.authorization { Nep171TransferAuthorization::Owner => { - if transfer.sender_id != &owner_id { + if transfer.sender_id.as_ref() != owner_id { return Err(TokenNotOwnedByExpectedOwnerError { - expected_owner_id: transfer.sender_id.clone(), + expected_owner_id: transfer.sender_id.clone().into(), owner_id, token_id: transfer.token_id.clone(), } @@ -233,7 +233,7 @@ impl CheckExternalTransfer for DefaultCheckExternalTrans Nep171TransferAuthorization::ApprovalId(approval_id) => { return Err(SenderNotApprovedError { owner_id, - sender_id: transfer.sender_id.clone(), + sender_id: transfer.sender_id.clone().into(), token_id: transfer.token_id.clone(), approval_id, } @@ -241,7 +241,7 @@ impl CheckExternalTransfer for DefaultCheckExternalTrans } } - if transfer.receiver_id == &owner_id { + if transfer.receiver_id.as_ref() == owner_id { return Err(TokenReceiverIsCurrentOwnerError { owner_id, token_id: transfer.token_id.clone(), @@ -266,16 +266,16 @@ impl Nep171Controller for T { Ok(current_owner_id) => { Self::TransferHook::hook(self, transfer, |contract| { contract.transfer_unchecked( - std::array::from_ref(transfer.token_id), - transfer.receiver_id, + std::array::from_ref(&transfer.token_id), + &transfer.receiver_id, ); Nep171Event::NftTransfer(vec![NftTransferLog { authorized_id: None, - old_owner_id: current_owner_id, + old_owner_id: current_owner_id.into(), new_owner_id: transfer.receiver_id.clone(), - token_ids: vec![transfer.token_id.clone()], - memo: transfer.memo.map(ToString::to_string), + token_ids: vec![transfer.token_id.clone().into()], + memo: transfer.memo.clone(), }]) .emit(); }); @@ -286,17 +286,17 @@ impl Nep171Controller for T { } } - fn transfer_unchecked(&mut self, token_ids: &[TokenId], receiver_id: &AccountId) { + fn transfer_unchecked(&mut self, token_ids: &[TokenId], receiver_id: &AccountIdRef) { for token_id in token_ids { let mut slot = Self::slot_token_owner(token_id); - slot.write(receiver_id); + slot.write_deref(receiver_id); } } - fn mint_unchecked(&mut self, token_ids: &[TokenId], owner_id: &AccountId) { + fn mint_unchecked(&mut self, token_ids: &[TokenId], owner_id: &AccountIdRef) { token_ids.iter().for_each(|token_id| { let mut slot = Self::slot_token_owner(token_id); - slot.write(owner_id); + slot.write_deref(owner_id); }); } @@ -305,7 +305,7 @@ impl Nep171Controller for T { return Ok(()); } - for token_id in action.token_ids { + for token_id in &action.token_ids { let slot = Self::slot_token_owner(token_id); if slot.exists() { return Err(TokenAlreadyExistsError { @@ -316,12 +316,12 @@ impl Nep171Controller for T { } Self::MintHook::hook(self, action, |contract| { - contract.mint_unchecked(action.token_ids, action.receiver_id); + contract.mint_unchecked(&action.token_ids, &action.receiver_id); Nep171Event::NftMint(vec![NftMintLog { - token_ids: action.token_ids.iter().map(ToString::to_string).collect(), + token_ids: action.token_ids.iter().map(Into::into).collect(), owner_id: action.receiver_id.clone(), - memo: action.memo.map(ToString::to_string), + memo: action.memo.clone(), }]) .emit(); @@ -334,32 +334,32 @@ impl Nep171Controller for T { return Ok(()); } - for token_id in action.token_ids { + for token_id in &action.token_ids { if let Some(actual_owner_id) = self.token_owner(token_id) { - if &actual_owner_id != action.owner_id { + if actual_owner_id != action.owner_id.as_ref() { return Err(TokenNotOwnedByExpectedOwnerError { - expected_owner_id: action.owner_id.clone(), + expected_owner_id: action.owner_id.clone().into(), owner_id: actual_owner_id, - token_id: (*token_id).clone(), + token_id: token_id.clone(), } .into()); } } else { return Err(TokenDoesNotExistError { - token_id: (*token_id).clone(), + token_id: token_id.clone(), } .into()); } } Self::BurnHook::hook(self, action, |contract| { - contract.burn_unchecked(action.token_ids); + contract.burn_unchecked(&action.token_ids); Nep171Event::NftBurn(vec![NftBurnLog { - token_ids: action.token_ids.iter().map(ToString::to_string).collect(), + token_ids: action.token_ids.iter().map(Into::into).collect(), owner_id: action.owner_id.clone(), authorized_id: None, - memo: action.memo.map(ToString::to_string), + memo: action.memo.clone(), }]) .emit(); diff --git a/src/standard/nep177.rs b/src/standard/nep177.rs index 7662e4b..bda1770 100644 --- a/src/standard/nep177.rs +++ b/src/standard/nep177.rs @@ -3,7 +3,9 @@ //! Reference: use std::error::Error; -use near_sdk::{borsh::BorshSerialize, env, json_types::U64, near, AccountId, BorshStorageKey}; +use near_sdk::{ + borsh::BorshSerialize, env, json_types::U64, near, AccountId, AccountIdRef, BorshStorageKey, +}; use thiserror::Error; use crate::{ @@ -226,31 +228,35 @@ pub trait Nep177Controller { /// Mint a new token with metadata. fn mint_with_metadata( &mut self, - token_id: TokenId, - owner_id: AccountId, - metadata: TokenMetadata, + token_id: &TokenId, + owner_id: &AccountIdRef, + metadata: &TokenMetadata, ) -> Result<(), Nep171MintError>; /// Burn a token with metadata. fn burn_with_metadata( &mut self, - token_id: TokenId, + token_id: &TokenId, owner_id: &AccountId, ) -> Result<(), Nep171BurnError>; /// Sets the metadata for a token ID without checking whether the token /// exists, etc. and emits an [`Nep171Event::NftMetadataUpdate`] event. - fn set_token_metadata_unchecked(&mut self, token_id: TokenId, metadata: Option); + fn set_token_metadata_unchecked( + &mut self, + token_id: &TokenId, + metadata: Option<&TokenMetadata>, + ); /// Sets the metadata for a token ID and emits an [`Nep171Event::NftMetadataUpdate`] event. fn set_token_metadata( &mut self, - token_id: TokenId, - metadata: TokenMetadata, + token_id: &TokenId, + metadata: &TokenMetadata, ) -> Result<(), UpdateTokenMetadataError>; /// Sets the contract metadata and emits an [`Nep171Event::ContractMetadataUpdate`] event. - fn set_contract_metadata(&mut self, metadata: ContractMetadata); + fn set_contract_metadata(&mut self, metadata: &ContractMetadata); /// Returns the contract metadata. fn contract_metadata(&self) -> ContractMetadata; @@ -270,62 +276,55 @@ pub enum UpdateTokenMetadataError { impl Nep177Controller for T { fn set_token_metadata( &mut self, - token_id: TokenId, - metadata: TokenMetadata, + token_id: &TokenId, + metadata: &TokenMetadata, ) -> Result<(), UpdateTokenMetadataError> { - if self.token_owner(&token_id).is_some() { + if self.token_owner(token_id).is_some() { self.set_token_metadata_unchecked(token_id, Some(metadata)); Ok(()) } else { - Err(TokenDoesNotExistError { token_id }.into()) + Err(TokenDoesNotExistError { + token_id: token_id.clone(), + } + .into()) } } - fn set_contract_metadata(&mut self, metadata: ContractMetadata) { - Self::slot_contract_metadata().set(Some(&metadata)); + fn set_contract_metadata(&mut self, metadata: &ContractMetadata) { + Self::slot_contract_metadata().set(Some(metadata)); Nep171Event::ContractMetadataUpdate(vec![NftContractMetadataUpdateLog { memo: None }]) .emit(); } fn mint_with_metadata( &mut self, - token_id: TokenId, - owner_id: AccountId, - metadata: TokenMetadata, + token_id: &TokenId, + owner_id: &AccountIdRef, + metadata: &TokenMetadata, ) -> Result<(), Nep171MintError> { - let token_ids = [token_id]; - let action = Nep171Mint { - token_ids: &token_ids, - receiver_id: &owner_id, - memo: None, - }; - self.mint(&action)?; - let [token_id] = token_ids; + self.mint(&Nep171Mint::new(vec![token_id.clone()], owner_id))?; self.set_token_metadata_unchecked(token_id, Some(metadata)); Ok(()) } fn burn_with_metadata( &mut self, - token_id: TokenId, + token_id: &TokenId, owner_id: &AccountId, ) -> Result<(), Nep171BurnError> { - let token_ids = [token_id]; - let action = Nep171Burn { - token_ids: &token_ids, - owner_id, - memo: None, - }; - self.burn(&action)?; - let [token_id] = token_ids; + self.burn(&Nep171Burn::new(vec![token_id.clone()], owner_id))?; self.set_token_metadata_unchecked(token_id, None); Ok(()) } - fn set_token_metadata_unchecked(&mut self, token_id: TokenId, metadata: Option) { - ::slot_token_metadata(&token_id).set(metadata.as_ref()); + fn set_token_metadata_unchecked( + &mut self, + token_id: &TokenId, + metadata: Option<&TokenMetadata>, + ) { + ::slot_token_metadata(token_id).set(metadata); Nep171Event::NftMetadataUpdate(vec![NftMetadataUpdateLog { - token_ids: vec![token_id], + token_ids: vec![token_id.into()], memo: None, }]) .emit(); diff --git a/src/standard/nep178/action.rs b/src/standard/nep178/action.rs index 23e4997..0cee2f3 100644 --- a/src/standard/nep178/action.rs +++ b/src/standard/nep178/action.rs @@ -4,44 +4,40 @@ //! implementing [`Hook`]s for the NEP-178 component. use super::*; -use near_sdk::{borsh::BorshSerialize, serde::Serialize}; /// NEP-178 approve action. -#[derive(Serialize, BorshSerialize, Clone, Debug, PartialEq, Eq)] -#[serde(crate = "near_sdk::serde")] -#[borsh(crate = "near_sdk::borsh")] +#[derive(Clone, Debug, PartialEq, Eq)] +#[near] pub struct Nep178Approve<'a> { /// Token ID that the target account is being approved for. - pub token_id: &'a TokenId, + pub token_id: TokenId, /// Account ID of the current owner of the token. - pub current_owner_id: &'a AccountId, + pub current_owner_id: Cow<'a, AccountIdRef>, /// Account ID of the target account. This account will be able to /// transfer the token. - pub account_id: &'a AccountId, + pub account_id: Cow<'a, AccountIdRef>, } /// NEP-178 revoke action. -#[derive(Serialize, BorshSerialize, Clone, Debug, PartialEq, Eq)] -#[serde(crate = "near_sdk::serde")] -#[borsh(crate = "near_sdk::borsh")] +#[derive(Clone, Debug, PartialEq, Eq)] +#[near] pub struct Nep178Revoke<'a> { /// Token ID that the target account will no longer be able to transfer /// (approval revoked). - pub token_id: &'a TokenId, + pub token_id: TokenId, /// Account ID of the current owner of the token. - pub current_owner_id: &'a AccountId, + pub current_owner_id: Cow<'a, AccountIdRef>, /// Account ID of the target account. This account will no longer be able /// to transfer the token. - pub account_id: &'a AccountId, + pub account_id: Cow<'a, AccountIdRef>, } /// NEP-178 revoke all action. -#[derive(Serialize, BorshSerialize, Clone, Debug, PartialEq, Eq)] -#[serde(crate = "near_sdk::serde")] -#[borsh(crate = "near_sdk::borsh")] +#[derive(Clone, Debug, PartialEq, Eq)] +#[near] pub struct Nep178RevokeAll<'a> { /// Token ID that all approvals will be revoked from. - pub token_id: &'a TokenId, + pub token_id: TokenId, /// Account ID of the current owner of the token. - pub current_owner_id: &'a AccountId, + pub current_owner_id: Cow<'a, AccountIdRef>, } diff --git a/src/standard/nep178/mod.rs b/src/standard/nep178/mod.rs index 0abb6a3..7e8d9c7 100644 --- a/src/standard/nep178/mod.rs +++ b/src/standard/nep178/mod.rs @@ -1,10 +1,11 @@ //! NEP-178 non-fungible token approval management implementation. //! //! Reference: -use std::{collections::HashMap, error::Error}; +use std::{borrow::Cow, collections::HashMap, error::Error}; use near_sdk::{ - borsh::BorshSerialize, collections::UnorderedMap, near, AccountId, BorshStorageKey, + borsh::BorshSerialize, collections::UnorderedMap, near, AccountId, AccountIdRef, + BorshStorageKey, }; use crate::{ @@ -62,7 +63,7 @@ impl Hook> for TokenApprovals {} impl Hook> for TokenApprovals { fn hook(contract: &mut C, args: &Nep171Transfer<'_>, f: impl FnOnce(&mut C) -> R) -> R { let r = f(contract); - contract.revoke_all_unchecked(args.token_id); + contract.revoke_all_unchecked(&args.token_id); r } } @@ -70,7 +71,7 @@ impl Hook> for TokenApprovals { impl Hook> for TokenApprovals { fn hook(contract: &mut C, args: &Nep171Burn<'_>, f: impl FnOnce(&mut C) -> R) -> R { let r = f(contract); - for token_id in args.token_ids { + for token_id in &args.token_ids { contract.revoke_all_unchecked(token_id); } r @@ -92,7 +93,7 @@ impl CheckExternalTransfer for TokenA Err(Nep171TransferError::SenderNotApproved(s)), ) => { let saved_approval = - contract.get_approval_id_for(transfer.token_id, transfer.sender_id); + contract.get_approval_id_for(&transfer.token_id, &transfer.sender_id); if saved_approval == Some(*approval_id) { Ok(s.owner_id) @@ -165,14 +166,14 @@ pub trait Nep178Controller { /// Approve a token without checking if the account is already approved or /// if it exceeds the maximum number of approvals. - fn approve_unchecked(&mut self, token_id: &TokenId, account_id: &AccountId) -> ApprovalId; + fn approve_unchecked(&mut self, token_id: &TokenId, account_id: &AccountIdRef) -> ApprovalId; /// Revoke approval for an account to transfer token. fn revoke(&mut self, action: &Nep178Revoke<'_>) -> Result<(), Nep178RevokeError>; /// Revoke approval for an account to transfer token without checking if /// the account is approved. - fn revoke_unchecked(&mut self, token_id: &TokenId, account_id: &AccountId); + fn revoke_unchecked(&mut self, token_id: &TokenId, account_id: &AccountIdRef); /// Revoke all approvals for a token. fn revoke_all(&mut self, action: &Nep178RevokeAll<'_>) -> Result<(), Nep178RevokeAllError>; @@ -181,8 +182,11 @@ pub trait Nep178Controller { fn revoke_all_unchecked(&mut self, token_id: &TokenId); /// Get the approval ID for an account, if it is approved for a token. - fn get_approval_id_for(&self, token_id: &TokenId, account_id: &AccountId) - -> Option; + fn get_approval_id_for( + &self, + token_id: &TokenId, + account_id: &AccountIdRef, + ) -> Option; /// Get the approvals for a token. fn get_approvals_for(&self, token_id: &TokenId) -> HashMap; @@ -193,14 +197,14 @@ impl Nep178Controller for T { type RevokeHook = T::RevokeHook; type RevokeAllHook = T::RevokeAllHook; - fn approve_unchecked(&mut self, token_id: &TokenId, account_id: &AccountId) -> ApprovalId { + fn approve_unchecked(&mut self, token_id: &TokenId, account_id: &AccountIdRef) -> ApprovalId { let mut slot = Self::slot_token_approvals(token_id); let mut approvals = slot.read().unwrap_or_else(|| TokenApprovals { next_approval_id: 0, accounts: UnorderedMap::new(Self::slot_token_approvals_unordered_map(token_id)), }); let approval_id = approvals.next_approval_id; - approvals.accounts.insert(account_id, &approval_id); + approvals.accounts.insert(&account_id.into(), &approval_id); approvals.next_approval_id += 1; // overflow unrealistic slot.write(&approvals); @@ -209,18 +213,18 @@ impl Nep178Controller for T { fn approve(&mut self, action: &Nep178Approve<'_>) -> Result { // owner check - if self.token_owner(action.token_id).as_ref() != Some(action.current_owner_id) { + if self.token_owner(&action.token_id).as_deref() != Some(action.current_owner_id.as_ref()) { return Err(UnauthorizedError { token_id: action.token_id.clone(), - account_id: action.account_id.clone(), + account_id: action.account_id.clone().into(), } .into()); } - let mut slot = Self::slot_token_approvals(action.token_id); + let mut slot = Self::slot_token_approvals(&action.token_id); let mut approvals = slot.read().unwrap_or_else(|| TokenApprovals { next_approval_id: 0, - accounts: UnorderedMap::new(Self::slot_token_approvals_unordered_map(action.token_id)), + accounts: UnorderedMap::new(Self::slot_token_approvals_unordered_map(&action.token_id)), }); if approvals.accounts.len() >= MAX_APPROVALS { @@ -231,16 +235,22 @@ impl Nep178Controller for T { } let approval_id = approvals.next_approval_id; - if approvals.accounts.get(action.account_id).is_some() { + if approvals + .accounts + .get(&AccountId::from(action.account_id.as_ref())) + .is_some() + { return Err(AccountAlreadyApprovedError { token_id: action.token_id.clone(), - account_id: action.account_id.clone(), + account_id: action.account_id.clone().into(), } .into()); } Self::ApproveHook::hook(self, action, |_| { - approvals.accounts.insert(action.account_id, &approval_id); + approvals + .accounts + .insert(&action.account_id.clone().into(), &approval_id); approvals.next_approval_id += 1; // overflow unrealistic slot.write(&approvals); @@ -248,64 +258,70 @@ impl Nep178Controller for T { }) } - fn revoke_unchecked(&mut self, token_id: &TokenId, account_id: &AccountId) { + fn revoke_unchecked(&mut self, token_id: &TokenId, account_id: &AccountIdRef) { let mut slot = Self::slot_token_approvals(token_id); let mut approvals = match slot.read() { Some(approvals) => approvals, None => return, }; - let old = approvals.accounts.remove(account_id); + let old = approvals.accounts.remove(&account_id.into()); if old.is_some() { slot.write(&approvals); } } - fn revoke(&mut self, action: &Nep178Revoke<'_>) -> Result<(), Nep178RevokeError> { + fn revoke(&mut self, action: &Nep178Revoke) -> Result<(), Nep178RevokeError> { // owner check - if self.token_owner(action.token_id).as_ref() != Some(action.current_owner_id) { + if self.token_owner(&action.token_id).as_deref() != Some(action.current_owner_id.as_ref()) { return Err(UnauthorizedError { token_id: action.token_id.clone(), - account_id: action.account_id.clone(), + account_id: action.account_id.clone().into(), } .into()); } - let mut slot = Self::slot_token_approvals(action.token_id); + let mut slot = Self::slot_token_approvals(&action.token_id); let mut approvals = slot.read().ok_or_else(|| AccountNotApprovedError { token_id: action.token_id.clone(), - account_id: action.account_id.clone(), + account_id: action.account_id.clone().into(), })?; - if approvals.accounts.get(action.account_id).is_none() { + if approvals + .accounts + .get(&AccountId::from(action.account_id.as_ref())) + .is_none() + { return Err(AccountNotApprovedError { token_id: action.token_id.clone(), - account_id: action.account_id.clone(), + account_id: action.account_id.clone().into(), } .into()); } Self::RevokeHook::hook(self, action, |_| { - approvals.accounts.remove(action.account_id); + approvals + .accounts + .remove(&AccountId::from(action.account_id.as_ref())); slot.write(&approvals); Ok(()) }) } - fn revoke_all(&mut self, action: &Nep178RevokeAll<'_>) -> Result<(), Nep178RevokeAllError> { + fn revoke_all(&mut self, action: &Nep178RevokeAll) -> Result<(), Nep178RevokeAllError> { // owner check - if self.token_owner(action.token_id).as_ref() != Some(action.current_owner_id) { + if self.token_owner(&action.token_id).as_deref() != Some(action.current_owner_id.as_ref()) { return Err(UnauthorizedError { token_id: action.token_id.clone(), - account_id: action.current_owner_id.clone(), + account_id: action.current_owner_id.clone().into(), } .into()); } Self::RevokeAllHook::hook(self, action, |contract| { - contract.revoke_all_unchecked(action.token_id); + contract.revoke_all_unchecked(&action.token_id); Ok(()) }) @@ -327,12 +343,12 @@ impl Nep178Controller for T { fn get_approval_id_for( &self, token_id: &TokenId, - account_id: &AccountId, + account_id: &AccountIdRef, ) -> Option { let slot = Self::slot_token_approvals(token_id); let approvals = slot.read()?; - approvals.accounts.get(account_id) + approvals.accounts.get(&account_id.into()) } fn get_approvals_for(&self, token_id: &TokenId) -> HashMap { @@ -342,10 +358,6 @@ impl Nep178Controller for T { None => return HashMap::default(), }; - approvals - .accounts - .into_iter() - .map(|(k, v)| (k.clone(), v)) - .collect() + approvals.accounts.into_iter().collect() } } diff --git a/src/standard/nep181.rs b/src/standard/nep181.rs index 8acf749..85e25e4 100644 --- a/src/standard/nep181.rs +++ b/src/standard/nep181.rs @@ -3,7 +3,9 @@ //! Reference: use std::borrow::Cow; -use near_sdk::{borsh::BorshSerialize, collections::UnorderedSet, env, AccountId, BorshStorageKey}; +use near_sdk::{ + borsh::BorshSerialize, collections::UnorderedSet, env, AccountId, AccountIdRef, BorshStorageKey, +}; use crate::{hook::Hook, slot::Slot, standard::nep171::*, DefaultStorageKey}; @@ -15,7 +17,7 @@ pub struct TokenEnumeration; impl Hook> for TokenEnumeration { fn hook(contract: &mut C, args: &action::Nep171Mint<'_>, f: impl FnOnce(&mut C) -> R) -> R { let r = f(contract); - contract.add_tokens_to_enumeration(args.token_ids, args.receiver_id); + contract.add_tokens_to_enumeration(&args.token_ids, &args.receiver_id); r } } @@ -30,16 +32,16 @@ impl Hook> ) -> R { let r = f(contract); let owner_id = match args.authorization { - Nep171TransferAuthorization::Owner => Cow::Borrowed(args.sender_id), - Nep171TransferAuthorization::ApprovalId(_) => Cow::Owned(contract.token_owner(args.token_id).unwrap_or_else(|| { + Nep171TransferAuthorization::Owner => args.sender_id.clone(), + Nep171TransferAuthorization::ApprovalId(_) => Cow::Owned(contract.token_owner(&args.token_id).unwrap_or_else(|| { env::panic_str(&format!("Inconsistent state: Enumeration reconciliation should only run after a token has been transferred, but token {} does not exist.", args.token_id)) })), }; contract.transfer_token_enumeration( - std::array::from_ref(args.token_id), + std::array::from_ref(&args.token_id), owner_id.as_ref(), - args.receiver_id, + &args.receiver_id, ); r } @@ -48,7 +50,7 @@ impl Hook> impl Hook> for TokenEnumeration { fn hook(contract: &mut C, args: &action::Nep171Burn<'_>, f: impl FnOnce(&mut C) -> R) -> R { let r = f(contract); - contract.remove_tokens_from_enumeration(args.token_ids, args.owner_id); + contract.remove_tokens_from_enumeration(&args.token_ids, &args.owner_id); r } } @@ -57,7 +59,7 @@ impl Hook> for #[borsh(crate = "near_sdk::borsh")] enum StorageKey<'a> { Tokens, - OwnerTokens(&'a AccountId), + OwnerTokens(&'a AccountIdRef), } /// Internal functions for [`Nep181Controller`]. @@ -73,7 +75,7 @@ pub trait Nep181ControllerInternal { } /// Storage slot for tokens owned by an account. - fn slot_owner_tokens(owner_id: &AccountId) -> Slot> { + fn slot_owner_tokens(owner_id: &AccountIdRef) -> Slot> { Self::root().field(StorageKey::OwnerTokens(owner_id)) } } @@ -86,7 +88,7 @@ pub trait Nep181Controller { /// /// Does not perform consistency checks. May cause inconsistent state if /// the same token ID is added to the enumeration multiple times. - fn add_tokens_to_enumeration(&mut self, token_ids: &[TokenId], owner_id: &AccountId); + fn add_tokens_to_enumeration(&mut self, token_ids: &[TokenId], owner_id: &AccountIdRef); /// Remove tokens from enumeration. /// @@ -94,7 +96,7 @@ pub trait Nep181Controller { /// /// Does not perform consistency checks. May cause inconsistent state if /// any of the token IDs are not currently enumerated (owned) by `owner_id`. - fn remove_tokens_from_enumeration(&mut self, token_ids: &[TokenId], owner_id: &AccountId); + fn remove_tokens_from_enumeration(&mut self, token_ids: &[TokenId], owner_id: &AccountIdRef); /// Transfer tokens between owners. /// @@ -107,8 +109,8 @@ pub trait Nep181Controller { fn transfer_token_enumeration( &mut self, token_ids: &[TokenId], - from_owner_id: &AccountId, - to_owner_id: &AccountId, + from_owner_id: &AccountIdRef, + to_owner_id: &AccountIdRef, ); /// Total number of tokens in enumeration. @@ -121,13 +123,13 @@ pub trait Nep181Controller { /// account. fn with_tokens_for_owner( &self, - owner_id: &AccountId, + owner_id: &AccountIdRef, f: impl FnOnce(&UnorderedSet) -> T, ) -> T; } impl Nep181Controller for T { - fn add_tokens_to_enumeration(&mut self, token_ids: &[TokenId], owner_id: &AccountId) { + fn add_tokens_to_enumeration(&mut self, token_ids: &[TokenId], owner_id: &AccountIdRef) { let mut all_tokens_slot = Self::slot_tokens(); let mut all_tokens = all_tokens_slot .read() @@ -147,7 +149,7 @@ impl Nep181Controller for T { owner_tokens_slot.write(&owner_tokens); } - fn remove_tokens_from_enumeration(&mut self, token_ids: &[TokenId], owner_id: &AccountId) { + fn remove_tokens_from_enumeration(&mut self, token_ids: &[TokenId], owner_id: &AccountIdRef) { let mut all_tokens_slot = Self::slot_tokens(); if let Some(mut all_tokens) = all_tokens_slot.read() { for token_id in token_ids { @@ -168,8 +170,8 @@ impl Nep181Controller for T { fn transfer_token_enumeration( &mut self, token_ids: &[TokenId], - from_owner_id: &AccountId, - to_owner_id: &AccountId, + from_owner_id: &AccountIdRef, + to_owner_id: &AccountIdRef, ) { let mut from_owner_tokens_slot = Self::slot_owner_tokens(from_owner_id); if let Some(mut from_owner_tokens) = from_owner_tokens_slot.read() { @@ -205,7 +207,7 @@ impl Nep181Controller for T { fn with_tokens_for_owner( &self, - owner_id: &AccountId, + owner_id: &AccountIdRef, f: impl FnOnce(&UnorderedSet) -> U, ) -> U { f(&Self::slot_owner_tokens(owner_id) diff --git a/src/standard/nep297.rs b/src/standard/nep297.rs index 572aa4c..79def6f 100644 --- a/src/standard/nep297.rs +++ b/src/standard/nep297.rs @@ -1,13 +1,16 @@ //! Helpers for `#[derive(near_sdk_contract_tools::Nep297)]` -use near_sdk::{serde::Serialize, serde_json}; +use std::borrow::Cow; + +use near_sdk::{ + serde::{self, Deserialize, Serialize}, + serde_json, +}; /// Emit events according to the [NEP-297 event standard](https://nomicon.io/Standards/EventsFormat). /// /// # Examples /// -/// ## Normal events -/// /// ``` /// use near_sdk_contract_tools::event; /// @@ -27,10 +30,10 @@ use near_sdk::{serde::Serialize, serde_json}; /// e.emit(); /// ``` pub trait Event { - /// Converts the event into an NEP-297 event-formatted string + /// Converts the event into an NEP-297 event-formatted string. fn to_event_string(&self) -> String; - /// Emits the event string to the blockchain + /// Emits the event string to the blockchain. fn emit(&self); } @@ -60,26 +63,92 @@ where } } -/// This type can be converted into an [`EventLog`] struct +/// This type can be converted into an [`EventLog`] struct. pub trait ToEventLog { - /// Metadata associated with the event - type Data: ?Sized; + /// Metadata associated with the event. + type Data; - /// Retrieves the event log before serialization + /// Retrieves the event log before serialization. fn to_event_log(&self) -> EventLog<&Self::Data>; } /// NEP-297 Event Log Data /// -#[derive(Serialize, Clone, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] #[serde(crate = "near_sdk::serde")] -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 struct EventLog<'a, T> { + /// Name of the event standard, e.g. `"nep171"`. + pub standard: Cow<'a, str>, + /// Version of the standard, e.g. `"1.0.0"`. + pub version: Cow<'a, str>, + /// Name of the particular event, e.g. `"nft_mint"`, `"ft_transfer"`. + pub event: Cow<'a, str>, + /// Data type of the event metadata. pub data: T, } + +impl<'de, T: Deserialize<'de>> EventLog<'de, T> { + /// Deserializes an event log from a string. + /// + /// # Errors + /// + /// Will return `Err` if the string is not a valid event log. A valid event + /// log begins with the string `"EVENT_JSON:"`, and is followed by a JSON + /// string. + pub fn from_event_log_string(s: &'de str) -> Result { + let data_str = s + .strip_prefix("EVENT_JSON:") + .ok_or(serde::de::Error::custom(serde::de::Unexpected::Str( + "EVENT_JSON:", + )))?; + let data = + serde_json::from_str::>(data_str).map_err(serde::de::Error::custom)?; + let x = Some(1); + x.as_ref(); + Ok(data) + } + + /// Converts the event log into a borrowed reference. + pub fn as_ref(&self) -> EventLog<&T> { + EventLog { + standard: Cow::Borrowed(&self.standard), + version: Cow::Borrowed(&self.version), + event: Cow::Borrowed(&self.event), + data: &self.data, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn to_and_from_event_log() { + #[derive(Debug, PartialEq, Eq)] + struct MyEvent; + + impl ToEventLog for MyEvent { + type Data = u32; + + fn to_event_log(&self) -> EventLog<&u32> { + EventLog { + standard: "nep171".into(), + version: "1.0.0".into(), + event: "nft_mint".into(), + data: &1, + } + } + } + + let event = MyEvent; + + let string = event.to_event_string(); + + assert_eq!(string, "EVENT_JSON:{\"standard\":\"nep171\",\"version\":\"1.0.0\",\"event\":\"nft_mint\",\"data\":1}"); + + let from_event_log_str = EventLog::::from_event_log_string(&string).unwrap(); + + assert_eq!(from_event_log_str.as_ref(), event.to_event_log()); + } +} diff --git a/tests/macros/mod.rs b/tests/macros/mod.rs index fbc0dc6..33c777e 100644 --- a/tests/macros/mod.rs +++ b/tests/macros/mod.rs @@ -412,7 +412,7 @@ mod pausable_fungible_token { pub fn new() -> Self { let mut contract = Self { storage_usage: 0 }; - contract.set_metadata(&FungibleTokenMetadata::new( + contract.set_metadata(&ContractMetadata::new( "Pausable Fungible Token".into(), "PFT".into(), 18, @@ -528,11 +528,7 @@ mod owned_fungible_token { pub fn mint(&mut self, amount: U128) { Nep141Controller::mint( self, - &Nep141Mint { - amount: amount.into(), - receiver_id: &env::predecessor_account_id(), - memo: None, - }, + &Nep141Mint::new(amount.0, env::predecessor_account_id()), ) .unwrap(); } diff --git a/tests/macros/standard/fungible_token.rs b/tests/macros/standard/fungible_token.rs index b7f493c..3f936ae 100644 --- a/tests/macros/standard/fungible_token.rs +++ b/tests/macros/standard/fungible_token.rs @@ -12,7 +12,7 @@ impl MyFungibleTokenContract { let mut contract = Self {}; contract.set_metadata( - &FungibleTokenMetadata::new("My Fungible Token".into(), "MYFT".into(), 24) + &ContractMetadata::new("My Fungible Token".into(), "MYFT".into(), 24) .icon("https://example.com/icon.png".into()) .reference("https://example.com/metadata.json".into()) .reference_hash(Base64VecU8::from([97, 115, 100, 102].to_vec())), diff --git a/tests/macros/standard/nep141.rs b/tests/macros/standard/nep141.rs index 5b2f952..717a204 100644 --- a/tests/macros/standard/nep141.rs +++ b/tests/macros/standard/nep141.rs @@ -94,24 +94,14 @@ fn nep141_transfer() { assert_eq!( ft.transfers.pop(), - Some( - borsh::to_vec(&Nep141Transfer { - sender_id: &alice, - receiver_id: &bob, - amount: 50, - memo: None, - msg: None, - revert: false, - }) - .unwrap() - ) + Some(borsh::to_vec(&Nep141Transfer::new(50, alice.clone(), bob.clone())).unwrap()) ); let expected_hook_execution_order = vec!["before_transfer", "after_transfer"]; let actual_hook_execution_order = ft.hooks.to_vec(); assert_eq!(expected_hook_execution_order, actual_hook_execution_order); - assert_eq!(ft.ft_balance_of(alice.clone()).0, 50); - assert_eq!(ft.ft_balance_of(bob.clone()).0, 70); + assert_eq!(ft.ft_balance_of(alice).0, 50); + assert_eq!(ft.ft_balance_of(bob).0, 70); assert_eq!(ft.ft_total_supply().0, 120); } diff --git a/tests/macros/standard/nep148.rs b/tests/macros/standard/nep148.rs index 37fa822..054772f 100644 --- a/tests/macros/standard/nep148.rs +++ b/tests/macros/standard/nep148.rs @@ -12,7 +12,7 @@ impl DerivesFTMetadata { let mut contract = Self {}; contract.set_metadata( - &FungibleTokenMetadata::new("Test Fungible Token".into(), "TFT".into(), 18) + &ContractMetadata::new("Test Fungible Token".into(), "TFT".into(), 18) .icon("https://example.com/icon.png".into()) .reference("https://example.com/metadata.json".into()) .reference_hash(Base64VecU8::from([97, 115, 100, 102].to_vec())), diff --git a/tests/macros/standard/nep171/hooks.rs b/tests/macros/standard/nep171/hooks.rs index 76fd665..5fcf15a 100644 --- a/tests/macros/standard/nep171/hooks.rs +++ b/tests/macros/standard/nep171/hooks.rs @@ -11,7 +11,7 @@ pub struct Contract { impl Hook> for Contract { fn hook( contract: &mut Contract, - args: &Nep171Transfer<'_>, + args: &Nep171Transfer, f: impl FnOnce(&mut Contract) -> R, ) -> R { log!( diff --git a/tests/macros/standard/nep171/manual_integration.rs b/tests/macros/standard/nep171/manual_integration.rs index 69a8185..7f177c1 100644 --- a/tests/macros/standard/nep171/manual_integration.rs +++ b/tests/macros/standard/nep171/manual_integration.rs @@ -41,7 +41,7 @@ impl Contract { pub fn new() -> Self { let mut contract = Self { next_token_id: 0 }; - contract.set_contract_metadata(nep177::ContractMetadata::new( + contract.set_contract_metadata(&nep177::ContractMetadata::new( "My NFT".to_string(), "MYNFT".to_string(), None, @@ -59,9 +59,9 @@ impl Contract { self.next_token_id += 1; Nep177Controller::mint_with_metadata( self, - token_id.clone(), - env::predecessor_account_id(), - nep177::TokenMetadata::new() + &token_id, + &env::predecessor_account_id(), + &nep177::TokenMetadata::new() .title(format!("Token {token_id}")) .description(format!("This is token {token_id}.")), ) diff --git a/tests/macros/standard/nep171/mod.rs b/tests/macros/standard/nep171/mod.rs index 2a8446a..82b8046 100644 --- a/tests/macros/standard/nep171/mod.rs +++ b/tests/macros/standard/nep171/mod.rs @@ -49,7 +49,7 @@ mod full_no_hooks { Nep145Controller::deposit_to_storage_account(&mut n, &alice, NearToken::from_near(1)) .unwrap(); - n.mint_with_metadata(token_id.clone(), alice, TokenMetadata::new().title("Title")) + n.mint_with_metadata(&token_id, &alice, &TokenMetadata::new().title("Title")) .unwrap(); let nft_tok = n.nft_token(token_id); @@ -95,14 +95,11 @@ impl NonFungibleToken { } pub fn mint(&mut self, token_id: TokenId, receiver_id: AccountId) { - let action = Nep171Mint { - token_ids: &[token_id], - receiver_id: &receiver_id, - memo: None, - }; - Nep171Controller::mint(self, &action).unwrap_or_else(|e| { - env::panic_str(&format!("Mint failed: {e:?}")); - }); + Nep171Controller::mint(self, &Nep171Mint::new(vec![token_id], receiver_id)).unwrap_or_else( + |e| { + env::panic_str(&format!("Mint failed: {e:?}")); + }, + ); } } @@ -170,9 +167,9 @@ mod tests { vec![Nep171Event::NftTransfer(vec![NftTransferLog { memo: None, authorized_id: None, - old_owner_id: account_alice.clone(), - new_owner_id: account_bob.clone(), - token_ids: vec![token_id.to_string()] + old_owner_id: account_alice.into(), + new_owner_id: account_bob.into(), + token_ids: vec![token_id.into()] }]) .to_event_string()] ); diff --git a/tests/macros/standard/nep171/no_hooks.rs b/tests/macros/standard/nep171/no_hooks.rs index 5c583ae..9917e3d 100644 --- a/tests/macros/standard/nep171/no_hooks.rs +++ b/tests/macros/standard/nep171/no_hooks.rs @@ -13,16 +13,11 @@ impl Contract { let token_id = format!("token_{}", self.next_token_id); self.next_token_id += 1; - let token_ids = [token_id]; - let action = Nep171Mint { - token_ids: &token_ids, - receiver_id: &env::predecessor_account_id(), - memo: None, - }; - Nep171Controller::mint(self, &action) - .unwrap_or_else(|e| env::panic_str(&format!("Minting failed: {e}"))); - - let [token_id] = token_ids; + Nep171Controller::mint( + self, + &Nep171Mint::new(vec![token_id.clone()], env::predecessor_account_id()), + ) + .unwrap_or_else(|e| env::panic_str(&format!("Minting failed: {e}"))); token_id } diff --git a/tests/macros/standard/nep171/non_fungible_token.rs b/tests/macros/standard/nep171/non_fungible_token.rs index 375c341..c960674 100644 --- a/tests/macros/standard/nep171/non_fungible_token.rs +++ b/tests/macros/standard/nep171/non_fungible_token.rs @@ -19,7 +19,7 @@ impl Contract { pub fn new() -> Self { let mut contract = Self { next_token_id: 0 }; - contract.set_contract_metadata(ContractMetadata::new( + contract.set_contract_metadata(&ContractMetadata::new( "My NFT".to_string(), "MYNFT".to_string(), None, @@ -36,9 +36,9 @@ impl Contract { let token_id = format!("token_{}", self.next_token_id); self.next_token_id += 1; self.mint_with_metadata( - token_id.clone(), - env::predecessor_account_id(), - TokenMetadata::new() + &token_id, + &env::predecessor_account_id(), + &TokenMetadata::new() .title(format!("Token {token_id}")) .description(format!("This is token {token_id}.")), ) diff --git a/workspaces-tests/src/bin/fungible_token.rs b/workspaces-tests/src/bin/fungible_token.rs index 329c118..74d228f 100644 --- a/workspaces-tests/src/bin/fungible_token.rs +++ b/workspaces-tests/src/bin/fungible_token.rs @@ -23,7 +23,7 @@ impl Contract { blobs: Vector::new(b"b"), }; - contract.set_metadata(&FungibleTokenMetadata::new( + contract.set_metadata(&ContractMetadata::new( "My Fungible Token".into(), "MYFT".into(), 24, @@ -35,11 +35,7 @@ impl Contract { pub fn mint(&mut self, amount: U128) { Nep141Controller::mint( self, - &Nep141Mint { - amount: amount.into(), - receiver_id: &env::predecessor_account_id(), - memo: None, - }, + &Nep141Mint::new(amount.0, env::predecessor_account_id()), ) .unwrap(); } diff --git a/workspaces-tests/src/bin/non_fungible_token_full.rs b/workspaces-tests/src/bin/non_fungible_token_full.rs index c5bbf0e..7ab4a23 100644 --- a/workspaces-tests/src/bin/non_fungible_token_full.rs +++ b/workspaces-tests/src/bin/non_fungible_token_full.rs @@ -108,7 +108,7 @@ impl Contract { pub fn new() -> Self { let mut contract = Self {}; - contract.set_contract_metadata(ContractMetadata::new( + contract.set_contract_metadata(&ContractMetadata::new( "My NFT Smart Contract".to_string(), "MNSC".to_string(), None, @@ -121,10 +121,10 @@ impl Contract { let receiver = env::predecessor_account_id(); for token_id in token_ids { self.mint_with_metadata( - token_id.clone(), - receiver.clone(), - TokenMetadata::new() - .title(token_id) + &token_id, + &receiver, + &TokenMetadata::new() + .title(token_id.clone()) .description("description"), ) .unwrap_or_else(|e| env::panic_str(&format!("Failed to mint: {:#?}", e))); diff --git a/workspaces-tests/src/bin/non_fungible_token_nep171.rs b/workspaces-tests/src/bin/non_fungible_token_nep171.rs index 4b47e83..9c2e234 100644 --- a/workspaces-tests/src/bin/non_fungible_token_nep171.rs +++ b/workspaces-tests/src/bin/non_fungible_token_nep171.rs @@ -1,7 +1,7 @@ workspaces_tests::predicate!(); use near_sdk::{env, log, near, PanicOnDefault}; -use near_sdk_contract_tools::{hook::Hook, standard::nep171::*, Nep171}; +use near_sdk_contract_tools::{hook::Hook, nft::Nep171Mint, standard::nep171::*, Nep171}; #[derive(Nep171, PanicOnDefault)] #[nep171(transfer_hook = "Self")] @@ -29,12 +29,10 @@ impl Contract { } pub fn mint(&mut self, token_ids: Vec) { - let action = action::Nep171Mint { - token_ids: &token_ids, - receiver_id: &env::predecessor_account_id(), - memo: None, - }; - Nep171Controller::mint(self, &action) - .unwrap_or_else(|e| env::panic_str(&format!("Failed to mint: {:#?}", e))); + Nep171Controller::mint( + self, + &Nep171Mint::new(token_ids, env::predecessor_account_id()), + ) + .unwrap_or_else(|e| env::panic_str(&format!("Failed to mint: {:#?}", e))); } } diff --git a/workspaces-tests/tests/fungible_token.rs b/workspaces-tests/tests/fungible_token.rs index 64d6767..0350d35 100644 --- a/workspaces-tests/tests/fungible_token.rs +++ b/workspaces-tests/tests/fungible_token.rs @@ -295,7 +295,7 @@ async fn fail_run_out_of_space() { format!( "Smart contract panicked: Storage lock error: {}", InsufficientBalanceError { - account_id: alice.id().as_str().parse().unwrap(), + account_id: alice.id().clone(), available: balance.available, attempted_to_use: NearToken::from_yoctonear(100490000000000000000000), } @@ -338,8 +338,8 @@ async fn transfer_call_normal() { result.logs().to_vec(), vec![ Nep141Event::FtTransfer(vec![FtTransferData { - old_owner_id: alice.id().as_str().parse().unwrap(), - new_owner_id: bob.id().as_str().parse().unwrap(), + old_owner_id: alice.id().into(), + new_owner_id: bob.id().into(), amount: U128(10), memo: None, }]) @@ -388,16 +388,16 @@ async fn transfer_call_return() { result.logs().to_vec(), vec![ Nep141Event::FtTransfer(vec![FtTransferData { - old_owner_id: alice.id().as_str().parse().unwrap(), - new_owner_id: bob.id().as_str().parse().unwrap(), + old_owner_id: alice.id().into(), + new_owner_id: bob.id().into(), amount: U128(10), memo: None, }]) .to_event_string(), format!("Received 10 from {}", alice.id()), Nep141Event::FtTransfer(vec![FtTransferData { - old_owner_id: bob.id().as_str().parse().unwrap(), - new_owner_id: alice.id().as_str().parse().unwrap(), + old_owner_id: bob.id().into(), + new_owner_id: alice.id().into(), amount: U128(10), memo: None, }]) @@ -445,8 +445,8 @@ async fn transfer_call_inner_transfer() { result.logs().to_vec(), vec![ Nep141Event::FtTransfer(vec![FtTransferData { - old_owner_id: alice.id().as_str().parse().unwrap(), - new_owner_id: bob.id().as_str().parse().unwrap(), + old_owner_id: alice.id().into(), + new_owner_id: bob.id().into(), amount: U128(10), memo: None, }]) @@ -454,15 +454,15 @@ async fn transfer_call_inner_transfer() { format!("Received 10 from {}", alice.id()), format!("Transferring 10 to {}", charlie.id()), Nep141Event::FtTransfer(vec![FtTransferData { - old_owner_id: bob.id().as_str().parse().unwrap(), - new_owner_id: charlie.id().as_str().parse().unwrap(), + old_owner_id: bob.id().into(), + new_owner_id: charlie.id().into(), amount: U128(10), memo: None, }]) .to_event_string(), Nep141Event::FtTransfer(vec![FtTransferData { - old_owner_id: bob.id().as_str().parse().unwrap(), - new_owner_id: alice.id().as_str().parse().unwrap(), + old_owner_id: bob.id().into(), + new_owner_id: alice.id().into(), amount: U128(10), memo: None, }]) @@ -514,16 +514,16 @@ async fn transfer_call_inner_panic() { result.logs().to_vec(), vec![ Nep141Event::FtTransfer(vec![FtTransferData { - old_owner_id: alice.id().as_str().parse().unwrap(), - new_owner_id: bob.id().as_str().parse().unwrap(), + old_owner_id: alice.id().into(), + new_owner_id: bob.id().into(), amount: U128(10), memo: None, }]) .to_event_string(), format!("Received 10 from {}", alice.id()), Nep141Event::FtTransfer(vec![FtTransferData { - old_owner_id: bob.id().as_str().parse().unwrap(), - new_owner_id: alice.id().as_str().parse().unwrap(), + old_owner_id: bob.id().into(), + new_owner_id: alice.id().into(), amount: U128(10), memo: None, }]) diff --git a/workspaces-tests/tests/native_multisig.rs b/workspaces-tests/tests/native_multisig.rs index f0905b4..2f6d3e9 100644 --- a/workspaces-tests/tests/native_multisig.rs +++ b/workspaces-tests/tests/native_multisig.rs @@ -181,7 +181,7 @@ async fn delete_account() { "receiver_id": contract.id(), "actions": [ PromiseAction::DeleteAccount { - beneficiary_id: alice.id().as_str().parse().unwrap() + beneficiary_id: alice.id().clone(), }, ], })) @@ -393,7 +393,7 @@ async fn add_both_access_key_kinds_and_delete() { execute_actions(vec![PromiseAction::AddAccessKey { public_key: new_public_key_string.clone(), allowance: NearToken::from_yoctonear(1234567890), - receiver_id: alice.id().as_str().parse().unwrap(), + receiver_id: alice.id().clone(), function_names: vec!["one".into(), "two".into(), "three".into()], nonce: None, }]) diff --git a/workspaces-tests/tests/non_fungible_token.rs b/workspaces-tests/tests/non_fungible_token.rs index 3c31aef..7e18a05 100644 --- a/workspaces-tests/tests/non_fungible_token.rs +++ b/workspaces-tests/tests/non_fungible_token.rs @@ -31,22 +31,9 @@ const RECEIVER_WASM: &[u8] = const THIRTY_TERAGAS: Gas = Gas::from_gas(30_000_000_000_000); -fn token_meta(id: String) -> near_sdk::serde_json::Value { - near_sdk::serde_json::to_value(TokenMetadata { - title: Some(id), - description: Some("description".to_string()), - media: None, - media_hash: None, - copies: None, - issued_at: None, - expires_at: None, - starts_at: None, - updated_at: None, - extra: None, - reference: None, - reference_hash: None, - }) - .unwrap() +fn token_meta(id: impl Into) -> near_sdk::serde_json::Value { + near_sdk::serde_json::to_value(TokenMetadata::new().title(id).description("description")) + .unwrap() } async fn setup_balances( @@ -99,7 +86,7 @@ async fn create_and_mint() { token_0, Some(Token { token_id: "token_0".to_string(), - owner_id: alice.id().as_str().parse().unwrap(), + owner_id: alice.id().clone(), extensions_metadata: Default::default(), }), ); @@ -107,7 +94,7 @@ async fn create_and_mint() { token_1, Some(Token { token_id: "token_1".to_string(), - owner_id: bob.id().as_str().parse().unwrap(), + owner_id: bob.id().clone(), extensions_metadata: Default::default(), }), ); @@ -115,7 +102,7 @@ async fn create_and_mint() { token_2, Some(Token { token_id: "token_2".to_string(), - owner_id: charlie.id().as_str().parse().unwrap(), + owner_id: charlie.id().clone(), extensions_metadata: Default::default(), }), ); @@ -163,9 +150,9 @@ async fn create_and_mint_with_metadata_and_enumeration() { token_0, Some(Token { token_id: "token_0".to_string(), - owner_id: alice.id().as_str().parse().unwrap(), + owner_id: alice.id().clone(), extensions_metadata: [ - ("metadata".to_string(), token_meta("token_0".to_string())), + ("metadata".to_string(), token_meta("token_0")), ("approved_account_ids".to_string(), json!({})), ("funky_data".to_string(), json!({"funky": "data"})), ] @@ -176,9 +163,9 @@ async fn create_and_mint_with_metadata_and_enumeration() { token_1, Some(Token { token_id: "token_1".to_string(), - owner_id: bob.id().as_str().parse().unwrap(), + owner_id: bob.id().clone(), extensions_metadata: [ - ("metadata".to_string(), token_meta("token_1".to_string())), + ("metadata".to_string(), token_meta("token_1")), ("approved_account_ids".to_string(), json!({})), ("funky_data".to_string(), json!({"funky": "data"})), ] @@ -189,9 +176,9 @@ async fn create_and_mint_with_metadata_and_enumeration() { token_2, Some(Token { token_id: "token_2".to_string(), - owner_id: charlie.id().as_str().parse().unwrap(), + owner_id: charlie.id().clone(), extensions_metadata: [ - ("metadata".to_string(), token_meta("token_2".to_string())), + ("metadata".to_string(), token_meta("token_2")), ("approved_account_ids".to_string(), json!({})), ("funky_data".to_string(), json!({"funky": "data"})), ] @@ -337,11 +324,11 @@ async fn transfer_success() { vec![ "before_nft_transfer(token_0)".to_string(), Nep171Event::NftTransfer(vec![NftTransferLog { - old_owner_id: alice.id().as_str().parse().unwrap(), - new_owner_id: bob.id().as_str().parse().unwrap(), + old_owner_id: alice.id().into(), + new_owner_id: bob.id().into(), authorized_id: None, memo: None, - token_ids: vec!["token_0".to_string()], + token_ids: vec!["token_0".into()], }]) .to_event_string(), "after_nft_transfer(token_0)".to_string(), @@ -358,7 +345,7 @@ async fn transfer_success() { token_0, Some(Token { token_id: "token_0".to_string(), - owner_id: bob.id().as_str().parse().unwrap(), + owner_id: bob.id().clone(), extensions_metadata: Default::default(), }), ); @@ -366,7 +353,7 @@ async fn transfer_success() { token_1, Some(Token { token_id: "token_1".to_string(), - owner_id: bob.id().as_str().parse().unwrap(), + owner_id: bob.id().clone(), extensions_metadata: Default::default(), }), ); @@ -374,7 +361,7 @@ async fn transfer_success() { token_2, Some(Token { token_id: "token_2".to_string(), - owner_id: charlie.id().as_str().parse().unwrap(), + owner_id: charlie.id().clone(), extensions_metadata: Default::default(), }), ); @@ -543,10 +530,10 @@ async fn transfer_call_success() { vec![ "before_nft_transfer(token_0)".to_string(), Nep171Event::NftTransfer(vec![NftTransferLog { - token_ids: vec!["token_0".to_string()], + token_ids: vec!["token_0".into()], authorized_id: None, - old_owner_id: alice.id().as_str().parse().unwrap(), - new_owner_id: bob.id().as_str().parse().unwrap(), + old_owner_id: alice.id().into(), + new_owner_id: bob.id().into(), memo: None, }]) .to_event_string(), @@ -561,7 +548,7 @@ async fn transfer_call_success() { nft_token(&contract, "token_0").await, Some(Token { token_id: "token_0".to_string(), - owner_id: bob.id().as_str().parse().unwrap(), + owner_id: bob.id().clone(), extensions_metadata: Default::default(), }), ); @@ -602,10 +589,10 @@ async fn transfer_call_return_success() { vec![ "before_nft_transfer(token_0)".to_string(), Nep171Event::NftTransfer(vec![NftTransferLog { - token_ids: vec!["token_0".to_string()], + token_ids: vec!["token_0".into()], authorized_id: None, - old_owner_id: alice.id().as_str().parse().unwrap(), - new_owner_id: bob.id().as_str().parse().unwrap(), + old_owner_id: alice.id().into(), + new_owner_id: bob.id().into(), memo: None, }]) .to_event_string(), @@ -613,10 +600,10 @@ async fn transfer_call_return_success() { format!("Received token_0 from {} via {}", alice.id(), alice.id()), "before_nft_transfer(token_0)".to_string(), Nep171Event::NftTransfer(vec![NftTransferLog { - token_ids: vec!["token_0".to_string()], + token_ids: vec!["token_0".into()], authorized_id: None, - old_owner_id: bob.id().as_str().parse().unwrap(), - new_owner_id: alice.id().as_str().parse().unwrap(), + old_owner_id: bob.id().into(), + new_owner_id: alice.id().into(), memo: None, }]) .to_event_string(), @@ -630,7 +617,7 @@ async fn transfer_call_return_success() { nft_token(&contract, "token_0").await, Some(Token { token_id: "token_0".to_string(), - owner_id: alice.id().as_str().parse().unwrap(), + owner_id: alice.id().clone(), extensions_metadata: Default::default(), }), ); @@ -671,10 +658,10 @@ async fn transfer_call_receiver_panic() { vec![ "before_nft_transfer(token_0)".to_string(), Nep171Event::NftTransfer(vec![NftTransferLog { - token_ids: vec!["token_0".to_string()], + token_ids: vec!["token_0".into()], authorized_id: None, - old_owner_id: alice.id().as_str().parse().unwrap(), - new_owner_id: bob.id().as_str().parse().unwrap(), + old_owner_id: alice.id().into(), + new_owner_id: bob.id().into(), memo: None, }]) .to_event_string(), @@ -682,10 +669,10 @@ async fn transfer_call_receiver_panic() { format!("Received token_0 from {} via {}", alice.id(), alice.id()), "before_nft_transfer(token_0)".to_string(), Nep171Event::NftTransfer(vec![NftTransferLog { - token_ids: vec!["token_0".to_string()], + token_ids: vec!["token_0".into()], authorized_id: None, - old_owner_id: bob.id().as_str().parse().unwrap(), - new_owner_id: alice.id().as_str().parse().unwrap(), + old_owner_id: bob.id().into(), + new_owner_id: alice.id().into(), memo: None, }]) .to_event_string(), @@ -699,7 +686,7 @@ async fn transfer_call_receiver_panic() { nft_token(&contract, "token_0").await, Some(Token { token_id: "token_0".to_string(), - owner_id: alice.id().as_str().parse().unwrap(), + owner_id: alice.id().clone(), extensions_metadata: Default::default(), }), ); @@ -743,10 +730,10 @@ async fn transfer_call_receiver_send_return() { vec![ "before_nft_transfer(token_0)".to_string(), Nep171Event::NftTransfer(vec![NftTransferLog { - token_ids: vec!["token_0".to_string()], + token_ids: vec!["token_0".into()], authorized_id: None, - old_owner_id: alice.id().as_str().parse().unwrap(), - new_owner_id: bob.id().as_str().parse().unwrap(), + old_owner_id: alice.id().into(), + new_owner_id: bob.id().into(), memo: None, }]) .to_event_string(), @@ -755,10 +742,10 @@ async fn transfer_call_receiver_send_return() { format!("Transferring token_0 to {}", charlie.id()), "before_nft_transfer(token_0)".to_string(), Nep171Event::NftTransfer(vec![NftTransferLog { - token_ids: vec!["token_0".to_string()], + token_ids: vec!["token_0".into()], authorized_id: None, - old_owner_id: bob.id().as_str().parse().unwrap(), - new_owner_id: charlie.id().as_str().parse().unwrap(), + old_owner_id: bob.id().into(), + new_owner_id: charlie.id().into(), memo: None, }]) .to_event_string(), @@ -773,7 +760,7 @@ async fn transfer_call_receiver_send_return() { nft_token(&contract, "token_0").await, Some(Token { token_id: "token_0".to_string(), - owner_id: charlie.id().as_str().parse().unwrap(), + owner_id: charlie.id().clone(), extensions_metadata: Default::default(), }), ); @@ -803,9 +790,9 @@ async fn transfer_approval_success() { let expected_view_token = Token { token_id: "token_0".into(), - owner_id: alice.id().as_str().parse().unwrap(), + owner_id: alice.id().clone(), extensions_metadata: [ - ("metadata".to_string(), token_meta("token_0".to_string())), + ("metadata".to_string(), token_meta("token_0")), ( "approved_account_ids".to_string(), json!({ @@ -848,9 +835,9 @@ async fn transfer_approval_success() { nft_token(&contract, "token_0").await, Some(Token { token_id: "token_0".to_string(), - owner_id: charlie.id().as_str().parse().unwrap(), + owner_id: charlie.id().clone(), extensions_metadata: [ - ("metadata".to_string(), token_meta("token_0".to_string())), + ("metadata".to_string(), token_meta("token_0")), ("approved_account_ids".to_string(), json!({})), ("funky_data".to_string(), json!({"funky": "data"})), ] @@ -908,8 +895,8 @@ async fn transfer_approval_unapproved_fail() { let expected_error_message = format!( "Smart contract panicked: {}", nep171::error::SenderNotApprovedError { - owner_id: alice.id().as_str().parse().unwrap(), - sender_id: bob.id().as_str().parse().unwrap(), + owner_id: alice.id().clone(), + sender_id: bob.id().clone(), token_id: "token_0".to_string(), approval_id: 0, } @@ -971,7 +958,7 @@ async fn transfer_approval_double_approval_fail() { let expected_error = format!( "Smart contract panicked: {}", Nep178ApproveError::AccountAlreadyApproved(AccountAlreadyApprovedError { - account_id: bob.id().as_str().parse().unwrap(), + account_id: bob.id().clone(), token_id: "token_0".to_string(), }), ); @@ -1000,7 +987,7 @@ async fn transfer_approval_unauthorized_approval_fail() { let expected_error = format!( "Smart contract panicked: {}", Nep178ApproveError::Unauthorized(UnauthorizedError { - account_id: bob.id().as_str().parse().unwrap(), + account_id: bob.id().clone(), token_id: "token_0".to_string(), }), ); @@ -1094,8 +1081,8 @@ async fn transfer_approval_approved_but_wrong_approval_id_fail() { "Smart contract panicked: {}", nep171::error::Nep171TransferError::SenderNotApproved( nep171::error::SenderNotApprovedError { - sender_id: bob.id().as_str().parse().unwrap(), - owner_id: alice.id().as_str().parse().unwrap(), + sender_id: bob.id().clone(), + owner_id: alice.id().clone(), token_id: "token_0".to_string(), approval_id: 1, }