Skip to content

Commit

Permalink
chore: more tests and examples in docs
Browse files Browse the repository at this point in the history
  • Loading branch information
encody committed Aug 26, 2023
1 parent 6824eb4 commit 1df7e02
Show file tree
Hide file tree
Showing 8 changed files with 341 additions and 56 deletions.
3 changes: 2 additions & 1 deletion macros/src/standard/nep171.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ pub fn expand(meta: Nep171Meta) -> Result<TokenStream, darling::Error> {
Ok(quote! {
impl #imp #me::standard::nep171::Nep171ControllerInternal for #ident #ty #wher {
type CheckExternalTransfer = #check_external_transfer;
type LoadTokenMetadata = #token_type;

#root
}
Expand Down Expand Up @@ -263,7 +264,7 @@ pub fn expand(meta: Nep171Meta) -> Result<TokenStream, darling::Error> {
&self,
token_id: #me::standard::nep171::TokenId,
) -> Option<#me::standard::nep171::Token> {
<Self as #me::standard::nep171::Nep171Controller>::load_token::<#token_type>(self, &token_id)
<Self as #me::standard::nep171::Nep171Controller>::load_token(self, &token_id)
}
}
})
Expand Down
2 changes: 1 addition & 1 deletion src/standard/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Implementations of NEP standards
//! Implementations of NEP standards.

pub mod nep141;
pub mod nep148;
Expand Down
6 changes: 5 additions & 1 deletion src/standard/nep171/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
use near_sdk::AccountId;
use thiserror::Error;

use crate::standard::nep178::ApprovalId;

use super::TokenId;

/// Occurs when trying to create a token ID that already exists.
Expand Down Expand Up @@ -38,14 +40,16 @@ pub struct TokenNotOwnedByExpectedOwnerError {

/// Occurs when a particular account is not allowed to transfer a token (e.g. on behalf of another user). See: NEP-178.
#[derive(Error, Clone, Debug)]
#[error("Sender `{sender_id}` does not have permission to transfer token `{token_id}`, owned by `{owner_id}`")]
#[error("Sender `{sender_id}` does not have permission to transfer token `{token_id}`, owned by `{owner_id}`, with approval ID {approval_id}")]
pub struct SenderNotApprovedError {
/// The unapproved sender.
pub sender_id: AccountId,
/// The owner of the token.
pub owner_id: AccountId,
/// The ID of the token in question.
pub token_id: TokenId,
/// The approval ID that the sender tried to use to transfer the token.
pub approval_id: ApprovalId,
}

/// Occurs when attempting to perform a transfer of a token from one
Expand Down
104 changes: 53 additions & 51 deletions src/standard/nep171/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,45 @@
//! NEP-171 non-fungible token core implementation.
//!
//! Reference: <https://github.com/near/NEPs/blob/master/neps/nep-0171.md>
//!
//! # Usage
//!
//! It is recommended to use the [`near_sdk_contract_tools::Nep171`] derive macro or the [`near_sdk_contract_tools::NonFungibleToken`] macro to implement NEP-171 with this crate.
//!
//! ## Basic implementation with no transfer hooks
//!
//! ```rust
//! use near_sdk_contract_tools::{Nep171, standard::nep171::*};
//! use near_sdk::{*, borsh::{self, *}};
//!
//! #[derive(BorshSerialize, BorshDeserialize, PanicOnDefault, Nep171)]
//! #[nep171(no_hooks)]
//! #[near_bindgen]
//! pub struct Contract {}
//! ```
//!
//! ## Basic implementation with transfer hooks
//!
//! ```rust
//! use near_sdk_contract_tools::{Nep171, standard::nep171::*};
//! use near_sdk::{*, borsh::{self, *}};
//!
//! #[derive(BorshSerialize, BorshDeserialize, PanicOnDefault, Nep171)]
//! #[near_bindgen]
//! pub struct Contract {
//! transfers: u32,
//! }
//!
//! impl Nep171Hook for Contract {
//! fn before_nft_transfer(_contract: &Self, transfer: &Nep171Transfer) {
//! log!("{} is transferring {} to {}", transfer.sender_id, transfer.token_id, transfer.receiver_id);
//! }
//!
//! fn after_nft_transfer(contract: &mut Self, _transfer: &Nep171Transfer, _: ()) {
//! contract.transfers += 1;
//! }
//! }
//! ```

use std::error::Error;

Expand Down Expand Up @@ -104,6 +143,10 @@ pub trait Nep171ControllerInternal {
where
Self: Sized;

type LoadTokenMetadata: LoadTokenMetadata<Self>
where
Self: Sized;

/// Root storage slot.
fn root() -> Slot<()> {
Slot::root(DefaultStorageKey::Nep171)
Expand All @@ -122,6 +165,10 @@ pub trait Nep171Controller {
where
Self: Sized;

type LoadTokenMetadata: LoadTokenMetadata<Self>
where
Self: Sized;

/// Transfer a token from `sender_id` to `receiver_id`. Checks that the transfer is valid using [`Nep171Controller::check_transfer`] before performing the transfer.
fn external_transfer(&mut self, transfer: &Nep171Transfer) -> Result<(), Nep171TransferError>
where
Expand Down Expand Up @@ -176,9 +223,7 @@ pub trait Nep171Controller {
fn token_owner(&self, token_id: &TokenId) -> Option<AccountId>;

/// Loads the metadata associated with a token.
fn load_token<T: LoadTokenMetadata<Self>>(&self, token_id: &TokenId) -> Option<Token>
where
Self: Sized;
fn load_token(&self, token_id: &TokenId) -> Option<Token>;
}

/// Transfer metadata generic over both types of transfer (`nft_transfer` and
Expand Down Expand Up @@ -243,11 +288,12 @@ impl<T: Nep171Controller> CheckExternalTransfer<T> for DefaultCheckExternalTrans
.into());
}
}
Nep171TransferAuthorization::ApprovalId(_) => {
Nep171TransferAuthorization::ApprovalId(approval_id) => {
return Err(error::SenderNotApprovedError {
owner_id,
sender_id: transfer.sender_id.clone(),
token_id: transfer.token_id.clone(),
approval_id,
}
.into())
}
Expand Down Expand Up @@ -313,6 +359,7 @@ where

impl<T: Nep171ControllerInternal> Nep171Controller for T {
type CheckExternalTransfer = <Self as Nep171ControllerInternal>::CheckExternalTransfer;
type LoadTokenMetadata = <Self as Nep171ControllerInternal>::LoadTokenMetadata;

fn external_transfer(&mut self, transfer: &Nep171Transfer) -> Result<(), Nep171TransferError> {
match Self::CheckExternalTransfer::check_external_transfer(self, transfer) {
Expand All @@ -330,51 +377,6 @@ impl<T: Nep171ControllerInternal> Nep171Controller for T {
}
}

// fn check_transfer(
// &self,
// token_ids: &[TokenId],
// authorization: Nep171TransferAuthorization,
// sender_id: &AccountId,
// receiver_id: &AccountId,
// ) -> Result<AccountId, Nep171TransferError> {
// for token_id in token_ids {
// let slot = Self::slot_token_owner(token_id);

// let owner_id = slot.read().ok_or_else(|| error::TokenDoesNotExistError {
// token_id: token_id.clone(),
// })?;

// match authorization {
// Nep171TransferAuthorization::Owner => {
// if sender_id != &owner_id {
// return Err(error::TokenNotOwnedByExpectedOwnerError {
// expected_owner_id: sender_id.clone(),
// actual_owner_id: owner_id,
// token_id: token_id.clone(),
// }
// .into());
// }
// }
// Nep171TransferAuthorization::ApprovalId(_) => {
// return Err(error::SenderNotApprovedError {
// sender_id: sender_id.clone(),
// token_id: token_id.clone(),
// }
// .into())
// }
// }

// if receiver_id == &owner_id {
// return Err(error::TokenReceiverIsCurrentOwnerError {
// current_owner_id: owner_id,
// token_id: token_id.clone(),
// }
// .into());
// }
// }
// Ok(())
// }

fn transfer_unchecked(
&mut self,
token_ids: &[TokenId],
Expand Down Expand Up @@ -490,9 +492,9 @@ impl<T: Nep171ControllerInternal> Nep171Controller for T {
Self::slot_token_owner(token_id).read()
}

fn load_token<L: LoadTokenMetadata<Self>>(&self, token_id: &TokenId) -> Option<Token> {
fn load_token(&self, token_id: &TokenId) -> Option<Token> {
let mut metadata = std::collections::HashMap::new();
L::load(self, token_id, &mut metadata).ok()?;
Self::LoadTokenMetadata::load(self, token_id, &mut metadata).ok()?;
Some(Token {
token_id: token_id.clone(),
owner_id: self.token_owner(token_id)?,
Expand Down
2 changes: 1 addition & 1 deletion src/standard/nep178.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ pub trait Nep178ControllerInternal {
#[derive(Error, Debug)]
pub enum Nep178ApproveError {
/// The account is not authorized to approve the token.
#[error("Account `{account_id}` is cannot create approvals for token `{token_id}`.")]
#[error("Account `{account_id}` cannot create approvals for token `{token_id}`.")]
Unauthorized {
/// The token ID.
token_id: TokenId,
Expand Down
1 change: 1 addition & 0 deletions tests/macros/standard/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ pub mod fungible_token;
pub mod nep141;
pub mod nep148;
pub mod nep171;
pub mod non_fungible_token;
31 changes: 31 additions & 0 deletions tests/macros/standard/non_fungible_token.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use near_sdk::{
borsh::{self, *},
*,
};
use near_sdk_contract_tools::{standard::nep171::*, Nep171};

#[derive(BorshSerialize, BorshDeserialize, PanicOnDefault, Nep171)]
#[near_bindgen]
pub struct Contract {
transfers: u32,
}

impl Nep171Hook for Contract {
fn before_nft_transfer(_contract: &Self, transfer: &Nep171Transfer) {
log!(
"{} is transferring {} to {}",
transfer.sender_id,
transfer.token_id,
transfer.receiver_id,
);
}

fn after_nft_transfer(contract: &mut Self, _transfer: &Nep171Transfer, _: ()) {
contract.transfers += 1;
}
}

#[derive(BorshSerialize, BorshDeserialize, PanicOnDefault, Nep171)]
#[nep171(no_hooks)]
#[near_bindgen]
pub struct Contract1 {}
Loading

0 comments on commit 1df7e02

Please sign in to comment.