#![cfg_attr(not(feature = "std"), no_std)]
use codec::{Decode, Encode, FullCodec};
use frame_support::{
decl_error, decl_event, decl_module, decl_storage, dispatch, ensure,
traits::{EnsureOrigin, Get},
Hashable,
};
use frame_system::{self as system, ensure_signed};
use sp_runtime::{
traits::{Hash, Member},
RuntimeDebug,
};
use sp_std::{
cmp::{Eq, Ordering},
fmt::Debug,
vec::Vec,
};
pub mod nft;
pub use crate::nft::{UniqueAssets, NFT};
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
pub trait Trait<I = DefaultInstance>: system::Trait {
type AssetAdmin: EnsureOrigin<Self::Origin>;
type AssetInfo: Hashable + Member + Debug + Default + FullCodec;
type AssetLimit: Get<u128>;
type UserAssetLimit: Get<u64>;
type Event: From<Event<Self, I>> + Into<<Self as system::Trait>::Event>;
}
pub type AssetId<T> = <T as frame_system::Trait>::Hash;
pub type IdentifiedAssetFor<T, I> = IdentifiedAsset<AssetId<T>, <T as Trait<I>>::AssetInfo>;
#[derive(Encode, Decode, Clone, Eq, RuntimeDebug)]
pub struct IdentifiedAsset<Hash, AssetInfo> {
pub id: Hash,
pub asset: AssetInfo,
}
impl<AssetId: Ord, AssetInfo: Eq> Ord for IdentifiedAsset<AssetId, AssetInfo> {
fn cmp(&self, other: &Self) -> Ordering {
self.id.cmp(&other.id)
}
}
impl<AssetId: Ord, AssetInfo> PartialOrd for IdentifiedAsset<AssetId, AssetInfo> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.id.cmp(&other.id))
}
}
impl<AssetId: Eq, AssetInfo> PartialEq for IdentifiedAsset<AssetId, AssetInfo> {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl<AssetId, AssetInfo> NFT for IdentifiedAsset<AssetId, AssetInfo> {
type Id = AssetId;
type Info = AssetInfo;
}
decl_storage! {
trait Store for Module<T: Trait<I>, I: Instance = DefaultInstance> as Commodity {
Total get(fn total): u128 = 0;
Burned get(fn burned): u128 = 0;
TotalForAccount get(fn total_for_account): map hasher(blake2_128_concat) T::AccountId => u64 = 0;
AssetsForAccount get(fn assets_for_account): map hasher(blake2_128_concat) T::AccountId => Vec<IdentifiedAssetFor<T, I>>;
AccountForAsset get(fn account_for_asset): map hasher(identity) AssetId<T> => T::AccountId;
}
}
decl_event!(
pub enum Event<T, I = DefaultInstance>
where
AssetId = <T as system::Trait>::Hash,
AccountId = <T as system::Trait>::AccountId,
{
Burned(AssetId),
Minted(AssetId, AccountId),
Transferred(AssetId, AccountId),
}
);
decl_error! {
pub enum Error for Module<T: Trait<I>, I: Instance> {
AssetExists,
NonexistentAsset,
NotAssetOwner,
TooManyAssets,
TooManyAssetsForAccount,
}
}
decl_module! {
pub struct Module<T: Trait<I>, I: Instance = DefaultInstance> for enum Call where origin: T::Origin {
type Error = Error<T, I>;
fn deposit_event() = default;
#[weight = 10_000]
pub fn mint(origin, owner_account: T::AccountId, asset_info: T::AssetInfo) -> dispatch::DispatchResult {
T::AssetAdmin::ensure_origin(origin)?;
let asset_id = <Self as UniqueAssets<_>>::mint(&owner_account, asset_info)?;
Self::deposit_event(RawEvent::Minted(asset_id, owner_account.clone()));
Ok(())
}
#[weight = 10_000]
pub fn burn(origin, asset_id: AssetId<T>) -> dispatch::DispatchResult {
let who = ensure_signed(origin)?;
ensure!(who == Self::account_for_asset(&asset_id), Error::<T, I>::NotAssetOwner);
<Self as UniqueAssets<_>>::burn(&asset_id)?;
Self::deposit_event(RawEvent::Burned(asset_id.clone()));
Ok(())
}
#[weight = 10_000]
pub fn transfer(origin, dest_account: T::AccountId, asset_id: AssetId<T>) -> dispatch::DispatchResult {
let who = ensure_signed(origin)?;
ensure!(who == Self::account_for_asset(&asset_id), Error::<T, I>::NotAssetOwner);
<Self as UniqueAssets<_>>::transfer(&dest_account, &asset_id)?;
Self::deposit_event(RawEvent::Transferred(asset_id.clone(), dest_account.clone()));
Ok(())
}
}
}
impl<T: Trait<I>, I: Instance> UniqueAssets<IdentifiedAsset<AssetId<T>, <T as Trait<I>>::AssetInfo>>
for Module<T, I>
{
type AccountId = <T as system::Trait>::AccountId;
type AssetLimit = T::AssetLimit;
type UserAssetLimit = T::UserAssetLimit;
fn total() -> u128 {
Self::total()
}
fn burned() -> u128 {
Self::burned()
}
fn total_for_account(account: &T::AccountId) -> u64 {
Self::total_for_account(account)
}
fn assets_for_account(
account: &T::AccountId,
) -> Vec<IdentifiedAsset<AssetId<T>, <T as Trait<I>>::AssetInfo>> {
Self::assets_for_account(account)
}
fn owner_of(asset_id: &AssetId<T>) -> T::AccountId {
Self::account_for_asset(asset_id)
}
fn mint(
owner_account: &T::AccountId,
asset_info: <T as Trait<I>>::AssetInfo,
) -> dispatch::result::Result<AssetId<T>, dispatch::DispatchError> {
let asset_id = T::Hashing::hash_of(&asset_info);
ensure!(
!AccountForAsset::<T, I>::contains_key(&asset_id),
Error::<T, I>::AssetExists
);
ensure!(
Self::total_for_account(owner_account) < T::UserAssetLimit::get(),
Error::<T, I>::TooManyAssetsForAccount
);
ensure!(
Self::total() < T::AssetLimit::get(),
Error::<T, I>::TooManyAssets
);
let new_asset = IdentifiedAsset {
id: asset_id,
asset: asset_info,
};
Total::<I>::mutate(|total| *total += 1);
TotalForAccount::<T, I>::mutate(owner_account, |total| *total += 1);
AssetsForAccount::<T, I>::mutate(owner_account, |assets| {
match assets.binary_search(&new_asset) {
Ok(_pos) => {}
Err(pos) => assets.insert(pos, new_asset),
}
});
AccountForAsset::<T, I>::insert(asset_id, &owner_account);
Ok(asset_id)
}
fn burn(asset_id: &AssetId<T>) -> dispatch::DispatchResult {
let owner = Self::owner_of(asset_id);
ensure!(
owner != T::AccountId::default(),
Error::<T, I>::NonexistentAsset
);
let burn_asset = IdentifiedAsset::<AssetId<T>, <T as Trait<I>>::AssetInfo> {
id: *asset_id,
asset: <T as Trait<I>>::AssetInfo::default(),
};
Total::<I>::mutate(|total| *total -= 1);
Burned::<I>::mutate(|total| *total += 1);
TotalForAccount::<T, I>::mutate(&owner, |total| *total -= 1);
AssetsForAccount::<T, I>::mutate(owner, |assets| {
let pos = assets
.binary_search(&burn_asset)
.expect("We already checked that we have the correct owner; qed");
assets.remove(pos);
});
AccountForAsset::<T, I>::remove(&asset_id);
Ok(())
}
fn transfer(dest_account: &T::AccountId, asset_id: &AssetId<T>) -> dispatch::DispatchResult {
let owner = Self::owner_of(&asset_id);
ensure!(
owner != T::AccountId::default(),
Error::<T, I>::NonexistentAsset
);
ensure!(
Self::total_for_account(dest_account) < T::UserAssetLimit::get(),
Error::<T, I>::TooManyAssetsForAccount
);
let xfer_asset = IdentifiedAsset::<AssetId<T>, <T as Trait<I>>::AssetInfo> {
id: *asset_id,
asset: <T as Trait<I>>::AssetInfo::default(),
};
TotalForAccount::<T, I>::mutate(&owner, |total| *total -= 1);
TotalForAccount::<T, I>::mutate(dest_account, |total| *total += 1);
let asset = AssetsForAccount::<T, I>::mutate(owner, |assets| {
let pos = assets
.binary_search(&xfer_asset)
.expect("We already checked that we have the correct owner; qed");
assets.remove(pos)
});
AssetsForAccount::<T, I>::mutate(dest_account, |assets| {
match assets.binary_search(&asset) {
Ok(_pos) => {}
Err(pos) => assets.insert(pos, asset),
}
});
AccountForAsset::<T, I>::insert(&asset_id, &dest_account);
Ok(())
}
}