#![cfg_attr(not(feature = "std"), no_std)]
use codec::{Decode, Encode};
use frame_support::{
decl_module, decl_storage, decl_error, decl_event, ensure,
traits::{Currency, ExistenceRequirement, Get, ReservableCurrency},
};
use frame_system::{self as system, ensure_signed};
use sp_runtime::{
DispatchResult, DispatchError, ModuleId, RuntimeDebug,
traits::{AccountIdConversion, One},
};
#[cfg(feature = "std")]
use serde::{Deserialize, Serialize};
use sp_std::vec::Vec;
use sp_std::str;
use orml_nft::Pallet as AssetModule;
use gamepower_traits::*;
use gamepower_primitives::{ ListingId, ClaimId };
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
#[derive(Encode, Decode, Default, Clone, RuntimeDebug, PartialEq, Eq)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
pub struct Listing<ClassIdOf, TokenIdOf, AccountId, Balance> {
pub id: ListingId,
pub seller: AccountId,
pub asset: (ClassIdOf, TokenIdOf),
pub price: Balance,
}
#[derive(Encode, Decode, Default, Clone, RuntimeDebug, PartialEq, Eq)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
pub struct Claim<ClassIdOf, TokenIdOf, AccountId> {
pub receiver: AccountId,
pub asset: (ClassIdOf, TokenIdOf)
}
#[derive(Encode, Decode, Default, Clone, RuntimeDebug, PartialEq, Eq)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
pub struct Order<ListingOf, AccountId, BlockNumber> {
pub listing: ListingOf,
pub buyer: AccountId,
pub block: BlockNumber,
}
pub trait Config: system::Config + orml_nft::Config {
type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;
type Transfer: OnTransferHandler<Self::AccountId, Self::ClassId, Self::TokenId>;
type Burn: OnBurnHandler<Self::AccountId, Self::ClassId, Self::TokenId>;
type Claim: OnClaimHandler<Self::AccountId, Self::ClassId, Self::TokenId>;
type AllowTransfer: Get<bool>;
type AllowBurn: Get<bool>;
type AllowEscrow: Get<bool>;
type AllowClaim: Get<bool>;
type Currency: Currency<Self::AccountId> + ReservableCurrency<Self::AccountId>;
type ModuleId: Get<ModuleId>;
}
pub type ClassIdOf<T> = <T as orml_nft::Config>::ClassId;
pub type TokenIdOf<T> = <T as orml_nft::Config>::TokenId;
pub type ListingOf<T> = Listing<ClassIdOf<T>, TokenIdOf<T>, <T as system::Config>::AccountId, BalanceOf<T>>;
pub type ClaimOf<T> = Claim<ClassIdOf<T>, TokenIdOf<T>, <T as system::Config>::AccountId>;
pub type OrderOf<T> = Order<ListingOf<T>, <T as system::Config>::AccountId, <T as system::Config>::BlockNumber>;
type BalanceOf<T> = <<T as Config>::Currency as Currency<<T as system::Config>::AccountId>>::Balance;
decl_storage! {
trait Store for Module<T: Config> as GamePowerWallet {
pub Listings get(fn listings):
map hasher(twox_64_concat) ListingId => ListingOf<T>;
pub ListingsByOwner get(fn listings_by_owner):
map hasher(blake2_128_concat) T::AccountId => Vec<ListingId>;
pub AllListings get(fn all_listings): Vec<(ClassIdOf<T>, TokenIdOf<T>)>;
pub NextListingId get(fn next_listing_id): ListingId;
pub ListingCount: u64;
pub OrderCount: u64;
pub OrderHistory get(fn order_history):
map hasher(twox_64_concat) (ClassIdOf<T>, TokenIdOf<T>) => OrderOf<T>;
pub OpenClaims get(fn open_claims):
double_map hasher(blake2_128_concat) T::AccountId, hasher(twox_64_concat) ClaimId => ClaimOf<T>;
pub AllClaims get(fn all_claims): Vec<(ClassIdOf<T>, TokenIdOf<T>)>;
pub NextClaimId get(fn next_claim_id): ClaimId;
pub Emotes get(fn emotes):
double_map hasher(twox_64_concat) (ClassIdOf<T>, TokenIdOf<T>), hasher(twox_64_concat) T::AccountId => Vec<Vec<u8>>;
}
}
decl_event!(
pub enum Event<T>
where
<T as frame_system::Config>::AccountId,
ClassId = ClassIdOf<T>,
TokenId = TokenIdOf<T>,
Balance = BalanceOf<T>,
{
WalletAssetTransferred(AccountId, AccountId, ClassId, TokenId),
WalletAssetBurned(AccountId, ClassId, TokenId),
WalletAssetListed(AccountId, Balance, ListingId, ClassId, TokenId),
WalletAssetUnlisted(AccountId, ListingId, ClassId, TokenId),
WalletAssetPurchased(AccountId, AccountId, ClassId, TokenId),
WalletAssetClaimed(AccountId, ClassId, TokenId),
WalletClaimCreated(AccountId, AccountId, ClassId, TokenId),
WalletAssetBuySuccess(AccountId, AccountId, ListingId, Balance),
WalletAssetEmotePosted(AccountId, ClassId, TokenId, Vec<u8>),
}
);
decl_error! {
pub enum Error for Module<T: Config> {
TransfersNotAllowed,
TransferCancelled,
BurnCancelled,
BurningNotAllowed,
EscrowNotAllowed,
AssetLocked,
ClaimingNotAllowed,
ClaimCancelled,
AssetNotFound,
ListingNotFound,
UnlistingFailed,
ClaimNotFound,
ClaimCreateFailed,
NoAvailableListingId,
NoAvailableClaimId,
NoAvailableOrderId,
InvalidEmote,
NoPermission,
}
}
decl_module! {
pub struct Module<T: Config> for enum Call where origin: T::Origin {
type Error = Error<T>;
fn deposit_event() = default;
const AllowTransfer: bool = T::AllowTransfer::get();
const AllowBurn: bool = T::AllowBurn::get();
const AllowEscrow: bool = T::AllowEscrow::get();
const AllowClaim: bool = T::AllowClaim::get();
#[weight = 10_000]
pub fn transfer(origin, to: T::AccountId, asset:(ClassIdOf<T>, TokenIdOf<T>)) -> DispatchResult{
let sender = ensure_signed(origin)?;
ensure!(T::AllowTransfer::get(), Error::<T>::TransfersNotAllowed);
let check_ownership = Self::check_ownership(&sender, &asset)?;
ensure!(check_ownership, Error::<T>::NoPermission);
ensure!(!Self::is_locked(&asset), Error::<T>::AssetLocked);
ensure!(T::Transfer::transfer(&sender, &to, asset).is_ok(), Error::<T>::TransferCancelled);
Ok(())
}
#[weight = 10_000]
pub fn burn(origin, asset:(ClassIdOf<T>, TokenIdOf<T>)) -> DispatchResult{
let sender = ensure_signed(origin)?;
ensure!(T::AllowBurn::get(), Error::<T>::BurningNotAllowed);
let check_ownership = Self::check_ownership(&sender, &asset)?;
ensure!(check_ownership, Error::<T>::NoPermission);
ensure!(!Self::is_locked(&asset), Error::<T>::AssetLocked);
ensure!(T::Burn::burn(&sender, asset).is_ok(), Error::<T>::BurnCancelled);
Ok(().into())
}
#[weight = 10_000]
pub fn list(origin, asset:(ClassIdOf<T>, TokenIdOf<T>), price: BalanceOf<T>) -> DispatchResult{
let sender = ensure_signed(origin)?;
ensure!(T::AllowEscrow::get(), Error::<T>::EscrowNotAllowed);
let check_ownership = Self::check_ownership(&sender, &asset)?;
ensure!(check_ownership, Error::<T>::NoPermission);
ensure!(!Self::is_locked(&asset), Error::<T>::AssetLocked);
let escrow_account: T::AccountId = Self::get_escrow_account();
Self::do_transfer(&sender, &escrow_account, asset).ok();
let listing_id = NextListingId::try_mutate(|id| -> Result<ListingId, DispatchError> {
let current_id = *id;
*id = id.checked_add(One::one()).ok_or(Error::<T>::NoAvailableListingId)?;
Ok(current_id)
})?;
let listing = Listing {
id: listing_id,
seller: sender.clone(),
asset,
price,
};
ListingCount::mutate(|id| -> Result<u64, DispatchError> {
let current_count = *id;
*id = id.checked_add(One::one()).ok_or(Error::<T>::NoAvailableListingId)?;
Ok(current_count)
}).ok();
Listings::<T>::insert(listing_id, listing);
let mut owner_data = ListingsByOwner::<T>::get(&sender);
owner_data.push(listing_id);
ListingsByOwner::<T>::insert(&sender, owner_data);
AllListings::<T>::append(&asset);
Self::deposit_event(RawEvent::WalletAssetListed(sender, price, listing_id, asset.0, asset.1));
Ok(())
}
#[weight = 10_000]
pub fn unlist(origin, listing_id: ListingId) -> DispatchResult{
let sender = ensure_signed(origin)?;
ensure!(T::AllowEscrow::get(), Error::<T>::EscrowNotAllowed);
let listing_data = Listings::<T>::get(listing_id);
ensure!(sender == listing_data.seller, Error::<T>::NoPermission);
let is_unlisted = Self::do_unlist(&sender, listing_data.clone())?;
ensure!(is_unlisted, Error::<T>::UnlistingFailed);
Self::deposit_event(RawEvent::WalletAssetUnlisted(sender, listing_id, listing_data.asset.0, listing_data.asset.1));
Ok(())
}
#[weight = 10_000]
pub fn buy(origin, listing_id: ListingId) -> DispatchResult{
let sender = ensure_signed(origin)?;
ensure!(T::AllowEscrow::get(), Error::<T>::EscrowNotAllowed);
ensure!(Listings::<T>::contains_key(listing_id), Error::<T>::ListingNotFound);
let listing_data = Listings::<T>::take(listing_id);
let is_unlisted = Self::do_unlist(&sender, listing_data.clone())?;
ensure!(is_unlisted, Error::<T>::UnlistingFailed);
<T as Config>::Currency::transfer(&sender, &listing_data.seller, listing_data.price, ExistenceRequirement::KeepAlive)?;
OrderCount::mutate(|id| -> Result<u64, DispatchError> {
let current_count = *id;
*id = id.checked_add(One::one()).ok_or(Error::<T>::NoAvailableOrderId)?;
Ok(current_count)
}).ok();
let block_number = <system::Module<T>>::block_number();
let order = Order {
listing: listing_data.clone(),
buyer: sender.clone(),
block: block_number,
};
OrderHistory::<T>::insert(order.listing.asset, order);
Self::deposit_event(
RawEvent::WalletAssetBuySuccess(
listing_data.seller,
sender,
listing_data.id,
listing_data.price
)
);
Ok(())
}
#[weight = 10_000]
pub fn emote(origin, asset:(ClassIdOf<T>, TokenIdOf<T>), emote: Vec<u8>) -> DispatchResult{
let sender = ensure_signed(origin)?;
ensure!(!AssetModule::<T>::tokens(asset.0, asset.1).is_none(), Error::<T>::AssetNotFound);
let str_emote = str::from_utf8(&emote).unwrap();
ensure!(!emojis::lookup(str_emote).is_none(), Error::<T>::InvalidEmote);
let emoji = emojis::lookup(str_emote).unwrap().as_str().as_bytes().to_vec();
let mut emotes_data = Emotes::<T>::get(asset, &sender);
emotes_data.push(emoji.clone());
Emotes::<T>::insert(asset, &sender, emotes_data);
Self::deposit_event(RawEvent::WalletAssetEmotePosted(sender, asset.0, asset.1, emoji));
Ok(())
}
#[weight = 10_000]
pub fn claim(origin, claim_id: ClaimId) -> DispatchResult{
let sender = ensure_signed(origin)?;
ensure!(T::AllowClaim::get(), Error::<T>::ClaimingNotAllowed);
ensure!(OpenClaims::<T>::contains_key(&sender, claim_id), Error::<T>::ClaimNotFound);
let claim_data = OpenClaims::<T>::get(&sender, claim_id);
ensure!(T::Claim::claim(&sender, claim_data.asset).is_ok(), Error::<T>::ClaimCancelled);
let claim_account: T::AccountId = Self::get_claim_account();
Self::do_transfer(&claim_account, &sender, claim_data.asset).ok();
AllClaims::<T>::try_mutate(|asset_ids| -> DispatchResult {
let asset_index = asset_ids.iter().position(|x| *x == claim_data.asset).unwrap();
asset_ids.remove(asset_index);
Ok(())
})?;
OpenClaims::<T>::remove(&sender, claim_id);
Self::deposit_event(RawEvent::WalletAssetClaimed(sender, claim_data.asset.0, claim_data.asset.1));
Ok(())
}
#[weight = 10_000]
pub fn create_claim(origin, receiver: T::AccountId, asset:(ClassIdOf<T>, TokenIdOf<T>)) -> DispatchResult{
let sender = ensure_signed(origin)?;
ensure!(T::AllowClaim::get(), Error::<T>::ClaimingNotAllowed);
let check_ownership = Self::check_ownership(&sender, &asset)?;
ensure!(check_ownership, Error::<T>::NoPermission);
let class_info = AssetModule::<T>::classes(asset.0).ok_or(Error::<T>::AssetNotFound)?;
ensure!(sender == class_info.owner, Error::<T>::NoPermission);
let claim_created = Self::do_create_claim(&sender, &receiver, asset)?;
ensure!(claim_created, Error::<T>::ClaimCreateFailed);
Self::deposit_event(RawEvent::WalletClaimCreated(sender, receiver, asset.0, asset.1));
Ok(())
}
}
}
impl<T: Config> Module<T> {
fn check_ownership(
owner: &T::AccountId,
asset: &(ClassIdOf<T>, TokenIdOf<T>)
) -> Result<bool, DispatchError> {
return Ok(AssetModule::<T>::is_owner(&owner, *asset));
}
fn do_transfer(
from: &T::AccountId,
to: &T::AccountId,
asset: (ClassIdOf<T>, TokenIdOf<T>)
) -> Result<bool, DispatchError> {
AssetModule::<T>::transfer(&from, &to, asset).ok();
return Ok(true)
}
fn is_listed(asset: &(ClassIdOf<T>, TokenIdOf<T>)) -> bool {
return Self::all_listings().contains(asset);
}
fn is_claiming(asset: &(ClassIdOf<T>, TokenIdOf<T>)) -> bool {
return Self::all_claims().contains(asset)
}
fn get_claim_account() -> T::AccountId {
return T::ModuleId::get().into_sub_account(100u32)
}
fn get_escrow_account() -> T::AccountId {
return T::ModuleId::get().into_account()
}
pub fn is_locked(asset: &(ClassIdOf<T>, TokenIdOf<T>)) -> bool {
return Self::is_listed(&asset) || Self::is_claiming(&asset)
}
fn do_unlist(sender: &T::AccountId, listing_data: ListingOf<T>) -> Result<bool, DispatchError> {
let escrow_account: T::AccountId = Self::get_escrow_account();
Self::do_transfer(&escrow_account, &sender, listing_data.asset).ok();
ListingCount::mutate(|id| -> Result<u64, DispatchError> {
let current_count = *id;
*id = id.checked_sub(One::one()).ok_or(Error::<T>::NoAvailableListingId)?;
Ok(current_count)
}).ok();
AllListings::<T>::try_mutate(|asset_ids| -> DispatchResult {
let asset_index = asset_ids.iter().position(|x| *x == listing_data.asset).unwrap();
asset_ids.remove(asset_index);
Ok(())
})?;
Listings::<T>::remove(listing_data.id);
let mut owner_data = ListingsByOwner::<T>::get(listing_data.clone().seller);
owner_data.retain(|&x| x != listing_data.id);
ListingsByOwner::<T>::insert(listing_data.clone().seller, owner_data);
Ok(true)
}
fn do_create_claim(
owner: &T::AccountId,
receiver: &T::AccountId,
asset: (ClassIdOf<T>, TokenIdOf<T>)
) -> Result<bool, DispatchError> {
let claim_account: T::AccountId = Self::get_claim_account();
Self::do_transfer(&owner, &claim_account, asset).ok();
let claim = Claim {
receiver: receiver.clone(),
asset,
};
let claim_id = NextClaimId::try_mutate(|id| -> Result<ClaimId, DispatchError> {
let current_id = *id;
*id = id.checked_add(One::one()).ok_or(Error::<T>::NoAvailableClaimId)?;
Ok(current_id)
})?;
OpenClaims::<T>::insert(receiver, claim_id, claim);
AllClaims::<T>::append(&asset);
Ok(true)
}
}
impl<T: Config> OnTransferHandler<T::AccountId, T::ClassId, T::TokenId> for Module<T> {
fn transfer(from: &T::AccountId, to: &T::AccountId, asset: (T::ClassId, T::TokenId)) -> DispatchResult {
Self::do_transfer(&from, &to, asset)?;
Module::<T>::deposit_event(RawEvent::WalletAssetTransferred(from.clone(), to.clone(), asset.0, asset.1));
Ok(())
}
}
impl<T: Config> OnBurnHandler<T::AccountId, T::ClassId, T::TokenId> for Module<T> {
fn burn(owner: &T::AccountId, asset: (T::ClassId, T::TokenId)) -> DispatchResult {
AssetModule::<T>::burn(&owner, asset)?;
Module::<T>::deposit_event(RawEvent::WalletAssetBurned(owner.clone(), asset.0, asset.1));
Ok(())
}
}
impl<T: Config> OnClaimHandler<T::AccountId, T::ClassId, T::TokenId> for Module<T> {
fn claim(_owner: &T::AccountId, _asset: (T::ClassId, T::TokenId)) -> DispatchResult {
Ok(())
}
}