Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//! # Non Fungible Token
//! The module provides implementations for non-fungible-token.
//!
//! - [`Trait`](./trait.Trait.html)
//! - [`Call`](./enum.Call.html)
//! - [`Module`](./struct.Module.html)
//!
//! ## Overview
//!
//! This module provides basic functions to create and manager
//! NFT(non fungible token) such as `create_class`, `transfer`, `mint`, `burn`.
//! ### Module Functions
//!
//! - `create_class` - Create NFT(non fungible token) class
//! - `transfer` - Transfer NFT(non fungible token) to another account.
//! - `mint` - Mint NFT(non fungible token)
//! - `burn` - Burn NFT(non fungible token)
//! - `destroy_class` - Destroy NFT(non fungible token) class
#![cfg_attr(not(feature = "std"), no_std)]
use codec::{Decode, Encode};
use frame_support::{decl_error, decl_module, decl_storage, ensure, Parameter};
use sp_runtime::{
traits::{AtLeast32BitUnsigned, CheckedAdd, CheckedSub, Member, One, Zero},
DispatchError, DispatchResult, RuntimeDebug,
};
use sp_std::vec::Vec;
mod mock;
mod tests;
/// Class info
#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug)]
pub struct ClassInfo<TokenId, AccountId, Data> {
/// Class metadata
/// Total issuance for the class
pub total_issuance: TokenId,
/// Class owner
pub owner: AccountId,
/// Class Properties
pub data: Data,
}
/// Token info
#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug)]
pub struct TokenInfo<AccountId, Data> {
/// Token metadata
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
/// Token owner
pub owner: AccountId,
/// Token Properties
pub data: Data,
}
pub trait Trait: frame_system::Trait {
/// The class ID type
type ClassId: Parameter + Member + AtLeast32BitUnsigned + Default + Copy;
/// The token ID type
type TokenId: Parameter + Member + AtLeast32BitUnsigned + Default + Copy;
/// The class properties type
type ClassData: Parameter + Member;
/// The token properties type
type TokenData: Parameter + Member;
}
decl_error! {
/// Error for non-fungible-token module.
pub enum Error for Module<T: Trait> {
/// No available class ID
NoAvailableClassId,
/// No available token ID
NoAvailableTokenId,
/// Token(ClassId, TokenId) not found
TokenNotFound,
/// Class not found
ClassNotFound,
/// The operator is not the owner of the token and has no permission
NoPermission,
/// Arithmetic calculation overflow
NumOverflow,
/// Can not destroy class
/// Total issuance is not 0
CannotDestroyClass,
}
}
pub type ClassInfoOf<T> =
ClassInfo<<T as Trait>::TokenId, <T as frame_system::Trait>::AccountId, <T as Trait>::ClassData>;
pub type TokenInfoOf<T> = TokenInfo<<T as frame_system::Trait>::AccountId, <T as Trait>::TokenData>;
decl_storage! {
trait Store for Module<T: Trait> as NonFungibleToken {
/// Next available class ID.
pub NextClassId get(fn next_class_id): T::ClassId;
/// Next available token ID.
pub NextTokenId get(fn next_token_id): map hasher(twox_64_concat) T::ClassId => T::TokenId;
/// Store class info.
///
/// Returns `None` if class info not set or removed.
pub Classes get(fn classes): map hasher(twox_64_concat) T::ClassId => Option<ClassInfoOf<T>>;
/// Store token info.
///
/// Returns `None` if token info not set or removed.
pub Tokens get(fn tokens): double_map hasher(twox_64_concat) T::ClassId, hasher(twox_64_concat) T::TokenId => Option<TokenInfoOf<T>>;
/// Token existence check by owner and class ID.
#[cfg(not(feature = "disable-tokens-by-owner"))]
pub TokensByOwner get(fn tokens_by_owner): double_map hasher(twox_64_concat) T::AccountId, hasher(twox_64_concat) (T::ClassId, T::TokenId) => Option<()>;
}
}
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
}
}
impl<T: Trait> Module<T> {
/// Create NFT(non fungible token) class
pub fn create_class(
owner: &T::AccountId,
metadata: Vec<u8>,
data: T::ClassData,
) -> Result<T::ClassId, DispatchError> {
let class_id = NextClassId::<T>::try_mutate(|id| -> Result<T::ClassId, DispatchError> {
let current_id = *id;
*id = id.checked_add(&One::one()).ok_or(Error::<T>::NoAvailableClassId)?;
Ok(current_id)
})?;
let info = ClassInfo {
metadata,
total_issuance: Default::default(),
owner: owner.clone(),
data,
};
Classes::<T>::insert(class_id, info);
Ok(class_id)
}
/// Transfer NFT(non fungible token) from `from` account to `to` account
pub fn transfer(from: &T::AccountId, to: &T::AccountId, token: (T::ClassId, T::TokenId)) -> DispatchResult {
Tokens::<T>::try_mutate(token.0, token.1, |token_info| -> DispatchResult {
let mut info = token_info.as_mut().ok_or(Error::<T>::TokenNotFound)?;
ensure!(info.owner == *from, Error::<T>::NoPermission);
if from == to {
// no change needed
return Ok(());
}
#[cfg(not(feature = "disable-tokens-by-owner"))]
{
TokensByOwner::<T>::remove(from, token);
TokensByOwner::<T>::insert(to, token, ());
}
Ok(())
})
}
/// Mint NFT(non fungible token) to `owner`
pub fn mint(
owner: &T::AccountId,
class_id: T::ClassId,
data: T::TokenData,
) -> Result<T::TokenId, DispatchError> {
NextTokenId::<T>::try_mutate(class_id, |id| -> Result<T::TokenId, DispatchError> {
let token_id = *id;
*id = id.checked_add(&One::one()).ok_or(Error::<T>::NoAvailableTokenId)?;
Classes::<T>::try_mutate(class_id, |class_info| -> DispatchResult {
let info = class_info.as_mut().ok_or(Error::<T>::ClassNotFound)?;
info.total_issuance = info
.total_issuance
.checked_add(&One::one())
.ok_or(Error::<T>::NumOverflow)?;
Ok(())
})?;
let token_info = TokenInfo {
metadata,
owner: owner.clone(),
data,
};
Tokens::<T>::insert(class_id, token_id, token_info);
#[cfg(not(feature = "disable-tokens-by-owner"))]
TokensByOwner::<T>::insert(owner, (class_id, token_id), ());
Ok(token_id)
})
}
/// Burn NFT(non fungible token) from `owner`
pub fn burn(owner: &T::AccountId, token: (T::ClassId, T::TokenId)) -> DispatchResult {
Tokens::<T>::try_mutate_exists(token.0, token.1, |token_info| -> DispatchResult {
let t = token_info.take().ok_or(Error::<T>::TokenNotFound)?;
ensure!(t.owner == *owner, Error::<T>::NoPermission);
Classes::<T>::try_mutate(token.0, |class_info| -> DispatchResult {
let info = class_info.as_mut().ok_or(Error::<T>::ClassNotFound)?;
info.total_issuance = info
.total_issuance
.checked_sub(&One::one())
.ok_or(Error::<T>::NumOverflow)?;
Ok(())
})?;
#[cfg(not(feature = "disable-tokens-by-owner"))]
TokensByOwner::<T>::remove(owner, token);
Ok(())
})
}
/// Destroy NFT(non fungible token) class
pub fn destroy_class(owner: &T::AccountId, class_id: T::ClassId) -> DispatchResult {
Classes::<T>::try_mutate_exists(class_id, |class_info| -> DispatchResult {
let info = class_info.take().ok_or(Error::<T>::ClassNotFound)?;
ensure!(info.owner == *owner, Error::<T>::NoPermission);
ensure!(info.total_issuance == Zero::zero(), Error::<T>::CannotDestroyClass);
pub fn is_owner(account: &T::AccountId, token: (T::ClassId, T::TokenId)) -> bool {
#[cfg(feature = "disable-tokens-by-owner")]
return Tokens::<T>::get(token.0, token.1).map_or(false, |token| token.owner == *account);
#[cfg(not(feature = "disable-tokens-by-owner"))]
TokensByOwner::<T>::contains_key(account, token)
}