git.delta.rocks / unique-network / refs/commits / b8e70f32f7e0

difftreelog

source

pallets/nft/src/lib.rs72.7 KiBsourcehistory
1#![cfg_attr(not(feature = "std"), no_std)]23#[cfg(feature = "std")]4pub use std::*;56#[cfg(feature = "std")]7pub use serde::*;89use codec::{Decode, Encode};10pub use frame_support::{11    construct_runtime, decl_event, decl_module, decl_storage,12    dispatch::DispatchResult,13    ensure, parameter_types,14    traits::{15        Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,16        Randomness, WithdrawReason,17    },18    weights::{19        constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},20        DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,21        WeightToFeePolynomial,22    },23    IsSubType, StorageValue,24};25// use frame_support::weights::{Weight, constants::RocksDbWeight as DbWeight};2627use frame_system::{self as system, ensure_signed, ensure_root};28use sp_runtime::sp_std::prelude::Vec;29use sp_runtime::{30    traits::{31        DispatchInfoOf, Dispatchable, PostDispatchInfoOf, SaturatedConversion, Saturating,32        SignedExtension, Zero,33    },34    transaction_validity::{35        InvalidTransaction, TransactionPriority, TransactionValidity, TransactionValidityError,36        ValidTransaction,37    },38    FixedPointOperand, FixedU128,39};4041#[cfg(test)]42mod mock;4344#[cfg(test)]45mod tests;4647// Structs48// #region4950#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]51#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]52pub enum CollectionMode {53    Invalid,54    // custom data size55    NFT(u32),56    // decimal points57    Fungible(u32),58    // custom data size and decimal points59    ReFungible(u32, u32),60}6162impl Into<u8> for CollectionMode {63    fn into(self) -> u8 {64        match self {65            CollectionMode::Invalid => 0,66            CollectionMode::NFT(_) => 1,67            CollectionMode::Fungible(_) => 2,68            CollectionMode::ReFungible(_, _) => 3,69        }70    }71}7273#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]74#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]75pub enum AccessMode {76    Normal,77    WhiteList,78}79impl Default for AccessMode {80    fn default() -> Self {81        Self::Normal82    }83}8485impl Default for CollectionMode {86    fn default() -> Self {87        Self::Invalid88    }89}9091#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]92#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]93pub struct Ownership<AccountId> {94    pub owner: AccountId,95    pub fraction: u128,96}9798#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]99#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]100pub struct CollectionType<AccountId> {101    pub owner: AccountId,102    pub mode: CollectionMode,103    pub access: AccessMode,104    pub decimal_points: u32,105    pub name: Vec<u16>,        // 64 include null escape char106    pub description: Vec<u16>, // 256 include null escape char107    pub token_prefix: Vec<u8>, // 16 include null escape char108    pub custom_data_size: u32,109    pub mint_mode: bool,110    pub offchain_schema: Vec<u8>,111    pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender112    pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship113}114115#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]116#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]117pub struct CollectionAdminsType<AccountId> {118    pub admin: AccountId,119    pub collection_id: u64,120}121122#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]123#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]124pub struct NftItemType<AccountId> {125    pub collection: u64,126    pub owner: AccountId,127    pub data: Vec<u8>,128}129130#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]131#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]132pub struct FungibleItemType<AccountId> {133    pub collection: u64,134    pub owner: AccountId,135    pub value: u128,136}137138#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]139#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]140pub struct ReFungibleItemType<AccountId> {141    pub collection: u64,142    pub owner: Vec<Ownership<AccountId>>,143    pub data: Vec<u8>,144}145146#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]147#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]148pub struct ApprovePermissions<AccountId> {149    pub approved: AccountId,150    pub amount: u64,151}152153#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]154#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]155pub struct VestingItem<AccountId, Moment> {156    pub sender: AccountId,157    pub recipient: AccountId,158    pub collection_id: u64,159    pub item_id: u64,160    pub amount: u64,161    pub vesting_date: Moment,162}163164#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]165#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]166pub struct BasketItem<AccountId, BlockNumber> {167    pub address: AccountId,168    pub start_block: BlockNumber,169}170171#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]172#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]173pub struct ChainLimits {174    pub collection_numbers_limit: u64,175    pub account_token_ownership_limit: u64,176    pub collections_admins_limit: u64,177    pub custom_data_limit: u32,178179    // Timeouts for item types in passed blocks180    pub nft_sponsor_transfer_timeout: u32,181    pub fungible_sponsor_transfer_timeout: u32,182    pub refungible_sponsor_transfer_timeout: u32,183}184185pub trait Trait: system::Trait {186    type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;187}188189#[cfg(feature = "runtime-benchmarks")]190mod benchmarking;191192// #endregion193194decl_storage! {195    trait Store for Module<T: Trait> as Nft {196197        // Private members198        NextCollectionID: u64;199        CreatedCollectionCount: u64;200        ChainVersion: u64;201        ItemListIndex: map hasher(blake2_128_concat) u64 => u64;202203        // Chain limits struct204        pub ChainLimit get(fn chain_limit) config(): ChainLimits;205206        // Bound counters207        CollectionCount: u64;208        pub AccountItemCount get(fn account_item_count): map hasher(identity) T::AccountId => u64;209210        // Basic collections211        pub Collection get(fn collection) config(): map hasher(identity) u64 => CollectionType<T::AccountId>;212        pub AdminList get(fn admin_list_collection): map hasher(identity) u64 => Vec<T::AccountId>;213        pub WhiteList get(fn white_list): map hasher(identity) u64 => Vec<T::AccountId>;214215        /// Balance owner per collection map216        pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => u64;217218        /// second parameter: item id + owner account id219        pub ApprovedList get(fn approved): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) (u64, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;220221        /// Item collections222        pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => NftItemType<T::AccountId>;223        pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => FungibleItemType<T::AccountId>;224        pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => ReFungibleItemType<T::AccountId>;225226        /// Index list227        pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => Vec<u64>;228229        /// Tokens transfer baskets230        pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => T::BlockNumber;231        pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => Vec<BasketItem<T::AccountId, T::BlockNumber>>;232        pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => T::BlockNumber;233234        // Sponsorship235        pub ContractSponsor get(fn contract_sponsor): map hasher(identity) T::AccountId => T::AccountId;236        pub UnconfirmedContractSponsor get(fn unconfirmed_contract_sponsor): map hasher(identity) T::AccountId => T::AccountId;237    }238    add_extra_genesis {239        build(|config: &GenesisConfig<T>| {240            // Modification of storage241            for (_num, _c) in &config.collection {242                <Module<T>>::init_collection(_c);243            }244245            for (_num, _q, _i) in &config.nft_item_id {246                <Module<T>>::init_nft_token(_i);247            }248249            for (_num, _q, _i) in &config.fungible_item_id {250                <Module<T>>::init_fungible_token(_i);251            }252253            for (_num, _q, _i) in &config.refungible_item_id {254                <Module<T>>::init_refungible_token(_i);255            }256        })257    }258}259260decl_event!(261    pub enum Event<T>262    where263        AccountId = <T as system::Trait>::AccountId,264    {265        /// New collection was created266        /// 267        /// # Arguments268        /// 269        /// * collection_id: Globally unique identifier of newly created collection.270        /// 271        /// * mode: [CollectionMode] converted into u8.272        /// 273        /// * account_id: Collection owner.274        Created(u64, u8, AccountId),275276        /// New item was created.277        /// 278        /// # Arguments279        /// 280        /// * collection_id: Id of the collection where item was created.281        /// 282        /// * item_id: Id of an item. Unique within the collection.283        ItemCreated(u64, u64),284285        /// Collection item was burned.286        /// 287        /// # Arguments288        /// 289        /// collection_id.290        /// 291        /// item_id: Identifier of burned NFT.292        ItemDestroyed(u64, u64),293    }294);295296decl_module! {297    pub struct Module<T: Trait> for enum Call where origin: T::Origin {298299        fn deposit_event() = default;300301        fn on_initialize(now: T::BlockNumber) -> Weight {302303            if ChainVersion::get() < 2304            {305                let value = NextCollectionID::get();306                CreatedCollectionCount::put(value);307                ChainVersion::put(2);308            }309310            0311        }312313        /// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.314        /// 315        /// # Permissions316        /// 317        /// * Anyone.318        /// 319        /// # Arguments320        /// 321        /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.322        /// 323        /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.324        /// 325        /// * token_prefix: UTF-8 string with token prefix.326        /// 327        /// * mode: [CollectionMode] collection type and type dependent data.328        // returns collection ID329        #[weight =330        (70_000_000 as Weight)331        .saturating_add(RocksDbWeight::get().reads(7 as Weight))332        .saturating_add(RocksDbWeight::get().writes(5 as Weight))]333        pub fn create_collection(origin,334                                 collection_name: Vec<u16>,335                                 collection_description: Vec<u16>,336                                 token_prefix: Vec<u8>,337                                 mode: CollectionMode) -> DispatchResult {338339            // Anyone can create a collection340            let who = ensure_signed(origin)?;341            let custom_data_size = match mode {342                CollectionMode::NFT(size) => {343344                    // bound Custom data size345                    ensure!(size < ChainLimit::get().custom_data_limit, "Custom data size bound exceeded");346                    size347                },348                CollectionMode::ReFungible(size, _) => {349350                    // bound Custom data size351                    ensure!(size < ChainLimit::get().custom_data_limit, "Custom data size bound exceeded");352                    size353                },354                _ => 0355            };356357            let decimal_points = match mode {358                CollectionMode::Fungible(points) => points,359                CollectionMode::ReFungible(_, points) => points,360                _ => 0361            };362363            // bound Total number of collections364            ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, "Total collections bound exceeded");365366            // check params367            ensure!(decimal_points <= 4, "decimal_points parameter must be lower than 4");368369            let mut name = collection_name.to_vec();370            name.push(0);371            ensure!(name.len() <= 64, "Collection name can not be longer than 63 char");372373            let mut description = collection_description.to_vec();374            description.push(0);375            ensure!(name.len() <= 256, "Collection description can not be longer than 255 char");376377            let mut prefix = token_prefix.to_vec();378            prefix.push(0);379            ensure!(prefix.len() <= 16, "Token prefix can not be longer than 15 char");380381            // Generate next collection ID382            let next_id = CreatedCollectionCount::get()383                .checked_add(1)384                .expect("collection id error");385386            // bound counter387            let total = CollectionCount::get()388                .checked_add(1)389                .expect("collection counter error");390391            CreatedCollectionCount::put(next_id);392            CollectionCount::put(total);393394            // Create new collection395            let new_collection = CollectionType {396                owner: who.clone(),397                name: name,398                mode: mode.clone(),399                mint_mode: false,400                access: AccessMode::Normal,401                description: description,402                decimal_points: decimal_points,403                token_prefix: prefix,404                offchain_schema: Vec::new(),405                custom_data_size: custom_data_size,406                sponsor: T::AccountId::default(),407                unconfirmed_sponsor: T::AccountId::default(),408            };409410            // Add new collection to map411            <Collection<T>>::insert(next_id, new_collection);412413            // call event414            Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));415416            Ok(())417        }418419        /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.420        /// 421        /// # Permissions422        /// 423        /// * Collection Owner.424        /// 425        /// # Arguments426        /// 427        /// * collection_id: collection to destroy.428        #[weight =429        (90_000_000 as Weight)430        .saturating_add(RocksDbWeight::get().reads(2 as Weight))431        .saturating_add(RocksDbWeight::get().writes(5 as Weight))]432        pub fn destroy_collection(origin, collection_id: u64) -> DispatchResult {433434            let sender = ensure_signed(origin)?;435            Self::check_owner_permissions(collection_id, sender)?;436437            <AddressTokens<T>>::remove_prefix(collection_id);438            <ApprovedList<T>>::remove_prefix(collection_id);439            <Balance<T>>::remove_prefix(collection_id);440            <ItemListIndex>::remove(collection_id);441            <AdminList<T>>::remove(collection_id);442            <Collection<T>>::remove(collection_id);443            <WhiteList<T>>::remove(collection_id);444445            <NftItemList<T>>::remove_prefix(collection_id);446            <FungibleItemList<T>>::remove_prefix(collection_id);447            <ReFungibleItemList<T>>::remove_prefix(collection_id);448449            <NftTransferBasket<T>>::remove_prefix(collection_id);450            <FungibleTransferBasket<T>>::remove_prefix(collection_id);451            <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);452453            if CollectionCount::get() > 0454            {455                // bound couter456                let total = CollectionCount::get()457                    .checked_sub(1)458                    .expect("collection counter error");459460                CollectionCount::put(total);461            }462463            Ok(())464        }465466        /// Add an address to white list.467        /// 468        /// # Permissions469        /// 470        /// * Collection Owner471        /// * Collection Admin472        /// 473        /// # Arguments474        /// 475        /// * collection_id.476        /// 477        /// * address.478        #[weight =479        (30_000_000 as Weight)480        .saturating_add(RocksDbWeight::get().reads(3 as Weight))481        .saturating_add(RocksDbWeight::get().writes(1 as Weight))]482        pub fn add_to_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{483484            let sender = ensure_signed(origin)?;485            Self::check_owner_or_admin_permissions(collection_id, sender)?;486487            let mut white_list_collection: Vec<T::AccountId>;488            if <WhiteList<T>>::contains_key(collection_id) {489                white_list_collection = <WhiteList<T>>::get(collection_id);490                if !white_list_collection.contains(&address.clone())491                {492                    white_list_collection.push(address.clone());493                }494            }495            else {496                white_list_collection = Vec::new();497                white_list_collection.push(address.clone());498            }499500            <WhiteList<T>>::insert(collection_id, white_list_collection);501            Ok(())502        }503504        /// Remove an address from white list.505        /// 506        /// # Permissions507        /// 508        /// * Collection Owner509        /// * Collection Admin510        /// 511        /// # Arguments512        /// 513        /// * collection_id.514        /// 515        /// * address.516        #[weight =517        (35_000_000 as Weight)518        .saturating_add(RocksDbWeight::get().reads(3 as Weight))519        .saturating_add(RocksDbWeight::get().writes(1 as Weight))]520        pub fn remove_from_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{521522            let sender = ensure_signed(origin)?;523            Self::check_owner_or_admin_permissions(collection_id, sender)?;524525            if <WhiteList<T>>::contains_key(collection_id) {526                let mut white_list_collection = <WhiteList<T>>::get(collection_id);527                if white_list_collection.contains(&address.clone())528                {529                    white_list_collection.retain(|i| *i != address.clone());530                    <WhiteList<T>>::insert(collection_id, white_list_collection);531                }532            }533534            Ok(())535        }536537        /// Toggle between normal and white list access for the methods with access for `Anyone`.538        /// 539        /// # Permissions540        /// 541        /// * Collection Owner.542        /// 543        /// # Arguments544        /// 545        /// * collection_id.546        /// 547        /// * mode: [AccessMode]548        #[weight =549        (27_000_000 as Weight)550        .saturating_add(RocksDbWeight::get().reads(1 as Weight))551        .saturating_add(RocksDbWeight::get().writes(1 as Weight))]552        pub fn set_public_access_mode(origin, collection_id: u64, mode: AccessMode) -> DispatchResult553        {554            let sender = ensure_signed(origin)?;555556            Self::check_owner_permissions(collection_id, sender)?;557            let mut target_collection = <Collection<T>>::get(collection_id);558            target_collection.access = mode;559            <Collection<T>>::insert(collection_id, target_collection);560561            Ok(())562        }563564        /// Allows Anyone to create tokens if:565        /// * White List is enabled, and566        /// * Address is added to white list, and567        /// * This method was called with True parameter568        /// 569        /// # Permissions570        /// * Collection Owner571        ///572        /// # Arguments573        /// 574        /// * collection_id.575        /// 576        /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.577        #[weight =578        (27_000_000 as Weight)579        .saturating_add(RocksDbWeight::get().reads(1 as Weight))580        .saturating_add(RocksDbWeight::get().writes(1 as Weight))]581        pub fn set_mint_permission(origin, collection_id: u64, mint_permission: bool) -> DispatchResult582        {583            let sender = ensure_signed(origin)?;584585            Self::check_owner_permissions(collection_id, sender)?;586            let mut target_collection = <Collection<T>>::get(collection_id);587            target_collection.mint_mode = mint_permission;588            <Collection<T>>::insert(collection_id, target_collection);589590            Ok(())591        }592593        /// Change the owner of the collection.594        /// 595        /// # Permissions596        /// 597        /// * Collection Owner.598        /// 599        /// # Arguments600        /// 601        /// * collection_id.602        /// 603        /// * new_owner.604        #[weight =605        (27_000_000 as Weight)606        .saturating_add(RocksDbWeight::get().reads(1 as Weight))607        .saturating_add(RocksDbWeight::get().writes(1 as Weight))]608        pub fn change_collection_owner(origin, collection_id: u64, new_owner: T::AccountId) -> DispatchResult {609610            let sender = ensure_signed(origin)?;611            Self::check_owner_permissions(collection_id, sender)?;612            let mut target_collection = <Collection<T>>::get(collection_id);613            target_collection.owner = new_owner;614            <Collection<T>>::insert(collection_id, target_collection);615616            Ok(())617        }618619        /// Adds an admin of the Collection.620        /// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership. 621        /// 622        /// # Permissions623        /// 624        /// * Collection Owner.625        /// * Collection Admin.626        /// 627        /// # Arguments628        /// 629        /// * collection_id: ID of the Collection to add admin for.630        /// 631        /// * new_admin_id: Address of new admin to add.632        #[weight =633        (32_000_000 as Weight)634        .saturating_add(RocksDbWeight::get().reads(3 as Weight))635        .saturating_add(RocksDbWeight::get().writes(1 as Weight))]636        pub fn add_collection_admin(origin, collection_id: u64, new_admin_id: T::AccountId) -> DispatchResult {637638            let sender = ensure_signed(origin)?;639            Self::check_owner_or_admin_permissions(collection_id, sender)?;640            let mut admin_arr: Vec<T::AccountId> = Vec::new();641642            if <AdminList<T>>::contains_key(collection_id)643            {644                admin_arr = <AdminList<T>>::get(collection_id);645                ensure!(!admin_arr.contains(&new_admin_id), "Account already has admin role");646            }647648            // Number of collection admins649            ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, "Number of collection admins bound exceeded");650651            admin_arr.push(new_admin_id);652            <AdminList<T>>::insert(collection_id, admin_arr);653654            Ok(())655        }656657        /// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.658        ///659        /// # Permissions660        /// 661        /// * Collection Owner.662        /// * Collection Admin.663        /// 664        /// # Arguments665        /// 666        /// * collection_id: ID of the Collection to remove admin for.667        /// 668        /// * account_id: Address of admin to remove.669        #[weight =670        (50_000_000 as Weight)671        .saturating_add(RocksDbWeight::get().reads(2 as Weight))672        .saturating_add(RocksDbWeight::get().writes(1 as Weight))]673        pub fn remove_collection_admin(origin, collection_id: u64, account_id: T::AccountId) -> DispatchResult {674675            let sender = ensure_signed(origin)?;676            Self::check_owner_or_admin_permissions(collection_id, sender)?;677678            if <AdminList<T>>::contains_key(collection_id)679            {680                let mut admin_arr = <AdminList<T>>::get(collection_id);681                admin_arr.retain(|i| *i != account_id);682                <AdminList<T>>::insert(collection_id, admin_arr);683            }684685            Ok(())686        }687688        /// # Permissions689        /// 690        /// * Collection Owner691        /// 692        /// # Arguments693        /// 694        /// * collection_id.695        /// 696        /// * new_sponsor.697        #[weight =698        (32_000_000 as Weight)699        .saturating_add(RocksDbWeight::get().reads(2 as Weight))700        .saturating_add(RocksDbWeight::get().writes(1 as Weight))]701        pub fn set_collection_sponsor(origin, collection_id: u64, new_sponsor: T::AccountId) -> DispatchResult {702703            let sender = ensure_signed(origin)?;704            ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");705706            let mut target_collection = <Collection<T>>::get(collection_id);707            ensure!(sender == target_collection.owner, "You do not own this collection");708709            target_collection.unconfirmed_sponsor = new_sponsor;710            <Collection<T>>::insert(collection_id, target_collection);711712            Ok(())713        }714715        /// # Permissions716        /// 717        /// * Sponsor.718        /// 719        /// # Arguments720        /// 721        /// * collection_id.722        #[weight =723        (22_000_000 as Weight)724        .saturating_add(RocksDbWeight::get().reads(1 as Weight))725        .saturating_add(RocksDbWeight::get().writes(1 as Weight))]726        pub fn confirm_sponsorship(origin, collection_id: u64) -> DispatchResult {727728            let sender = ensure_signed(origin)?;729            ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");730731            let mut target_collection = <Collection<T>>::get(collection_id);732            ensure!(sender == target_collection.unconfirmed_sponsor, "This address is not set as sponsor, use setCollectionSponsor first");733734            target_collection.sponsor = target_collection.unconfirmed_sponsor;735            target_collection.unconfirmed_sponsor = T::AccountId::default();736            <Collection<T>>::insert(collection_id, target_collection);737738            Ok(())739        }740741        /// Switch back to pay-per-own-transaction model.742        ///743        /// # Permissions744        ///745        /// * Collection owner.746        /// 747        /// # Arguments748        /// 749        /// * collection_id.750        #[weight =751        (24_000_000 as Weight)752        .saturating_add(RocksDbWeight::get().reads(1 as Weight))753        .saturating_add(RocksDbWeight::get().writes(1 as Weight))]754        pub fn remove_collection_sponsor(origin, collection_id: u64) -> DispatchResult {755756            let sender = ensure_signed(origin)?;757            ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");758759            let mut target_collection = <Collection<T>>::get(collection_id);760            ensure!(sender == target_collection.owner, "You do not own this collection");761762            target_collection.sponsor = T::AccountId::default();763            <Collection<T>>::insert(collection_id, target_collection);764765            Ok(())766        }767768        /// This method creates a concrete instance of NFT Collection created with CreateCollection method.769        /// 770        /// # Permissions771        /// 772        /// * Collection Owner.773        /// * Collection Admin.774        /// * Anyone if775        ///     * White List is enabled, and776        ///     * Address is added to white list, and777        ///     * MintPermission is enabled (see SetMintPermission method)778        /// 779        /// # Arguments780        /// 781        /// * collection_id: ID of the collection.782        /// 783        /// * properties: Array of bytes that contains NFT properties. Since NFT Module is agnostic of properties meaning, it is treated purely as an array of bytes.784        /// 785        /// * owner: Address, initial owner of the NFT.786        #[weight =787        (130_000_000 as Weight)788        .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))789        .saturating_add(RocksDbWeight::get().reads(10 as Weight))790        .saturating_add(RocksDbWeight::get().writes(8 as Weight))]791        pub fn create_item(origin, collection_id: u64, properties: Vec<u8>, owner: T::AccountId) -> DispatchResult {792793            let sender = ensure_signed(origin)?;794            Self::collection_exists(collection_id)?;795            let target_collection = <Collection<T>>::get(collection_id);796797            if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {798                ensure!(target_collection.mint_mode == true, "Public minting is not allowed for this collection");799                Self::check_white_list(collection_id, &owner)?;800                Self::check_white_list(collection_id, &sender)?;801            }802803            match target_collection.mode804            {805                CollectionMode::NFT(_) => {806807                    // check size808                    ensure!(target_collection.custom_data_size >= properties.len() as u32, "Size of item is too large");809810                    // Create nft item811                    let item = NftItemType {812                        collection: collection_id,813                        owner: owner,814                        data: properties.clone(),815                    };816817                    Self::add_nft_item(item)?;818819                },820                CollectionMode::Fungible(_) => {821822                    // check size823                    ensure!(properties.len() as u32 == 0, "Size of item must be 0 with fungible type");824825                    let item = FungibleItemType {826                        collection: collection_id,827                        owner: owner,828                        value: (10 as u128).pow(target_collection.decimal_points)829                    };830831                    Self::add_fungible_item(item)?;832                },833                CollectionMode::ReFungible(_, _) => {834835                    // check size836                    ensure!(target_collection.custom_data_size >= properties.len() as u32, "Size of item is too large");837838                    let mut owner_list = Vec::new();839                    let value = (10 as u128).pow(target_collection.decimal_points);840                    owner_list.push(Ownership {owner: owner.clone(), fraction: value});841842                    let item = ReFungibleItemType {843                        collection: collection_id,844                        owner: owner_list,845                        data: properties.clone()846                    };847848                    Self::add_refungible_item(item)?;849                },850                _ => { ensure!(1 == 0,"just error"); }851852            };853854            // call event855            Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));856857            Ok(())858        }859860        /// Destroys a concrete instance of NFT.861        /// 862        /// # Permissions863        /// 864        /// * Collection Owner.865        /// * Collection Admin.866        /// * Current NFT Owner.867        /// 868        /// # Arguments869        /// 870        /// * collection_id: ID of the collection.871        /// 872        /// * item_id: ID of NFT to burn.873        #[weight =874        (170_000_000 as Weight)875        .saturating_add(RocksDbWeight::get().reads(9 as Weight))876        .saturating_add(RocksDbWeight::get().writes(7 as Weight))]877        pub fn burn_item(origin, collection_id: u64, item_id: u64) -> DispatchResult {878879            let sender = ensure_signed(origin)?;880            Self::collection_exists(collection_id)?;881882            // Transfer permissions check883            let target_collection = <Collection<T>>::get(collection_id);884            ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||885                Self::is_owner_or_admin_permissions(collection_id, sender.clone()),886                "Only item owner, collection owner and admins can modify item");887888            if target_collection.access == AccessMode::WhiteList {889                Self::check_white_list(collection_id, &sender)?;890            }891892            match target_collection.mode893            {894                CollectionMode::NFT(_) => Self::burn_nft_item(collection_id, item_id)?,895                CollectionMode::Fungible(_)  => Self::burn_fungible_item(collection_id, item_id)?,896                CollectionMode::ReFungible(_, _)  => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,897                _ => ()898            };899900            // call event901            Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));902903            Ok(())904        }905906        /// Change ownership of the token.907        /// 908        /// # Permissions909        /// 910        /// * Collection Owner911        /// * Collection Admin912        /// * Current NFT owner913        ///914        /// # Arguments915        /// 916        /// * recipient: Address of token recipient.917        /// 918        /// * collection_id.919        /// 920        /// * item_id: ID of the item921        ///     * Non-Fungible Mode: Required.922        ///     * Fungible Mode: Ignored.923        ///     * Re-Fungible Mode: Required.924        /// 925        /// * value: Amount to transfer.926        ///     * Non-Fungible Mode: Ignored927        ///     * Fungible Mode: Must specify transferred amount928        ///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)929        #[weight =930        (125_000_000 as Weight)931        .saturating_add(RocksDbWeight::get().reads(7 as Weight))932        .saturating_add(RocksDbWeight::get().writes(7 as Weight))]933        pub fn transfer(origin, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64) -> DispatchResult {934935            let sender = ensure_signed(origin)?;936937            // Transfer permissions check938            let target_collection = <Collection<T>>::get(collection_id);939            ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||940                Self::is_owner_or_admin_permissions(collection_id, sender.clone()),941                "Only item owner, collection owner and admins can modify item");942943            if target_collection.access == AccessMode::WhiteList {944                Self::check_white_list(collection_id, &sender)?;945                Self::check_white_list(collection_id, &recipient)?;946            }947948            match target_collection.mode949            {950                CollectionMode::NFT(_) => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,951                CollectionMode::Fungible(_)  => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,952                CollectionMode::ReFungible(_, _)  => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,953                _ => ()954            };955956            Ok(())957        }958959        /// Set, change, or remove approved address to transfer the ownership of the NFT.960        /// 961        /// # Permissions962        /// 963        /// * Collection Owner964        /// * Collection Admin965        /// * Current NFT owner966        /// 967        /// # Arguments968        /// 969        /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).970        /// 971        /// * collection_id.972        /// 973        /// * item_id: ID of the item.974        #[weight =975        (45_000_000 as Weight)976        .saturating_add(RocksDbWeight::get().reads(3 as Weight))977        .saturating_add(RocksDbWeight::get().writes(1 as Weight))]978        pub fn approve(origin, approved: T::AccountId, collection_id: u64, item_id: u64) -> DispatchResult {979980            let sender = ensure_signed(origin)?;981982            // Transfer permissions check983            let target_collection = <Collection<T>>::get(collection_id);984            ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||985                Self::is_owner_or_admin_permissions(collection_id, sender.clone()),986                "Only item owner, collection owner and admins can approve");987988            if target_collection.access == AccessMode::WhiteList {989                Self::check_white_list(collection_id, &sender)?;990                Self::check_white_list(collection_id, &approved)?;991            }992993            // amount param stub994            let amount = 100000000;995996            let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));997            if list_exists {998999                let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));1000                let item_contains = list.iter().any(|i| i.approved == approved);10011002                if !item_contains {1003                    list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1004                    <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1005                }1006            } else {10071008                let mut list = Vec::new();1009                list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1010                <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1011            }10121013            Ok(())1014        }1015        1016        /// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.1017        /// 1018        /// # Permissions1019        /// * Collection Owner1020        /// * Collection Admin1021        /// * Current NFT owner1022        /// * Address approved by current NFT owner1023        /// 1024        /// # Arguments1025        /// 1026        /// * from: Address that owns token.1027        /// 1028        /// * recipient: Address of token recipient.1029        /// 1030        /// * collection_id.1031        /// 1032        /// * item_id: ID of the item.1033        /// 1034        /// * value: Amount to transfer.1035        #[weight =1036        (150_000_000 as Weight)1037        .saturating_add(RocksDbWeight::get().reads(9 as Weight))1038        .saturating_add(RocksDbWeight::get().writes(8 as Weight))]1039        pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64 ) -> DispatchResult {10401041            let sender = ensure_signed(origin)?;1042            let mut appoved_transfer = false;10431044            // Check approve1045            if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {1046                let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));1047                let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());1048                if opt_item.is_some()1049                {1050                    appoved_transfer = true;1051                    ensure!(opt_item.unwrap().amount >= value, "Requested value more than approved");1052                }1053            }10541055            // Transfer permissions check1056            let target_collection = <Collection<T>>::get(collection_id);1057            ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1058                "Only item owner, collection owner and admins can modify items");10591060            if target_collection.access == AccessMode::WhiteList {1061                Self::check_white_list(collection_id, &sender)?;1062                Self::check_white_list(collection_id, &recipient)?;1063            }10641065            // remove approve1066            let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))1067                .into_iter().filter(|i| i.approved != sender.clone()).collect();1068            <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);106910701071            match target_collection.mode1072            {1073                CollectionMode::NFT(_) => Self::transfer_nft(collection_id, item_id, from, recipient)?,1074                CollectionMode::Fungible(_)  => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,1075                CollectionMode::ReFungible(_, _)  => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1076                _ => ()1077            };10781079            Ok(())1080        }10811082        ///1083        #[weight = 0]1084        pub fn safe_transfer_from(origin, collection_id: u64, item_id: u64, new_owner: T::AccountId) -> DispatchResult {10851086            // let no_perm_mes = "You do not have permissions to modify this collection";1087            // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1088            // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1089            // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);10901091            // // on_nft_received  call10921093            // Self::transfer(origin, collection_id, item_id, new_owner)?;10941095            Ok(())1096        }10971098        /// Set off-chain data schema.1099        /// 1100        /// # Permissions1101        /// 1102        /// * Collection Owner1103        /// * Collection Admin1104        /// 1105        /// # Arguments1106        /// 1107        /// * collection_id.1108        /// 1109        /// * schema: String representing the offchain data schema.1110        #[weight =1111        (33_000_000 as Weight)1112        .saturating_add(RocksDbWeight::get().reads(2 as Weight))1113        .saturating_add(RocksDbWeight::get().writes(1 as Weight))]1114        pub fn set_offchain_schema(1115            origin,1116            collection_id: u64,1117            schema: Vec<u8>1118        ) -> DispatchResult {1119            let sender = ensure_signed(origin)?;1120            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;11211122            let mut target_collection = <Collection<T>>::get(collection_id);1123            target_collection.offchain_schema = schema;1124            <Collection<T>>::insert(collection_id, target_collection);11251126            Ok(())1127        }11281129        // Sudo permissions function1130        #[weight = 0]1131        pub fn set_chain_limits(1132            origin,1133            limits: ChainLimits1134        ) -> DispatchResult {1135            ensure_root(origin)?;1136            <ChainLimit>::put(limits);1137            Ok(())1138        }        1139    }1140}11411142impl<T: Trait> Module<T> {1143    fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {1144        let current_index = <ItemListIndex>::get(item.collection)1145            .checked_add(1)1146            .expect("Item list index id error");1147        let itemcopy = item.clone();1148        let owner = item.owner.clone();1149        let value = item.value as u64;11501151        Self::add_token_index(item.collection, current_index, owner.clone())?;11521153        <ItemListIndex>::insert(item.collection, current_index);1154        <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);11551156        // Add current block1157        let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();1158        <FungibleTransferBasket<T>>::insert(item.collection, current_index, v);1159        1160        // Update balance1161        let new_balance = <Balance<T>>::get(item.collection, owner.clone())1162            .checked_add(value)1163            .unwrap();1164        <Balance<T>>::insert(item.collection, owner.clone(), new_balance);11651166        Ok(())1167    }11681169    fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1170        let current_index = <ItemListIndex>::get(item.collection)1171            .checked_add(1)1172            .expect("Item list index id error");1173        let itemcopy = item.clone();11741175        let value = item.owner.first().unwrap().fraction as u64;1176        let owner = item.owner.first().unwrap().owner.clone();11771178        Self::add_token_index(item.collection, current_index, owner.clone())?;11791180        <ItemListIndex>::insert(item.collection, current_index);1181        <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);11821183        // Add current block1184        let block_number: T::BlockNumber = 0.into();1185        <ReFungibleTransferBasket<T>>::insert(item.collection, current_index, block_number);11861187        // Update balance1188        let new_balance = <Balance<T>>::get(item.collection, owner.clone())1189            .checked_add(value)1190            .unwrap();1191        <Balance<T>>::insert(item.collection, owner.clone(), new_balance);11921193        Ok(())1194    }11951196    fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {1197        let current_index = <ItemListIndex>::get(item.collection)1198            .checked_add(1)1199            .expect("Item list index id error");12001201        let item_owner = item.owner.clone();1202        let collection_id = item.collection.clone();1203        Self::add_token_index(collection_id, current_index, item.owner.clone())?;12041205        <ItemListIndex>::insert(collection_id, current_index);1206        <NftItemList<T>>::insert(collection_id, current_index, item);12071208        // Add current block1209        let block_number: T::BlockNumber = 0.into();1210        <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);12111212        // Update balance1213        let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1214            .checked_add(1)1215            .unwrap();1216        <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);12171218        Ok(())1219    }12201221    fn burn_refungible_item(1222        collection_id: u64,1223        item_id: u64,1224        owner: T::AccountId,1225    ) -> DispatchResult {1226        ensure!(1227            <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1228            "Item does not exists"1229        );1230        let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1231        let item = collection1232            .owner1233            .iter()1234            .filter(|&i| i.owner == owner)1235            .next()1236            .unwrap();1237        Self::remove_token_index(collection_id, item_id, owner.clone())?;12381239        // remove approve list1240        <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));12411242        // update balance1243        let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1244            .checked_sub(item.fraction as u64)1245            .unwrap();1246        <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);12471248        <ReFungibleItemList<T>>::remove(collection_id, item_id);12491250        Ok(())1251    }12521253    fn burn_nft_item(collection_id: u64, item_id: u64) -> DispatchResult {1254        ensure!(1255            <NftItemList<T>>::contains_key(collection_id, item_id),1256            "Item does not exists"1257        );1258        let item = <NftItemList<T>>::get(collection_id, item_id);1259        Self::remove_token_index(collection_id, item_id, item.owner.clone())?;12601261        // remove approve list1262        <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));12631264        // update balance1265        let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1266            .checked_sub(1)1267            .unwrap();1268        <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1269        <NftItemList<T>>::remove(collection_id, item_id);12701271        Ok(())1272    }12731274    fn burn_fungible_item(collection_id: u64, item_id: u64) -> DispatchResult {1275        ensure!(1276            <FungibleItemList<T>>::contains_key(collection_id, item_id),1277            "Item does not exists"1278        );1279        let item = <FungibleItemList<T>>::get(collection_id, item_id);1280        Self::remove_token_index(collection_id, item_id, item.owner.clone())?;12811282        // remove approve list1283        <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));12841285        // update balance1286        let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1287            .checked_sub(item.value as u64)1288            .unwrap();1289        <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);12901291        <FungibleItemList<T>>::remove(collection_id, item_id);12921293        Ok(())1294    }12951296    fn collection_exists(collection_id: u64) -> DispatchResult {1297        ensure!(1298            <Collection<T>>::contains_key(collection_id),1299            "This collection does not exist"1300        );1301        Ok(())1302    }13031304    fn check_owner_permissions(collection_id: u64, subject: T::AccountId) -> DispatchResult {1305        Self::collection_exists(collection_id)?;13061307        let target_collection = <Collection<T>>::get(collection_id);1308        ensure!(1309            subject == target_collection.owner,1310            "You do not own this collection"1311        );13121313        Ok(())1314    }13151316    fn is_owner_or_admin_permissions(collection_id: u64, subject: T::AccountId) -> bool {1317        let target_collection = <Collection<T>>::get(collection_id);1318        let mut result: bool = subject == target_collection.owner;1319        let exists = <AdminList<T>>::contains_key(collection_id);13201321        if !result & exists {1322            if <AdminList<T>>::get(collection_id).contains(&subject) {1323                result = true1324            }1325        }13261327        result1328    }13291330    fn check_owner_or_admin_permissions(1331        collection_id: u64,1332        subject: T::AccountId,1333    ) -> DispatchResult {1334        Self::collection_exists(collection_id)?;1335        let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());13361337        ensure!(1338            result,1339            "You do not have permissions to modify this collection"1340        );1341        Ok(())1342    }13431344    fn is_item_owner(subject: T::AccountId, collection_id: u64, item_id: u64) -> bool {1345        let target_collection = <Collection<T>>::get(collection_id);13461347        match target_collection.mode {1348            CollectionMode::NFT(_) => {1349                <NftItemList<T>>::get(collection_id, item_id).owner == subject1350            }1351            CollectionMode::Fungible(_) => {1352                <FungibleItemList<T>>::get(collection_id, item_id).owner == subject1353            }1354            CollectionMode::ReFungible(_, _) => {1355                <ReFungibleItemList<T>>::get(collection_id, item_id)1356                    .owner1357                    .iter()1358                    .any(|i| i.owner == subject)1359            }1360            CollectionMode::Invalid => false,1361        }1362    }13631364    fn check_white_list(collection_id: u64, address: &T::AccountId) -> DispatchResult {1365        let mes = "Address is not in white list";1366        ensure!(<WhiteList<T>>::contains_key(collection_id), mes);1367        let wl = <WhiteList<T>>::get(collection_id);1368        ensure!(wl.contains(address), mes);13691370        Ok(())1371    }13721373    fn transfer_fungible(1374        collection_id: u64,1375        item_id: u64,1376        value: u64,1377        owner: T::AccountId,1378        new_owner: T::AccountId,1379    ) -> DispatchResult {1380        ensure!(1381            <FungibleItemList<T>>::contains_key(collection_id, item_id),1382            "Item not exists"1383        );13841385        let full_item = <FungibleItemList<T>>::get(collection_id, item_id);1386        let amount = full_item.value;13871388        ensure!(amount >= value.into(), "Item balance not enouth");13891390        // update balance1391        let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())1392            .checked_sub(value)1393            .unwrap();1394        <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);13951396        let mut new_owner_account_id = 0;1397        let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());1398        if new_owner_items.len() > 0 {1399            new_owner_account_id = new_owner_items[0];1400        }14011402        let val64 = value.into();14031404        // transfer1405        if amount == val64 && new_owner_account_id == 0 {1406            // change owner1407            // new owner do not have account1408            let mut new_full_item = full_item.clone();1409            new_full_item.owner = new_owner.clone();1410            <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);14111412            // update balance1413            let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1414                .checked_add(value)1415                .unwrap();1416            <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);14171418            // update index collection1419            Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;1420        } else {1421            let mut new_full_item = full_item.clone();1422            new_full_item.value -= val64;14231424            // separate amount1425            if new_owner_account_id > 0 {1426                // new owner has account1427                let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);1428                item.value += val64;14291430                // update balance1431                let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1432                    .checked_add(value)1433                    .unwrap();1434                <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);14351436                <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1437            } else {1438                // new owner do not have account1439                let item = FungibleItemType {1440                    collection: collection_id,1441                    owner: new_owner.clone(),1442                    value: val64,1443                };14441445                Self::add_fungible_item(item)?;1446            }14471448            if amount == val64 {1449                Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;14501451                // remove approve list1452                <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1453                <FungibleItemList<T>>::remove(collection_id, item_id);1454            }14551456            <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1457        }14581459        Ok(())1460    }14611462    fn transfer_refungible(1463        collection_id: u64,1464        item_id: u64,1465        value: u64,1466        owner: T::AccountId,1467        new_owner: T::AccountId,1468    ) -> DispatchResult {1469        ensure!(1470            <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1471            "Item not exists"1472        );14731474        let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1475        let item = full_item1476            .owner1477            .iter()1478            .filter(|i| i.owner == owner)1479            .next()1480            .unwrap();1481        let amount = item.fraction;14821483        ensure!(amount >= value.into(), "Item balance not enouth");14841485        // update balance1486        let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1487            .checked_sub(value)1488            .unwrap();1489        <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);14901491        let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1492            .checked_add(value)1493            .unwrap();1494        <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);14951496        let old_owner = item.owner.clone();1497        let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);1498        let val64 = value.into();14991500        // transfer1501        if amount == val64 && !new_owner_has_account {1502            // change owner1503            // new owner do not have account1504            let mut new_full_item = full_item.clone();1505            new_full_item1506                .owner1507                .iter_mut()1508                .find(|i| i.owner == owner)1509                .unwrap()1510                .owner = new_owner.clone();1511            <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);15121513            // update index collection1514            Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1515        } else {1516            let mut new_full_item = full_item.clone();1517            new_full_item1518                .owner1519                .iter_mut()1520                .find(|i| i.owner == owner)1521                .unwrap()1522                .fraction -= val64;15231524            // separate amount1525            if new_owner_has_account {1526                // new owner has account1527                new_full_item1528                    .owner1529                    .iter_mut()1530                    .find(|i| i.owner == new_owner)1531                    .unwrap()1532                    .fraction += val64;1533            } else {1534                // new owner do not have account1535                new_full_item.owner.push(Ownership {1536                    owner: new_owner.clone(),1537                    fraction: val64,1538                });1539                Self::add_token_index(collection_id, item_id, new_owner.clone())?;1540            }15411542            <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1543        }15441545        Ok(())1546    }15471548    fn transfer_nft(1549        collection_id: u64,1550        item_id: u64,1551        sender: T::AccountId,1552        new_owner: T::AccountId,1553    ) -> DispatchResult {1554        ensure!(1555            <NftItemList<T>>::contains_key(collection_id, item_id),1556            "Item not exists"1557        );15581559        let mut item = <NftItemList<T>>::get(collection_id, item_id);15601561        ensure!(1562            sender == item.owner,1563            "sender parameter and item owner must be equal"1564        );15651566        // update balance1567        let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1568            .checked_sub(1)1569            .unwrap();1570        <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);15711572        let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1573            .checked_add(1)1574            .unwrap();1575        <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);15761577        // change owner1578        let old_owner = item.owner.clone();1579        item.owner = new_owner.clone();1580        <NftItemList<T>>::insert(collection_id, item_id, item);15811582        // update index collection1583        Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;15841585        // reset approved list1586        <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));1587        Ok(())1588    }15891590    fn init_collection(item: &CollectionType<T::AccountId>) {1591        // check params1592        assert!(1593            item.decimal_points <= 4,1594            "decimal_points parameter must be lower than 4"1595        );1596        assert!(1597            item.name.len() <= 64,1598            "Collection name can not be longer than 63 char"1599        );1600        assert!(1601            item.name.len() <= 256,1602            "Collection description can not be longer than 255 char"1603        );1604        assert!(1605            item.token_prefix.len() <= 16,1606            "Token prefix can not be longer than 15 char"1607        );16081609        // Generate next collection ID1610        let next_id = CreatedCollectionCount::get()1611            .checked_add(1)1612            .expect("collection id error");16131614        CreatedCollectionCount::put(next_id);1615    }16161617    fn init_nft_token(item: &NftItemType<T::AccountId>) {1618        let current_index = <ItemListIndex>::get(item.collection)1619            .checked_add(1)1620            .expect("Item list index id error");16211622        let item_owner = item.owner.clone();1623        let collection_id = item.collection.clone();1624        Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();16251626        <ItemListIndex>::insert(collection_id, current_index);16271628        // Update balance1629        let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1630            .checked_add(1)1631            .unwrap();1632        <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);1633    }16341635    fn init_fungible_token(item: &FungibleItemType<T::AccountId>) {1636        let current_index = <ItemListIndex>::get(item.collection)1637            .checked_add(1)1638            .expect("Item list index id error");1639        let owner = item.owner.clone();1640        let value = item.value as u64;16411642        Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();16431644        <ItemListIndex>::insert(item.collection, current_index);16451646        // Update balance1647        let new_balance = <Balance<T>>::get(item.collection, owner.clone())1648            .checked_add(value)1649            .unwrap();1650        <Balance<T>>::insert(item.collection, owner.clone(), new_balance);1651    }16521653    fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>) {1654        let current_index = <ItemListIndex>::get(item.collection)1655            .checked_add(1)1656            .expect("Item list index id error");16571658        let value = item.owner.first().unwrap().fraction as u64;1659        let owner = item.owner.first().unwrap().owner.clone();16601661        Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();16621663        <ItemListIndex>::insert(item.collection, current_index);16641665        // Update balance1666        let new_balance = <Balance<T>>::get(item.collection, owner.clone())1667            .checked_add(value)1668            .unwrap();1669        <Balance<T>>::insert(item.collection, owner.clone(), new_balance);1670    }16711672    fn add_token_index(collection_id: u64, item_index: u64, owner: T::AccountId) -> DispatchResult {16731674        // add to account limit1675        if <AccountItemCount<T>>::contains_key(owner.clone()) {16761677            // bound Owned tokens by a single address1678            let count = <AccountItemCount<T>>::get(owner.clone());1679            ensure!(count < ChainLimit::get().account_token_ownership_limit, "Owned tokens by a single address bound exceeded");16801681            <AccountItemCount<T>>::insert(owner.clone(), 1682                count.checked_add(1).unwrap());1683        }1684        else {1685            <AccountItemCount<T>>::insert(owner.clone(), 1);1686        }16871688        let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());1689        if list_exists {1690            let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());1691            let item_contains = list.contains(&item_index.clone());16921693            if !item_contains {1694                list.push(item_index.clone());1695            }16961697            <AddressTokens<T>>::insert(collection_id, owner.clone(), list);1698        } else {1699            let mut itm = Vec::new();1700            itm.push(item_index.clone());1701            <AddressTokens<T>>::insert(collection_id, owner, itm);1702            1703        }17041705        Ok(())1706    }17071708    fn remove_token_index(1709        collection_id: u64,1710        item_index: u64,1711        owner: T::AccountId,1712    ) -> DispatchResult {17131714        // update counter1715        <AccountItemCount<T>>::insert(owner.clone(), 1716            <AccountItemCount<T>>::get(owner.clone()).checked_sub(1).unwrap());171717181719        let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());1720        if list_exists {1721            let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());1722            let item_contains = list.contains(&item_index.clone());17231724            if item_contains {1725                list.retain(|&item| item != item_index);1726                <AddressTokens<T>>::insert(collection_id, owner, list);1727            }1728        }17291730        Ok(())1731    }17321733    fn move_token_index(1734        collection_id: u64,1735        item_index: u64,1736        old_owner: T::AccountId,1737        new_owner: T::AccountId,1738    ) -> DispatchResult {1739        Self::remove_token_index(collection_id, item_index, old_owner)?;1740        Self::add_token_index(collection_id, item_index, new_owner)?;17411742        Ok(())1743    }1744}17451746////////////////////////////////////////////////////////////////////////////////////////////////////1747// Economic models1748// #region17491750/// Fee multiplier.1751pub type Multiplier = FixedU128;17521753type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<1754    <T as system::Trait>::AccountId,1755>>::Balance;1756type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<1757    <T as system::Trait>::AccountId,1758>>::NegativeImbalance;17591760/// Require the transactor pay for themselves and maybe include a tip to gain additional priority1761/// in the queue.1762#[derive(Encode, Decode, Clone, Eq, PartialEq)]1763pub struct ChargeTransactionPayment<T: transaction_payment::Trait + Send + Sync>(1764    #[codec(compact)] BalanceOf<T>,1765);17661767impl<T: Trait + transaction_payment::Trait + Send + Sync> sp_std::fmt::Debug1768    for ChargeTransactionPayment<T>1769{1770    #[cfg(feature = "std")]1771    fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {1772        write!(f, "ChargeTransactionPayment<{:?}>", self.0)1773    }1774    #[cfg(not(feature = "std"))]1775    fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {1776        Ok(())1777    }1778}17791780impl<T: Trait + transaction_payment::Trait + Send + Sync> ChargeTransactionPayment<T>1781where1782    T::Call:1783        Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>>,1784    BalanceOf<T>: Send + Sync + FixedPointOperand,1785{1786    /// utility constructor. Used only in client/factory code.1787    pub fn from(fee: BalanceOf<T>) -> Self {1788        Self(fee)1789    }17901791    pub fn traditional_fee(1792        len: usize,1793        info: &DispatchInfoOf<T::Call>,1794        tip: BalanceOf<T>,1795    ) -> BalanceOf<T>1796    where1797        T::Call: Dispatchable<Info = DispatchInfo>,1798    {1799        <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)1800    }18011802    fn withdraw_fee(1803        &self,1804        who: &T::AccountId,1805        call: &T::Call,1806        info: &DispatchInfoOf<T::Call>,1807        len: usize,1808    ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {1809        let tip = self.0;18101811        // Set fee based on call type. Creating collection costs 1 Unique.1812        // All other transactions have traditional fees so far1813        let fee = match call.is_sub_type() {1814            Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),1815            _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes1816                                                        // _ => <BalanceOf<T>>::from(100)1817        };18181819        // Determine who is paying transaction fee based on ecnomic model1820        // Parse call to extract collection ID and access collection sponsor1821        let sponsor: T::AccountId = match call.is_sub_type() {1822            Some(Call::create_item(collection_id, _properties, _owner)) => {1823                <Collection<T>>::get(collection_id).sponsor1824            }1825            Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {1826                let _collection_mode = <Collection<T>>::get(collection_id).mode;18271828                // sponsor timeout1829                let sponsor_transfer = match _collection_mode {1830                    CollectionMode::NFT(_) => {1831                        let basket = <NftTransferBasket<T>>::get(collection_id, _item_id);1832                        let block_number = <system::Module<T>>::block_number() as T::BlockNumber;1833                        let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into();1834                        if block_number >= limit_time {1835                            <NftTransferBasket<T>>::insert(collection_id, _item_id, block_number);1836                            true1837                        }1838                        else {1839                            false1840                        }1841                    }1842                    CollectionMode::Fungible(_) => {1843                        let mut basket = <FungibleTransferBasket<T>>::get(collection_id, _item_id);1844                        let block_number = <system::Module<T>>::block_number() as T::BlockNumber;1845                        if basket.iter().any(|i| i.address == _new_owner.clone())1846                        {1847                            let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone();1848                            let limit_time = item.start_block + ChainLimit::get().fungible_sponsor_transfer_timeout.into();1849                            if block_number >= limit_time {1850                                basket.retain(|x| x.address == item.address);1851                                basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() });1852                                <FungibleTransferBasket<T>>::insert(collection_id, _item_id, basket);1853                                true1854                            }1855                            else {1856                                false1857                            }1858                        }1859                        else {1860                            basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()});1861                            true1862                        }1863                    }1864                    CollectionMode::ReFungible(_, _) => {1865                        let basket = <ReFungibleTransferBasket<T>>::get(collection_id, _item_id);1866                        let block_number = <system::Module<T>>::block_number() as T::BlockNumber;1867                        let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into();1868                        if block_number >= limit_time {1869                            <ReFungibleTransferBasket<T>>::insert(collection_id, _item_id, block_number);1870                            true1871                        } else {1872                            false1873                        }1874                    }1875                    _ => {1876                        false1877                    },1878                };18791880                if !sponsor_transfer {1881                    T::AccountId::default()1882                } else {1883                    <Collection<T>>::get(collection_id).sponsor1884                }1885            }18861887            _ => T::AccountId::default(),1888        };18891890        let mut who_pays_fee: T::AccountId = sponsor.clone();1891        if sponsor == T::AccountId::default() {1892            who_pays_fee = who.clone();1893        }18941895        // Only mess with balances if fee is not zero.1896        if fee.is_zero() {1897            return Ok((fee, None));1898        }18991900        match <T as transaction_payment::Trait>::Currency::withdraw(1901            &who_pays_fee,1902            fee,1903            if tip.is_zero() {1904                WithdrawReason::TransactionPayment.into()1905            } else {1906                WithdrawReason::TransactionPayment | WithdrawReason::Tip1907            },1908            ExistenceRequirement::KeepAlive,1909        ) {1910            Ok(imbalance) => Ok((fee, Some(imbalance))),1911            Err(_) => Err(InvalidTransaction::Payment.into()),1912        }1913    }1914}19151916impl<T: Trait + transaction_payment::Trait + Send + Sync> SignedExtension1917    for ChargeTransactionPayment<T>1918where1919    BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,1920    T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>>,1921{1922    const IDENTIFIER: &'static str = "ChargeTransactionPayment";1923    type AccountId = T::AccountId;1924    type Call = T::Call;1925    type AdditionalSigned = ();1926    type Pre = (1927        BalanceOf<T>,1928        Self::AccountId,1929        Option<NegativeImbalanceOf<T>>,1930        BalanceOf<T>,1931    );1932    fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {1933        Ok(())1934    }19351936    fn validate(1937        &self,1938        who: &Self::AccountId,1939        call: &Self::Call,1940        info: &DispatchInfoOf<Self::Call>,1941        len: usize,1942    ) -> TransactionValidity {1943        let (fee, _) = self.withdraw_fee(who, call, info, len)?;19441945        let mut r = ValidTransaction::default();1946        // NOTE: we probably want to maximize the _fee (of any type) per weight unit_ here, which1947        // will be a bit more than setting the priority to tip. For now, this is enough.1948        r.priority = fee.saturated_into::<TransactionPriority>();1949        Ok(r)1950    }19511952    fn pre_dispatch(1953        self,1954        who: &Self::AccountId,1955        call: &Self::Call,1956        info: &DispatchInfoOf<Self::Call>,1957        len: usize,1958    ) -> Result<Self::Pre, TransactionValidityError> {1959        let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;1960        Ok((self.0, who.clone(), imbalance, fee))1961    }19621963    fn post_dispatch(1964        pre: Self::Pre,1965        info: &DispatchInfoOf<Self::Call>,1966        post_info: &PostDispatchInfoOf<Self::Call>,1967        len: usize,1968        _result: &DispatchResult,1969    ) -> Result<(), TransactionValidityError> {1970        let (tip, who, imbalance, fee) = pre;1971        if let Some(payed) = imbalance {1972            let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(1973                len as u32, info, post_info, tip,1974            );1975            let refund = fee.saturating_sub(actual_fee);1976            let actual_payment =1977                match <T as transaction_payment::Trait>::Currency::deposit_into_existing(1978                    &who, refund,1979                ) {1980                    Ok(refund_imbalance) => {1981                        // The refund cannot be larger than the up front payed max weight.1982                        // `PostDispatchInfo::calc_unspent` guards against such a case.1983                        match payed.offset(refund_imbalance) {1984                            Ok(actual_payment) => actual_payment,1985                            Err(_) => return Err(InvalidTransaction::Payment.into()),1986                        }1987                    }1988                    // We do not recreate the account using the refund. The up front payment1989                    // is gone in that case.1990                    Err(_) => payed,1991                };1992            let imbalances = actual_payment.split(tip);1993            <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(1994                Some(imbalances.0).into_iter().chain(Some(imbalances.1)),1995            );1996        }1997        Ok(())1998    }1999}2000// #endregion20012002