git.delta.rocks / unique-network / refs/commits / 99e7555e5ee6

difftreelog

Merge pull request #104 from usetech-llc/feature/NFTPAR-301_transfer_event

Greg Zaitsev2021-02-18parents: #dcb255f #04b203b.patch.diff
in: master
Add Transfer event

2 files changed

modifiedpallets/nft/src/lib.rsdiffbeforeafterboth
before · pallets/nft/src/lib.rs
1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]78#![cfg_attr(not(feature = "std"), no_std)]910#[cfg(feature = "std")]11pub use std::*;1213#[cfg(feature = "std")]14pub use serde::*;1516use codec::{Decode, Encode};17pub use frame_support::{18    construct_runtime, decl_event, decl_module, decl_storage, decl_error,19    dispatch::DispatchResult,20    ensure, fail, parameter_types,21    traits::{22        Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,23        Randomness, IsSubType,24    },25    weights::{26        constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},27        DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,28        WeightToFeePolynomial, DispatchClass,29    },30    StorageValue,31};3233use frame_system::{self as system, ensure_signed, ensure_root};34use sp_runtime::sp_std::prelude::Vec;35use sp_runtime::{36    traits::{37        DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SaturatedConversion, SignedExtension, Zero,38    },39    transaction_validity::{40        TransactionPriority, InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,41    },42    FixedPointOperand, FixedU128,43};44use sp_runtime::traits::StaticLookup;45use pallet_contracts::chain_extension::UncheckedFrom;46use transaction_payment::OnChargeTransaction;4748#[cfg(test)]49mod mock;5051#[cfg(test)]52mod tests;5354mod default_weights;5556pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;57pub const MAX_REFUNGIBLE_PIECES: u128 = 1_000_000_000_000_000_000_000;58pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;59pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;6061// Structs62// #region6364pub type CollectionId = u32;65pub type TokenId = u32;66pub type DecimalPoints = u8;6768#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]69#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]70pub enum CollectionMode {71    Invalid,72    NFT,73    // decimal points74    Fungible(DecimalPoints),75    ReFungible,76}7778impl Default for CollectionMode {79    fn default() -> Self {80        Self::Invalid81    }82}8384impl Into<u8> for CollectionMode {85    fn into(self) -> u8 {86        match self {87            CollectionMode::Invalid => 0,88            CollectionMode::NFT => 1,89            CollectionMode::Fungible(_) => 2,90            CollectionMode::ReFungible => 3,91        }92    }93}9495#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]96#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]97pub enum AccessMode {98    Normal,99    WhiteList,100}101impl Default for AccessMode {102    fn default() -> Self {103        Self::Normal104    }105}106107#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]108#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]109pub enum SchemaVersion {110    ImageURL,111    Unique,112}113impl Default for SchemaVersion {114    fn default() -> Self {115        Self::ImageURL116    }117}118119#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]120#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]121pub struct Ownership<AccountId> {122    pub owner: AccountId,123    pub fraction: u128,124}125126#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]127#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]128pub struct CollectionType<AccountId> {129    pub owner: AccountId,130    pub mode: CollectionMode,131    pub access: AccessMode,132    pub decimal_points: DecimalPoints,133    pub name: Vec<u16>,        // 64 include null escape char134    pub description: Vec<u16>, // 256 include null escape char135    pub token_prefix: Vec<u8>, // 16 include null escape char136    pub mint_mode: bool,137    pub offchain_schema: Vec<u8>,138    pub schema_version: SchemaVersion,139    pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender140    pub sponsor_confirmed: bool, // False if sponsor address has not yet confirmed sponsorship. True otherwise.141    pub limits: CollectionLimits, // Collection private restrictions 142    pub variable_on_chain_schema: Vec<u8>, //143    pub const_on_chain_schema: Vec<u8>, //144}145146#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]147#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]148pub struct NftItemType<AccountId> {149    pub owner: AccountId,150    pub const_data: Vec<u8>,151    pub variable_data: Vec<u8>,152}153154#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]155#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]156pub struct FungibleItemType {157    pub value: u128,158}159160#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]161#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]162pub struct ReFungibleItemType<AccountId> {163    pub owner: Vec<Ownership<AccountId>>,164    pub const_data: Vec<u8>,165    pub variable_data: Vec<u8>,166}167168// #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]169// #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]170// pub struct VestingItem<AccountId, Moment> {171//     pub sender: AccountId,172//     pub recipient: AccountId,173//     pub collection_id: CollectionId,174//     pub item_id: TokenId,175//     pub amount: u64,176//     pub vesting_date: Moment,177// }178179#[derive(Encode, Decode, Debug, Clone, PartialEq)]180#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]181pub struct CollectionLimits {182    pub account_token_ownership_limit: u32,183    pub sponsored_data_size: u32,184    pub token_limit: u32,185186    // Timeouts for item types in passed blocks187    pub sponsor_transfer_timeout: u32,188    pub owner_can_transfer: bool,189    pub owner_can_destroy: bool,190}191192impl Default for CollectionLimits {193    fn default() -> CollectionLimits {194        CollectionLimits { 195            account_token_ownership_limit: 10_000_000, 196            token_limit: u32::max_value(),197            sponsored_data_size: u32::max_value(), 198            sponsor_transfer_timeout: 14400,199            owner_can_transfer: true,200            owner_can_destroy: true201        }202    }203}204205#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]206#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]207pub struct ChainLimits {208    pub collection_numbers_limit: u32,209    pub account_token_ownership_limit: u32,210    pub collections_admins_limit: u64,211    pub custom_data_limit: u32,212213    // Timeouts for item types in passed blocks214    pub nft_sponsor_transfer_timeout: u32,215    pub fungible_sponsor_transfer_timeout: u32,216    pub refungible_sponsor_transfer_timeout: u32,217218    // Schema limits219    pub offchain_schema_limit: u32,220    pub variable_on_chain_schema_limit: u32,221    pub const_on_chain_schema_limit: u32,222}223224pub trait WeightInfo {225	fn create_collection() -> Weight;226	fn destroy_collection() -> Weight;227	fn add_to_white_list() -> Weight;228	fn remove_from_white_list() -> Weight;229    fn set_public_access_mode() -> Weight;230    fn set_mint_permission() -> Weight;231    fn change_collection_owner() -> Weight;232    fn add_collection_admin() -> Weight;233    fn remove_collection_admin() -> Weight;234    fn set_collection_sponsor() -> Weight;235    fn confirm_sponsorship() -> Weight;236    fn remove_collection_sponsor() -> Weight;237    fn create_item(s: usize) -> Weight;238    fn burn_item() -> Weight;239    fn transfer() -> Weight;240    fn approve() -> Weight;241    fn transfer_from() -> Weight;242    fn set_offchain_schema() -> Weight;243    fn set_const_on_chain_schema() -> Weight;244    fn set_variable_on_chain_schema() -> Weight;245    fn set_variable_meta_data() -> Weight;246    fn enable_contract_sponsoring() -> Weight;247    fn set_schema_version() -> Weight;248    fn set_chain_limits() -> Weight;249    fn set_contract_sponsoring_rate_limit() -> Weight;250    fn toggle_contract_white_list() -> Weight;251    fn add_to_contract_white_list() -> Weight;252    fn remove_from_contract_white_list() -> Weight;253    fn set_collection_limits() -> Weight;254}255256#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]257#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]258pub struct CreateNftData {259    pub const_data: Vec<u8>,260    pub variable_data: Vec<u8>,261}262263#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]264#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]265pub struct CreateFungibleData {266    pub value: u128,267}268269#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]270#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]271pub struct CreateReFungibleData {272    pub const_data: Vec<u8>,273    pub variable_data: Vec<u8>,274    pub pieces: u128,275}276277#[derive(Encode, Decode, Debug, Clone, PartialEq)]278#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]279pub enum CreateItemData {280    NFT(CreateNftData),281    Fungible(CreateFungibleData),282    ReFungible(CreateReFungibleData),283}284285impl CreateItemData {286    pub fn len(&self) -> usize {287        let len = match self {288            CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),289            CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),290            _ => 0291        };292        293        return len;294    }295}296297impl From<CreateNftData> for CreateItemData {298    fn from(item: CreateNftData) -> Self {299        CreateItemData::NFT(item)300    }301}302303impl From<CreateReFungibleData> for CreateItemData {304    fn from(item: CreateReFungibleData) -> Self {305        CreateItemData::ReFungible(item)306    }307}308309impl From<CreateFungibleData> for CreateItemData {310    fn from(item: CreateFungibleData) -> Self {311        CreateItemData::Fungible(item)312    }313}314315316decl_error! {317	/// Error for non-fungible-token module.318	pub enum Error for Module<T: Config> {319        /// Total collections bound exceeded.320        TotalCollectionsLimitExceeded,321		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.322        CollectionDecimalPointLimitExceeded, 323        /// Collection name can not be longer than 63 char.324        CollectionNameLimitExceeded, 325        /// Collection description can not be longer than 255 char.326        CollectionDescriptionLimitExceeded, 327        /// Token prefix can not be longer than 15 char.328        CollectionTokenPrefixLimitExceeded,329        /// This collection does not exist.330        CollectionNotFound,331        /// Item not exists.332        TokenNotFound,333        /// Admin not found334        AdminNotFound,335        /// Arithmetic calculation overflow.336        NumOverflow,       337        /// Account already has admin role.338        AlreadyAdmin,  339        /// You do not own this collection.340        NoPermission,341        /// This address is not set as sponsor, use setCollectionSponsor first.342        ConfirmUnsetSponsorFail,343        /// Collection is not in mint mode.344        PublicMintingNotAllowed,345        /// Sender parameter and item owner must be equal.346        MustBeTokenOwner,347        /// Item balance not enough.348        TokenValueTooLow,349        /// Size of item is too large.350        NftSizeLimitExceeded,351        /// No approve found352        ApproveNotFound,353        /// Requested value more than approved.354        TokenValueNotEnough,355        /// Only approved addresses can call this method.356        ApproveRequired,357        /// Address is not in white list.358        AddresNotInWhiteList,359        /// Number of collection admins bound exceeded.360        CollectionAdminsLimitExceeded,361        /// Owned tokens by a single address bound exceeded.362        AddressOwnershipLimitExceeded,363        /// Length of items properties must be greater than 0.364        EmptyArgument,365        /// const_data exceeded data limit.366        TokenConstDataLimitExceeded,367        /// variable_data exceeded data limit.368        TokenVariableDataLimitExceeded,369        /// Not NFT item data used to mint in NFT collection.370        NotNftDataUsedToMintNftCollectionToken,371        /// Not Fungible item data used to mint in Fungible collection.372        NotFungibleDataUsedToMintFungibleCollectionToken,373        /// Not Re Fungible item data used to mint in Re Fungible collection.374        NotReFungibleDataUsedToMintReFungibleCollectionToken,375        /// Unexpected collection type.376        UnexpectedCollectionType,377        /// Can't store metadata in fungible tokens.378        CantStoreMetadataInFungibleTokens,379        /// Collection token limit exceeded380        CollectionTokenLimitExceeded,381        /// Account token limit exceeded per collection382        AccountTokenLimitExceeded,383        /// Collection limit bounds per collection exceeded384        CollectionLimitBoundsExceeded,385        /// Schema data size limit bound exceeded386        SchemaDataLimitExceeded,387        /// Maximum refungibility exceeded388        WrongRefungiblePieces389	}390}391392pub trait Config: system::Config + Sized + transaction_payment::Config + pallet_contracts::Config {393    type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;394395    /// Weight information for extrinsics in this pallet.396	type WeightInfo: WeightInfo;397}398399#[cfg(feature = "runtime-benchmarks")]400mod benchmarking;401402// #endregion403404decl_storage! {405    trait Store for Module<T: Config> as Nft {406407        // Private members408        NextCollectionID: CollectionId;409        CreatedCollectionCount: u32;410        ChainVersion: u64;411        ItemListIndex: map hasher(identity) CollectionId => TokenId;412413        // Chain limits struct414        pub ChainLimit get(fn chain_limit) config(): ChainLimits;415416        // Bound counters417        CollectionCount: u32;418        pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;419420        // Basic collections421        pub Collection get(fn collection) config(): map hasher(identity) CollectionId => CollectionType<T::AccountId>;422        pub AdminList get(fn admin_list_collection): map hasher(identity) CollectionId => Vec<T::AccountId>;423        pub WhiteList get(fn white_list): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => bool;424425        /// Balance owner per collection map426        pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;427428        /// second parameter: item id + owner account id + spender account id429        pub Allowances get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId, T::AccountId) => u128;430431        /// Item collections432        pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;433        pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => FungibleItemType;434        pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;435436        /// Index list437        pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;438439        /// Tokens transfer baskets440        pub CreateItemBasket get(fn create_item_basket): map hasher(twox_64_concat) (CollectionId, T::AccountId) => T::BlockNumber;441        pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;442        pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;443        pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;444445        // Contract Sponsorship and Ownership446        pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId;447        pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;448        pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;449        pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;450        pub ContractWhiteListEnabled get(fn contract_white_list_enabled): map hasher(twox_64_concat) T::AccountId => bool; 451        pub ContractWhiteList get(fn contract_white_list): double_map hasher(twox_64_concat) T::AccountId, hasher(twox_64_concat) T::AccountId => bool; 452    }453    add_extra_genesis {454        build(|config: &GenesisConfig<T>| {455            // Modification of storage456            for (_num, _c) in &config.collection {457                <Module<T>>::init_collection(_c);458            }459460            for (_num, _c, _i) in &config.nft_item_id {461                <Module<T>>::init_nft_token(*_c, _i);462            }463464            for (collection_id, account_id, fungible_item) in &config.fungible_item_id {465                <Module<T>>::init_fungible_token(*collection_id, account_id, fungible_item);466            }467468            for (_num, _c, _i) in &config.refungible_item_id {469                <Module<T>>::init_refungible_token(*_c, _i);470            }471        })472    }473}474475decl_event!(476    pub enum Event<T>477    where478        AccountId = <T as system::Config>::AccountId,479    {480        /// New collection was created481        /// 482        /// # Arguments483        /// 484        /// * collection_id: Globally unique identifier of newly created collection.485        /// 486        /// * mode: [CollectionMode] converted into u8.487        /// 488        /// * account_id: Collection owner.489        Created(CollectionId, u8, AccountId),490491        /// New item was created.492        /// 493        /// # Arguments494        /// 495        /// * collection_id: Id of the collection where item was created.496        /// 497        /// * item_id: Id of an item. Unique within the collection.498        ///499        /// * recipient: Owner of newly created item 500        ItemCreated(CollectionId, TokenId, AccountId),501502        /// Collection item was burned.503        /// 504        /// # Arguments505        /// 506        /// collection_id.507        /// 508        /// item_id: Identifier of burned NFT.509        ItemDestroyed(CollectionId, TokenId),510    }511);512513decl_module! {514    pub struct Module<T: Config> for enum Call 515    where 516        origin: T::Origin517    {518        fn deposit_event() = default;519        type Error = Error<T>;520521        fn on_initialize(now: T::BlockNumber) -> Weight {522523            if ChainVersion::get() < 2524            {525                let value = NextCollectionID::get();526                CreatedCollectionCount::put(value);527                ChainVersion::put(2);528            }529530            0531        }532533        /// 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.534        /// 535        /// # Permissions536        /// 537        /// * Anyone.538        /// 539        /// # Arguments540        /// 541        /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.542        /// 543        /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.544        /// 545        /// * token_prefix: UTF-8 string with token prefix.546        /// 547        /// * mode: [CollectionMode] collection type and type dependent data.548        // returns collection ID549        #[weight = <T as Config>::WeightInfo::create_collection()]550        pub fn create_collection(origin,551                                 collection_name: Vec<u16>,552                                 collection_description: Vec<u16>,553                                 token_prefix: Vec<u8>,554                                 mode: CollectionMode) -> DispatchResult {555556            // Anyone can create a collection557            let who = ensure_signed(origin)?;558559            let decimal_points = match mode {560                CollectionMode::Fungible(points) => points,561                _ => 0562            };563564            // bound Total number of collections565            ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);566567            // check params568            ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);569            ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);570            ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);571            ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);572573            // Generate next collection ID574            let next_id = CreatedCollectionCount::get()575                .checked_add(1)576                .ok_or(Error::<T>::NumOverflow)?;577578            // bound counter579            let total = CollectionCount::get()580                .checked_add(1)581                .ok_or(Error::<T>::NumOverflow)?;582583            CreatedCollectionCount::put(next_id);584            CollectionCount::put(total);585586            // Create new collection587            let new_collection = CollectionType {588                owner: who.clone(),589                name: collection_name,590                mode: mode.clone(),591                mint_mode: false,592                access: AccessMode::Normal,593                description: collection_description,594                decimal_points: decimal_points,595                token_prefix: token_prefix,596                offchain_schema: Vec::new(),597                schema_version: SchemaVersion::ImageURL,598                sponsor: T::AccountId::default(),599                sponsor_confirmed: false,600                variable_on_chain_schema: Vec::new(),601                const_on_chain_schema: Vec::new(),602                limits: CollectionLimits::default()603            };604605            // Add new collection to map606            <Collection<T>>::insert(next_id, new_collection);607608            // call event609            Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));610611            Ok(())612        }613614        /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.615        /// 616        /// # Permissions617        /// 618        /// * Collection Owner.619        /// 620        /// # Arguments621        /// 622        /// * collection_id: collection to destroy.623        #[weight = <T as Config>::WeightInfo::destroy_collection()]624        pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {625626            let sender = ensure_signed(origin)?;627            Self::check_owner_permissions(collection_id, sender)?;628629            <AddressTokens<T>>::remove_prefix(collection_id);630            <Allowances<T>>::remove_prefix(collection_id);631            <Balance<T>>::remove_prefix(collection_id);632            <ItemListIndex>::remove(collection_id);633            <AdminList<T>>::remove(collection_id);634            <Collection<T>>::remove(collection_id);635            <WhiteList<T>>::remove_prefix(collection_id);636637            <NftItemList<T>>::remove_prefix(collection_id);638            <FungibleItemList<T>>::remove_prefix(collection_id);639            <ReFungibleItemList<T>>::remove_prefix(collection_id);640641            <NftTransferBasket<T>>::remove_prefix(collection_id);642            <FungibleTransferBasket<T>>::remove_prefix(collection_id);643            <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);644645            if CollectionCount::get() > 0646            {647                // bound couter648                let total = CollectionCount::get()649                    .checked_sub(1)650                    .ok_or(Error::<T>::NumOverflow)?;651652                CollectionCount::put(total);653            }654655            Ok(())656        }657658        /// Add an address to white list.659        /// 660        /// # Permissions661        /// 662        /// * Collection Owner663        /// * Collection Admin664        /// 665        /// # Arguments666        /// 667        /// * collection_id.668        /// 669        /// * address.670        #[weight = <T as Config>::WeightInfo::add_to_white_list()]671        pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{672673            let sender = ensure_signed(origin)?;674            Self::check_owner_or_admin_permissions(collection_id, sender)?;675676            <WhiteList<T>>::insert(collection_id, address, true);677            678            Ok(())679        }680681        /// Remove an address from white list.682        /// 683        /// # Permissions684        /// 685        /// * Collection Owner686        /// * Collection Admin687        /// 688        /// # Arguments689        /// 690        /// * collection_id.691        /// 692        /// * address.693        #[weight = <T as Config>::WeightInfo::remove_from_white_list()]694        pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{695696            let sender = ensure_signed(origin)?;697            Self::check_owner_or_admin_permissions(collection_id, sender)?;698699            <WhiteList<T>>::remove(collection_id, address);700701            Ok(())702        }703704        /// Toggle between normal and white list access for the methods with access for `Anyone`.705        /// 706        /// # Permissions707        /// 708        /// * Collection Owner.709        /// 710        /// # Arguments711        /// 712        /// * collection_id.713        /// 714        /// * mode: [AccessMode]715        #[weight = <T as Config>::WeightInfo::set_public_access_mode()]716        pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult717        {718            let sender = ensure_signed(origin)?;719720            Self::check_owner_permissions(collection_id, sender)?;721            let mut target_collection = <Collection<T>>::get(collection_id);722            target_collection.access = mode;723            <Collection<T>>::insert(collection_id, target_collection);724725            Ok(())726        }727728        /// Allows Anyone to create tokens if:729        /// * White List is enabled, and730        /// * Address is added to white list, and731        /// * This method was called with True parameter732        /// 733        /// # Permissions734        /// * Collection Owner735        ///736        /// # Arguments737        /// 738        /// * collection_id.739        /// 740        /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.741        #[weight = <T as Config>::WeightInfo::set_mint_permission()]742        pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult743        {744            let sender = ensure_signed(origin)?;745746            Self::check_owner_permissions(collection_id, sender)?;747            let mut target_collection = <Collection<T>>::get(collection_id);748            target_collection.mint_mode = mint_permission;749            <Collection<T>>::insert(collection_id, target_collection);750751            Ok(())752        }753754        /// Change the owner of the collection.755        /// 756        /// # Permissions757        /// 758        /// * Collection Owner.759        /// 760        /// # Arguments761        /// 762        /// * collection_id.763        /// 764        /// * new_owner.765        #[weight = <T as Config>::WeightInfo::change_collection_owner()]766        pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {767768            let sender = ensure_signed(origin)?;769            Self::check_owner_permissions(collection_id, sender)?;770            let mut target_collection = <Collection<T>>::get(collection_id);771            target_collection.owner = new_owner;772            <Collection<T>>::insert(collection_id, target_collection);773774            Ok(())775        }776777        /// Adds an admin of the Collection.778        /// 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. 779        /// 780        /// # Permissions781        /// 782        /// * Collection Owner.783        /// * Collection Admin.784        /// 785        /// # Arguments786        /// 787        /// * collection_id: ID of the Collection to add admin for.788        /// 789        /// * new_admin_id: Address of new admin to add.790        #[weight = <T as Config>::WeightInfo::add_collection_admin()]791        pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {792793            let sender = ensure_signed(origin)?;794            Self::check_owner_or_admin_permissions(collection_id, sender)?;795            let mut admin_arr: Vec<T::AccountId> = Vec::new();796797            if <AdminList<T>>::contains_key(collection_id)798            {799                admin_arr = <AdminList<T>>::get(collection_id);800                ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);801            }802803            // Number of collection admins804            ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);805806            admin_arr.push(new_admin_id);807            <AdminList<T>>::insert(collection_id, admin_arr);808809            Ok(())810        }811812        /// 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.813        ///814        /// # Permissions815        /// 816        /// * Collection Owner.817        /// * Collection Admin.818        /// 819        /// # Arguments820        /// 821        /// * collection_id: ID of the Collection to remove admin for.822        /// 823        /// * account_id: Address of admin to remove.824        #[weight = <T as Config>::WeightInfo::remove_collection_admin()]825        pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {826827            let sender = ensure_signed(origin)?;828            Self::check_owner_or_admin_permissions(collection_id, sender)?;829            ensure!(<AdminList<T>>::contains_key(collection_id), Error::<T>::AdminNotFound);830831            let mut admin_arr = <AdminList<T>>::get(collection_id);832            admin_arr.retain(|i| *i != account_id);833            <AdminList<T>>::insert(collection_id, admin_arr);834835            Ok(())836        }837838        /// # Permissions839        /// 840        /// * Collection Owner841        /// 842        /// # Arguments843        /// 844        /// * collection_id.845        /// 846        /// * new_sponsor.847        #[weight = <T as Config>::WeightInfo::set_collection_sponsor()]848        pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {849850            let sender = ensure_signed(origin)?;851            ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);852853            let mut target_collection = <Collection<T>>::get(collection_id);854            ensure!(sender == target_collection.owner, Error::<T>::NoPermission);855856            target_collection.sponsor = new_sponsor;857            target_collection.sponsor_confirmed = false;858            <Collection<T>>::insert(collection_id, target_collection);859860            Ok(())861        }862863        /// # Permissions864        /// 865        /// * Sponsor.866        /// 867        /// # Arguments868        /// 869        /// * collection_id.870        #[weight = <T as Config>::WeightInfo::confirm_sponsorship()]871        pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {872873            let sender = ensure_signed(origin)?;874            ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);875876            let mut target_collection = <Collection<T>>::get(collection_id);877            ensure!(sender == target_collection.sponsor, Error::<T>::ConfirmUnsetSponsorFail);878879            target_collection.sponsor_confirmed = true;880            <Collection<T>>::insert(collection_id, target_collection);881882            Ok(())883        }884885        /// Switch back to pay-per-own-transaction model.886        ///887        /// # Permissions888        ///889        /// * Collection owner.890        /// 891        /// # Arguments892        /// 893        /// * collection_id.894        #[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]895        pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {896897            let sender = ensure_signed(origin)?;898            ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);899900            let mut target_collection = <Collection<T>>::get(collection_id);901            ensure!(sender == target_collection.owner, Error::<T>::NoPermission);902903            target_collection.sponsor = T::AccountId::default();904            target_collection.sponsor_confirmed = false;905            <Collection<T>>::insert(collection_id, target_collection);906907            Ok(())908        }909910        /// This method creates a concrete instance of NFT Collection created with CreateCollection method.911        /// 912        /// # Permissions913        /// 914        /// * Collection Owner.915        /// * Collection Admin.916        /// * Anyone if917        ///     * White List is enabled, and918        ///     * Address is added to white list, and919        ///     * MintPermission is enabled (see SetMintPermission method)920        /// 921        /// # Arguments922        /// 923        /// * collection_id: ID of the collection.924        /// 925        /// * owner: Address, initial owner of the NFT.926        ///927        /// * data: Token data to store on chain.928        // #[weight =929        // (130_000_000 as Weight)930        // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))931        // .saturating_add(RocksDbWeight::get().reads(10 as Weight))932        // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]933934        #[weight = <T as Config>::WeightInfo::create_item(data.len())]935        pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {936937            let sender = ensure_signed(origin)?;938939            Self::collection_exists(collection_id)?;940941            let target_collection = <Collection<T>>::get(collection_id);942943            Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;944            Self::validate_create_item_args(&target_collection, &data)?;945            Self::create_item_no_validation(collection_id, owner, data)?;946947            Ok(())948        }949950        /// This method creates multiple instances of NFT Collection created with CreateCollection method.951        /// 952        /// # Permissions953        /// 954        /// * Collection Owner.955        /// * Collection Admin.956        /// * Anyone if957        ///     * White List is enabled, and958        ///     * Address is added to white list, and959        ///     * MintPermission is enabled (see SetMintPermission method)960        /// 961        /// # Arguments962        /// 963        /// * collection_id: ID of the collection.964        /// 965        /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].966        /// 967        /// * owner: Address, initial owner of the NFT.968        #[weight = <T as Config>::WeightInfo::create_item(items_data.into_iter()969                               .map(|data| { data.len() })970                               .sum())]971        pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {972973            ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);974            let sender = ensure_signed(origin)?;975976            Self::collection_exists(collection_id)?;977            let target_collection = <Collection<T>>::get(collection_id);978979            Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;980981            for data in &items_data {982                Self::validate_create_item_args(&target_collection, data)?;983            }984            for data in &items_data {985                Self::create_item_no_validation(collection_id, owner.clone(), data.clone())?;986            }987988            Ok(())989        }990991        /// Destroys a concrete instance of NFT.992        /// 993        /// # Permissions994        /// 995        /// * Collection Owner.996        /// * Collection Admin.997        /// * Current NFT Owner.998        /// 999        /// # Arguments1000        /// 1001        /// * collection_id: ID of the collection.1002        /// 1003        /// * item_id: ID of NFT to burn.1004        #[weight = <T as Config>::WeightInfo::burn_item()]1005        pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {10061007            let sender = ensure_signed(origin)?;1008            Self::collection_exists(collection_id)?;10091010            // Transfer permissions check1011            let target_collection = <Collection<T>>::get(collection_id);1012            ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1013                Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1014                Error::<T>::NoPermission);10151016            if target_collection.access == AccessMode::WhiteList {1017                Self::check_white_list(collection_id, &sender)?;1018            }10191020            match target_collection.mode1021            {1022                CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1023                CollectionMode::Fungible(_)  => Self::burn_fungible_item(&sender, collection_id, value)?,1024                CollectionMode::ReFungible  => Self::burn_refungible_item(collection_id, item_id, &sender)?,1025                _ => ()1026            };10271028            // call event1029            Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));10301031            Ok(())1032        }10331034        /// Change ownership of the token.1035        /// 1036        /// # Permissions1037        /// 1038        /// * Collection Owner1039        /// * Collection Admin1040        /// * Current NFT owner1041        ///1042        /// # Arguments1043        /// 1044        /// * recipient: Address of token recipient.1045        /// 1046        /// * collection_id.1047        /// 1048        /// * item_id: ID of the item1049        ///     * Non-Fungible Mode: Required.1050        ///     * Fungible Mode: Ignored.1051        ///     * Re-Fungible Mode: Required.1052        /// 1053        /// * value: Amount to transfer.1054        ///     * Non-Fungible Mode: Ignored1055        ///     * Fungible Mode: Must specify transferred amount1056        ///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1057        #[weight = <T as Config>::WeightInfo::transfer()]1058        pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {1059            let sender = ensure_signed(origin)?;1060            Self::transfer_internal(sender, recipient, collection_id, item_id, value)1061        }10621063        /// Set, change, or remove approved address to transfer the ownership of the NFT.1064        /// 1065        /// # Permissions1066        /// 1067        /// * Collection Owner1068        /// * Collection Admin1069        /// * Current NFT owner1070        /// 1071        /// # Arguments1072        /// 1073        /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1074        /// 1075        /// * collection_id.1076        /// 1077        /// * item_id: ID of the item.1078        #[weight = <T as Config>::WeightInfo::approve()]1079        pub fn approve(origin, spender: T::AccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {10801081            let sender = ensure_signed(origin)?;10821083            Self::collection_exists(collection_id)?;1084            Self::token_exists(collection_id, item_id, &sender)?;10851086            // Transfer permissions check1087            let target_collection = <Collection<T>>::get(collection_id);1088            ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1089                Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1090                Error::<T>::NoPermission);10911092            if target_collection.access == AccessMode::WhiteList {1093                Self::check_white_list(collection_id, &sender)?;1094                Self::check_white_list(collection_id, &spender)?;1095            }10961097            let allowance_exists = <Allowances<T>>::contains_key(collection_id, (item_id, &sender, &spender));1098            let mut allowance: u128 = amount;1099            if allowance_exists {1100                allowance += <Allowances<T>>::get(collection_id, (item_id, &sender, &spender));1101            }1102            <Allowances<T>>::insert(collection_id, (item_id, sender.clone(), spender.clone()), allowance);11031104            Ok(())1105        }1106        1107        /// 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.1108        /// 1109        /// # Permissions1110        /// * Collection Owner1111        /// * Collection Admin1112        /// * Current NFT owner1113        /// * Address approved by current NFT owner1114        /// 1115        /// # Arguments1116        /// 1117        /// * from: Address that owns token.1118        /// 1119        /// * recipient: Address of token recipient.1120        /// 1121        /// * collection_id.1122        /// 1123        /// * item_id: ID of the item.1124        /// 1125        /// * value: Amount to transfer.1126        #[weight = <T as Config>::WeightInfo::transfer_from()]1127        pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {11281129            let sender = ensure_signed(origin)?;1130            let mut appoved_transfer = false;11311132            // Check approval1133            let mut approval: u128 = 0;1134            if <Allowances<T>>::contains_key(collection_id, (item_id, &from, &sender)) {1135                approval = <Allowances<T>>::get(collection_id, (item_id, &from, &sender));1136                ensure!(approval >= value, Error::<T>::TokenValueNotEnough);1137                appoved_transfer = true;1138            }11391140            let target_collection = <Collection<T>>::get(collection_id);11411142            // Limits check1143            Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;11441145            // Transfer permissions check         1146            ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1147                Error::<T>::NoPermission);11481149            if target_collection.access == AccessMode::WhiteList {1150                Self::check_white_list(collection_id, &sender)?;1151                Self::check_white_list(collection_id, &recipient)?;1152            }11531154            // Reduce approval by transferred amount or remove if remaining approval drops to 01155            if approval.checked_sub(value).unwrap_or(0) > 0 {1156                <Allowances<T>>::insert(collection_id, (item_id, &from, &sender), approval - value);1157            }1158            else {1159                <Allowances<T>>::remove(collection_id, (item_id, &from, &sender));1160            }11611162            match target_collection.mode1163            {1164                CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1165                CollectionMode::Fungible(_)  => Self::transfer_fungible(collection_id, value, &from, &recipient)?,1166                CollectionMode::ReFungible  => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1167                _ => ()1168            };11691170            Ok(())1171        }11721173        // #[weight = 0]1174        // pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {11751176        //     // let no_perm_mes = "You do not have permissions to modify this collection";1177        //     // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1178        //     // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1179        //     // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);11801181        //     // // on_nft_received  call11821183        //     // Self::transfer(origin, collection_id, item_id, new_owner)?;11841185        //     Ok(())1186        // }11871188        /// Set off-chain data schema.1189        /// 1190        /// # Permissions1191        /// 1192        /// * Collection Owner1193        /// * Collection Admin1194        /// 1195        /// # Arguments1196        /// 1197        /// * collection_id.1198        /// 1199        /// * schema: String representing the offchain data schema.1200        #[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1201        pub fn set_variable_meta_data (1202            origin,1203            collection_id: CollectionId,1204            item_id: TokenId,1205            data: Vec<u8>1206        ) -> DispatchResult {1207            let sender = ensure_signed(origin)?;1208            1209            Self::collection_exists(collection_id)?;1210            Self::token_exists(collection_id, item_id, &sender)?;12111212            ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);12131214            // Modify permissions check1215            let target_collection = <Collection<T>>::get(collection_id);1216            ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1217                Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1218                Error::<T>::NoPermission);12191220            match target_collection.mode1221            {1222                CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1223                CollectionMode::ReFungible  => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1224                CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1225                _ => fail!(Error::<T>::UnexpectedCollectionType)1226            };12271228            Ok(())1229        }1230 1231        /// Set schema standard1232        /// ImageURL1233        /// Unique1234        /// 1235        /// # Permissions1236        /// 1237        /// * Collection Owner1238        /// * Collection Admin1239        /// 1240        /// # Arguments1241        /// 1242        /// * collection_id.1243        /// 1244        /// * schema: SchemaVersion: enum1245        #[weight = <T as Config>::WeightInfo::set_schema_version()]1246        pub fn set_schema_version(1247            origin,1248            collection_id: CollectionId,1249            version: SchemaVersion1250        ) -> DispatchResult {1251            let sender = ensure_signed(origin)?;1252            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;1253            let mut target_collection = <Collection<T>>::get(collection_id);1254            target_collection.schema_version = version;1255            <Collection<T>>::insert(collection_id, target_collection);12561257            Ok(())1258        }12591260        /// Set off-chain data schema.1261        /// 1262        /// # Permissions1263        /// 1264        /// * Collection Owner1265        /// * Collection Admin1266        /// 1267        /// # Arguments1268        /// 1269        /// * collection_id.1270        /// 1271        /// * schema: String representing the offchain data schema.1272        #[weight = <T as Config>::WeightInfo::set_offchain_schema()]1273        pub fn set_offchain_schema(1274            origin,1275            collection_id: CollectionId,1276            schema: Vec<u8>1277        ) -> DispatchResult {1278            let sender = ensure_signed(origin)?;1279            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12801281            // check schema limit1282            ensure!(schema.len() as u32 <= ChainLimit::get().offchain_schema_limit, "");12831284            let mut target_collection = <Collection<T>>::get(collection_id);1285            target_collection.offchain_schema = schema;1286            <Collection<T>>::insert(collection_id, target_collection);12871288            Ok(())1289        }12901291        /// Set const on-chain data schema.1292        /// 1293        /// # Permissions1294        /// 1295        /// * Collection Owner1296        /// * Collection Admin1297        /// 1298        /// # Arguments1299        /// 1300        /// * collection_id.1301        /// 1302        /// * schema: String representing the const on-chain data schema.1303        #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1304        pub fn set_const_on_chain_schema (1305            origin,1306            collection_id: CollectionId,1307            schema: Vec<u8>1308        ) -> DispatchResult {1309            let sender = ensure_signed(origin)?;1310            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13111312            // check schema limit1313            ensure!(schema.len() as u32 <= ChainLimit::get().const_on_chain_schema_limit, "");13141315            let mut target_collection = <Collection<T>>::get(collection_id);1316            target_collection.const_on_chain_schema = schema;1317            <Collection<T>>::insert(collection_id, target_collection);13181319            Ok(())1320        }13211322        /// Set variable on-chain data schema.1323        /// 1324        /// # Permissions1325        /// 1326        /// * Collection Owner1327        /// * Collection Admin1328        /// 1329        /// # Arguments1330        /// 1331        /// * collection_id.1332        /// 1333        /// * schema: String representing the variable on-chain data schema.1334        #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1335        pub fn set_variable_on_chain_schema (1336            origin,1337            collection_id: CollectionId,1338            schema: Vec<u8>1339        ) -> DispatchResult {1340            let sender = ensure_signed(origin)?;1341            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13421343            // check schema limit1344            ensure!(schema.len() as u32 <= ChainLimit::get().variable_on_chain_schema_limit, "");13451346            let mut target_collection = <Collection<T>>::get(collection_id);1347            target_collection.variable_on_chain_schema = schema;1348            <Collection<T>>::insert(collection_id, target_collection);13491350            Ok(())1351        }13521353        // Sudo permissions function1354        #[weight = <T as Config>::WeightInfo::set_chain_limits()]1355        pub fn set_chain_limits(1356            origin,1357            limits: ChainLimits1358        ) -> DispatchResult {13591360            #[cfg(not(feature = "runtime-benchmarks"))]1361            ensure_root(origin)?;13621363            <ChainLimit>::put(limits);1364            Ok(())1365        }13661367        /// Enable smart contract self-sponsoring.1368        /// 1369        /// # Permissions1370        /// 1371        /// * Contract Owner1372        /// 1373        /// # Arguments1374        /// 1375        /// * contract address1376        /// * enable flag1377        /// 1378        #[weight = <T as Config>::WeightInfo::enable_contract_sponsoring()]1379        pub fn enable_contract_sponsoring(1380            origin,1381            contract_address: T::AccountId,1382            enable: bool1383        ) -> DispatchResult {13841385            let sender = ensure_signed(origin)?;13861387            #[cfg(feature = "runtime-benchmarks")]1388            <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());13891390            Self::ensure_contract_owned(sender, &contract_address)?;13911392            <ContractSelfSponsoring<T>>::insert(contract_address, enable);1393            Ok(())1394        }13951396        /// Set the rate limit for contract sponsoring to specified number of blocks.1397        /// 1398        /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1399        /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1400        /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1401        /// from contract endowment if there are at least B blocks between such transactions. 1402        /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1403        /// 1404        /// # Permissions1405        /// 1406        /// * Contract Owner1407        /// 1408        /// # Arguments1409        /// 1410        /// -`contract_address`: Address of the contract to sponsor1411        /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1412        /// 1413        #[weight = <T as Config>::WeightInfo::set_contract_sponsoring_rate_limit()]1414        pub fn set_contract_sponsoring_rate_limit(1415            origin,1416            contract_address: T::AccountId,1417            rate_limit: T::BlockNumber1418        ) -> DispatchResult {1419            let sender = ensure_signed(origin)?;14201421            #[cfg(feature = "runtime-benchmarks")]1422            <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14231424            Self::ensure_contract_owned(sender, &contract_address)?;1425            <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1426            Ok(())1427        }14281429        /// Enable the white list for a contract. Only addresses added to the white list with addToContractWhiteList will be able to call this smart contract.1430        /// 1431        /// # Permissions1432        /// 1433        /// * Address that deployed smart contract.1434        /// 1435        /// # Arguments1436        /// 1437        /// -`contract_address`: Address of the contract.1438        /// 1439        /// - `enable`: .  1440        #[weight = <T as Config>::WeightInfo::toggle_contract_white_list()]1441        pub fn toggle_contract_white_list(1442            origin,1443            contract_address: T::AccountId,1444            enable: bool1445        ) -> DispatchResult {1446            let sender = ensure_signed(origin)?;14471448            #[cfg(feature = "runtime-benchmarks")]1449            <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14501451            Self::ensure_contract_owned(sender, &contract_address)?;1452            <ContractWhiteListEnabled<T>>::insert(contract_address, enable);1453            Ok(())1454        }1455        1456        /// Add an address to smart contract white list.1457        /// 1458        /// # Permissions1459        /// 1460        /// * Address that deployed smart contract.1461        /// 1462        /// # Arguments1463        /// 1464        /// -`contract_address`: Address of the contract.1465        ///1466        /// -`account_address`: Address to add.1467        #[weight = <T as Config>::WeightInfo::add_to_contract_white_list()]1468        pub fn add_to_contract_white_list(1469            origin,1470            contract_address: T::AccountId,1471            account_address: T::AccountId1472        ) -> DispatchResult {1473            let sender = ensure_signed(origin)?;14741475            #[cfg(feature = "runtime-benchmarks")]1476            <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());1477            1478            Self::ensure_contract_owned(sender, &contract_address)?;      1479            <ContractWhiteList<T>>::insert(contract_address, account_address, true);1480            Ok(())1481        }14821483        /// Remove an address from smart contract white list.1484        /// 1485        /// # Permissions1486        /// 1487        /// * Address that deployed smart contract.1488        /// 1489        /// # Arguments1490        /// 1491        /// -`contract_address`: Address of the contract.1492        ///1493        /// -`account_address`: Address to remove.1494        #[weight = <T as Config>::WeightInfo::remove_from_contract_white_list()]1495        pub fn remove_from_contract_white_list(1496            origin,1497            contract_address: T::AccountId,1498            account_address: T::AccountId1499        ) -> DispatchResult {1500            let sender = ensure_signed(origin)?;15011502            #[cfg(feature = "runtime-benchmarks")]1503            <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15041505            Self::ensure_contract_owned(sender, &contract_address)?;1506            <ContractWhiteList<T>>::remove(contract_address, account_address);1507            Ok(())1508        }15091510        #[weight = <T as Config>::WeightInfo::set_collection_limits()]1511        pub fn set_collection_limits(1512            origin,1513            collection_id: u32,1514            limits: CollectionLimits,1515        ) -> DispatchResult {1516            let sender = ensure_signed(origin)?;1517            Self::check_owner_permissions(collection_id, sender.clone())?;1518            let mut target_collection = <Collection<T>>::get(collection_id);1519            let chain_limits = ChainLimit::get();1520            let climits = target_collection.limits;15211522            // collection bounds1523            ensure!(limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1524                limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP,  1525                Error::<T>::CollectionLimitBoundsExceeded);15261527            // token_limit   check  prev1528            ensure!(climits.token_limit > limits.token_limit && 1529                limits.token_limit <= chain_limits.account_token_ownership_limit, 1530                Error::<T>::AccountTokenLimitExceeded);15311532            target_collection.limits = limits;1533            <Collection<T>>::insert(collection_id, target_collection);15341535            Ok(())1536        } 1537    }1538}15391540impl<T: Config> Module<T> {15411542    pub fn transfer_internal(sender: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {15431544        let target_collection = <Collection<T>>::get(collection_id);15451546        // Limits check1547        Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;15481549        // Transfer permissions check1550        ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1551            Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1552            Error::<T>::NoPermission);15531554        if target_collection.access == AccessMode::WhiteList {1555            Self::check_white_list(collection_id, &sender)?;1556            Self::check_white_list(collection_id, &recipient)?;1557        }15581559        match target_collection.mode1560        {1561            CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1562            CollectionMode::Fungible(_)  => Self::transfer_fungible(collection_id, value, &sender, &recipient)?,1563            CollectionMode::ReFungible  => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1564            _ => ()1565        };15661567        Ok(())1568    }156915701571    fn is_correct_transfer(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, recipient: &T::AccountId) -> DispatchResult {15721573        // check token limit and account token limit1574        let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1575        ensure!(collection.limits.account_token_ownership_limit > account_items,  Error::<T>::AccountTokenLimitExceeded);1576        1577        Ok(())1578    }15791580    fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {15811582        // check token limit and account token limit1583        let total_items: u32 = ItemListIndex::get(collection_id);1584        let account_items: u32 = <AddressTokens<T>>::get(collection_id, owner).len() as u32;1585        ensure!(collection.limits.token_limit > total_items,  Error::<T>::CollectionTokenLimitExceeded);1586        ensure!(collection.limits.account_token_ownership_limit > account_items,  Error::<T>::AccountTokenLimitExceeded);15871588        if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1589            ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1590            Self::check_white_list(collection_id, owner)?;1591            Self::check_white_list(collection_id, sender)?;1592        }15931594        Ok(())1595    }15961597    fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1598        match target_collection.mode1599        {1600            CollectionMode::NFT => {1601                if let CreateItemData::NFT(data) = data {1602                    // check sizes1603                    ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1604                    ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1605                } else {1606                    fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1607                }1608            },1609            CollectionMode::Fungible(_) => {1610                if let CreateItemData::Fungible(_) = data {1611                } else {1612                    fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1613                }1614            },1615            CollectionMode::ReFungible => {1616                if let CreateItemData::ReFungible(data) = data {16171618                    // check sizes1619                    ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1620                    ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);16211622                    // Check refungibility limits1623                    ensure!(data.pieces <= MAX_REFUNGIBLE_PIECES, Error::<T>::WrongRefungiblePieces);1624                    ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1625                } else {1626                    fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1627                }1628            },1629            _ => { fail!(Error::<T>::UnexpectedCollectionType); }1630        };16311632        Ok(())1633    }16341635    fn create_item_no_validation(collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1636        match data1637        {1638            CreateItemData::NFT(data) => {1639                let item = NftItemType {1640                    owner: owner.clone(),1641                    const_data: data.const_data,1642                    variable_data: data.variable_data1643                };16441645                Self::add_nft_item(collection_id, item)?;1646            },1647            CreateItemData::Fungible(data) => {1648                Self::add_fungible_item(collection_id, &owner, data.value)?;1649            },1650            CreateItemData::ReFungible(data) => {1651                let mut owner_list = Vec::new();1652                owner_list.push(Ownership {owner: owner.clone(), fraction: data.pieces});16531654                let item = ReFungibleItemType {1655                    owner: owner_list,1656                    const_data: data.const_data,1657                    variable_data: data.variable_data1658                };16591660                Self::add_refungible_item(collection_id, item)?;1661            }1662        };16631664        // call event1665        Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id), owner));16661667        Ok(())1668    }16691670    fn add_fungible_item(collection_id: CollectionId, owner: &T::AccountId, value: u128) -> DispatchResult {16711672        // Does new owner already have an account?1673        let mut balance: u128 = 0;1674        if <FungibleItemList<T>>::contains_key(collection_id, owner) {1675            balance = <FungibleItemList<T>>::get(collection_id, owner).value;1676        } 16771678        // Mint 1679        let item = FungibleItemType {1680            value: balance + value1681        };1682        <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), item);16831684        // Update balance1685        let new_balance = <Balance<T>>::get(collection_id, owner)1686            .checked_add(value)1687            .ok_or(Error::<T>::NumOverflow)?;1688        <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);16891690        Ok(())1691    }16921693    fn add_refungible_item(collection_id: CollectionId, item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1694        let current_index = <ItemListIndex>::get(collection_id)1695            .checked_add(1)1696            .ok_or(Error::<T>::NumOverflow)?;1697        let itemcopy = item.clone();16981699        let value = item.owner.first().unwrap().fraction;1700        let owner = item.owner.first().unwrap().owner.clone();17011702        Self::add_token_index(collection_id, current_index, &owner)?;17031704        <ItemListIndex>::insert(collection_id, current_index);1705        <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);17061707        // Update balance1708        let new_balance = <Balance<T>>::get(collection_id, &owner)1709            .checked_add(value)1710            .ok_or(Error::<T>::NumOverflow)?;1711        <Balance<T>>::insert(collection_id, owner.clone(), new_balance);17121713        Ok(())1714    }17151716    fn add_nft_item(collection_id: CollectionId, item: NftItemType<T::AccountId>) -> DispatchResult {1717        let current_index = <ItemListIndex>::get(collection_id)1718            .checked_add(1)1719            .ok_or(Error::<T>::NumOverflow)?;17201721        let item_owner = item.owner.clone();1722        Self::add_token_index(collection_id, current_index, &item.owner)?;17231724        <ItemListIndex>::insert(collection_id, current_index);1725        <NftItemList<T>>::insert(collection_id, current_index, item);17261727        // Update balance1728        let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1729            .checked_add(1)1730            .ok_or(Error::<T>::NumOverflow)?;1731        <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);17321733        Ok(())1734    }17351736    fn burn_refungible_item(1737        collection_id: CollectionId,1738        item_id: TokenId,1739        owner: &T::AccountId,1740    ) -> DispatchResult {1741        ensure!(1742            <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1743            Error::<T>::TokenNotFound1744        );1745        let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id);1746        let rft_balance = token1747            .owner1748            .iter()1749            .filter(|&i| i.owner == *owner)1750            .next()1751            .unwrap();1752        Self::remove_token_index(collection_id, item_id, owner)?;17531754        // update balance1755        let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.clone())1756            .checked_sub(rft_balance.fraction)1757            .ok_or(Error::<T>::NumOverflow)?;1758        <Balance<T>>::insert(collection_id, rft_balance.owner.clone(), new_balance);17591760        // Re-create owners list with sender removed1761        let index = token1762            .owner1763            .iter()1764            .position(|i| i.owner == *owner)1765            .unwrap();1766        token.owner.remove(index);1767        let owner_count = token.owner.len();17681769        // Burn the token completely if this was the last (only) owner1770        if owner_count == 0 {1771            <ReFungibleItemList<T>>::remove(collection_id, item_id);1772        }1773        else {1774            <ReFungibleItemList<T>>::insert(collection_id, item_id, token);1775        }17761777        Ok(())1778    }17791780    fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1781        ensure!(1782            <NftItemList<T>>::contains_key(collection_id, item_id),1783            Error::<T>::TokenNotFound1784        );1785        let item = <NftItemList<T>>::get(collection_id, item_id);1786        Self::remove_token_index(collection_id, item_id, &item.owner)?;17871788        // update balance1789        let new_balance = <Balance<T>>::get(collection_id, &item.owner)1790            .checked_sub(1)1791            .ok_or(Error::<T>::NumOverflow)?;1792        <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1793        <NftItemList<T>>::remove(collection_id, item_id);17941795        Ok(())1796    }17971798    fn burn_fungible_item(owner: &T::AccountId, collection_id: CollectionId, value: u128) -> DispatchResult {1799        ensure!(1800            <FungibleItemList<T>>::contains_key(collection_id, owner),1801            Error::<T>::TokenNotFound1802        );1803        let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1804        ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);18051806        // update balance1807        let new_balance = <Balance<T>>::get(collection_id, owner)1808            .checked_sub(value)1809            .ok_or(Error::<T>::NumOverflow)?;1810        <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);18111812        if balance.value - value > 0 {1813            balance.value -= value;1814            <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1815        }1816        else {1817            <FungibleItemList<T>>::remove(collection_id, owner);1818        }18191820        Ok(())1821    }18221823    fn collection_exists(collection_id: CollectionId) -> DispatchResult {1824        ensure!(1825            <Collection<T>>::contains_key(collection_id),1826            Error::<T>::CollectionNotFound1827        );1828        Ok(())1829    }18301831    fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult {1832        Self::collection_exists(collection_id)?;18331834        let target_collection = <Collection<T>>::get(collection_id);1835        ensure!(1836            subject == target_collection.owner,1837            Error::<T>::NoPermission1838        );18391840        Ok(())1841    }18421843    fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool {1844        let target_collection = <Collection<T>>::get(collection_id);1845        let mut result: bool = subject == target_collection.owner;1846        let exists = <AdminList<T>>::contains_key(collection_id);18471848        if !result & exists {1849            if <AdminList<T>>::get(collection_id).contains(&subject) {1850                result = true1851            }1852        }18531854        result1855    }18561857    fn check_owner_or_admin_permissions(1858        collection_id: CollectionId,1859        subject: T::AccountId,1860    ) -> DispatchResult {1861        Self::collection_exists(collection_id)?;1862        let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());18631864        ensure!(1865            result,1866            Error::<T>::NoPermission1867        );1868        Ok(())1869    }18701871    fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool {1872        let target_collection = <Collection<T>>::get(collection_id);18731874        match target_collection.mode {1875            CollectionMode::NFT => {1876                <NftItemList<T>>::get(collection_id, item_id).owner == subject1877            }1878            CollectionMode::Fungible(_) => {1879                <FungibleItemList<T>>::contains_key(collection_id, &subject)1880            }1881            CollectionMode::ReFungible => {1882                <ReFungibleItemList<T>>::get(collection_id, item_id)1883                    .owner1884                    .iter()1885                    .any(|i| i.owner == subject)1886            }1887            CollectionMode::Invalid => false,1888        }1889    }18901891    fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult {1892        let mes = Error::<T>::AddresNotInWhiteList;1893        ensure!(<WhiteList<T>>::contains_key(collection_id, address), mes);18941895        Ok(())1896    }18971898    /// Check if token exists. In case of Fungible, check if there is an entry for 1899    /// the owner in fungible balances double map1900    fn token_exists(1901        collection_id: CollectionId,1902        item_id: TokenId,1903        owner: &T::AccountId1904    ) -> DispatchResult {1905        let target_collection = <Collection<T>>::get(collection_id);1906        let exists = match target_collection.mode1907        {1908            CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),1909            CollectionMode::Fungible(_)  => <FungibleItemList<T>>::contains_key(collection_id, owner),1910            CollectionMode::ReFungible  => <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1911            _ => false1912        };19131914        ensure!(exists == true, Error::<T>::TokenNotFound);1915        Ok(())1916    }19171918    fn transfer_fungible(1919        collection_id: CollectionId,1920        value: u128,1921        owner: &T::AccountId,1922        recipient: &T::AccountId,1923    ) -> DispatchResult {1924        Self::token_exists(collection_id, 0, owner)?;19251926        let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1927        ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);19281929        // Send balance to recipient (updates balanceOf of recipient)1930        Self::add_fungible_item(collection_id, recipient, value)?;19311932        // update balanceOf of sender1933        <Balance<T>>::insert(collection_id, (*owner).clone(), balance.value - value);19341935        // Reduce or remove sender1936        if balance.value == value {1937            <FungibleItemList<T>>::remove(collection_id, owner);1938        }1939        else {1940            balance.value -= value;1941            <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1942        }19431944        Ok(())1945    }19461947    fn transfer_refungible(1948        collection_id: CollectionId,1949        item_id: TokenId,1950        value: u128,1951        owner: T::AccountId,1952        new_owner: T::AccountId,1953    ) -> DispatchResult {1954        Self::token_exists(collection_id, item_id, &owner)?;19551956        let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1957        let item = full_item1958            .owner1959            .iter()1960            .filter(|i| i.owner == owner)1961            .next()1962            .ok_or(Error::<T>::NumOverflow)?;1963        let amount = item.fraction;19641965        ensure!(amount >= value, Error::<T>::TokenValueTooLow);19661967        // update balance1968        let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1969            .checked_sub(value)1970            .ok_or(Error::<T>::NumOverflow)?;1971        <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19721973        let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())1974            .checked_add(value)1975            .ok_or(Error::<T>::NumOverflow)?;1976        <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);19771978        let old_owner = item.owner.clone();1979        let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);19801981        // transfer1982        if amount == value && !new_owner_has_account {1983            // change owner1984            // new owner do not have account1985            let mut new_full_item = full_item.clone();1986            new_full_item1987                .owner1988                .iter_mut()1989                .find(|i| i.owner == owner)1990                .unwrap()1991                .owner = new_owner.clone();1992            <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);19931994            // update index collection1995            Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;1996        } else {1997            let mut new_full_item = full_item.clone();1998            new_full_item1999                .owner2000                .iter_mut()2001                .find(|i| i.owner == owner)2002                .unwrap()2003                .fraction -= value;20042005            // separate amount2006            if new_owner_has_account {2007                // new owner has account2008                new_full_item2009                    .owner2010                    .iter_mut()2011                    .find(|i| i.owner == new_owner)2012                    .unwrap()2013                    .fraction += value;2014            } else {2015                // new owner do not have account2016                new_full_item.owner.push(Ownership {2017                    owner: new_owner.clone(),2018                    fraction: value,2019                });2020                Self::add_token_index(collection_id, item_id, &new_owner)?;2021            }20222023            <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2024        }20252026        Ok(())2027    }20282029    fn transfer_nft(2030        collection_id: CollectionId,2031        item_id: TokenId,2032        sender: T::AccountId,2033        new_owner: T::AccountId,2034    ) -> DispatchResult {2035        Self::token_exists(collection_id, item_id, &sender)?;20362037        let mut item = <NftItemList<T>>::get(collection_id, item_id);20382039        ensure!(2040            sender == item.owner,2041            Error::<T>::MustBeTokenOwner2042        );20432044        // update balance2045        let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())2046            .checked_sub(1)2047            .ok_or(Error::<T>::NumOverflow)?;2048        <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);20492050        let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())2051            .checked_add(1)2052            .ok_or(Error::<T>::NumOverflow)?;2053        <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);20542055        // change owner2056        let old_owner = item.owner.clone();2057        item.owner = new_owner.clone();2058        <NftItemList<T>>::insert(collection_id, item_id, item);20592060        // update index collection2061        Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;20622063        Ok(())2064    }2065    2066    fn set_re_fungible_variable_data(2067        collection_id: CollectionId,2068        item_id: TokenId,2069        data: Vec<u8>2070    ) -> DispatchResult {2071        let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);20722073        item.variable_data = data;20742075        <ReFungibleItemList<T>>::insert(collection_id, item_id, item);20762077        Ok(())2078    }20792080    fn set_nft_variable_data(2081        collection_id: CollectionId,2082        item_id: TokenId,2083        data: Vec<u8>2084    ) -> DispatchResult {2085        let mut item = <NftItemList<T>>::get(collection_id, item_id);2086        2087        item.variable_data = data;20882089        <NftItemList<T>>::insert(collection_id, item_id, item);2090        2091        Ok(())2092    }20932094    fn init_collection(item: &CollectionType<T::AccountId>) {2095        // check params2096        assert!(2097            item.decimal_points <= MAX_DECIMAL_POINTS,2098            "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2099        );2100        assert!(2101            item.name.len() <= 64,2102            "Collection name can not be longer than 63 char"2103        );2104        assert!(2105            item.name.len() <= 256,2106            "Collection description can not be longer than 255 char"2107        );2108        assert!(2109            item.token_prefix.len() <= 16,2110            "Token prefix can not be longer than 15 char"2111        );21122113        // Generate next collection ID2114        let next_id = CreatedCollectionCount::get()2115            .checked_add(1)2116            .unwrap();21172118        CreatedCollectionCount::put(next_id);2119    }21202121    fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::AccountId>) {2122        let current_index = <ItemListIndex>::get(collection_id)2123            .checked_add(1)2124            .unwrap();21252126        let item_owner = item.owner.clone();2127        Self::add_token_index(collection_id, current_index, &item.owner).unwrap();21282129        <ItemListIndex>::insert(collection_id, current_index);21302131        // Update balance2132        let new_balance = <Balance<T>>::get(collection_id, &item_owner)2133            .checked_add(1)2134            .unwrap();2135        <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2136    }21372138    fn init_fungible_token(collection_id: CollectionId, owner: &T::AccountId, item: &FungibleItemType) {2139        let current_index = <ItemListIndex>::get(collection_id)2140            .checked_add(1)2141            .unwrap();21422143        Self::add_token_index(collection_id, current_index, owner).unwrap();21442145        <ItemListIndex>::insert(collection_id, current_index);21462147        // Update balance2148        let new_balance = <Balance<T>>::get(collection_id, owner)2149            .checked_add(item.value)2150            .unwrap();2151        <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);2152    }21532154    fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::AccountId>) {2155        let current_index = <ItemListIndex>::get(collection_id)2156            .checked_add(1)2157            .unwrap();21582159        let value = item.owner.first().unwrap().fraction;2160        let owner = item.owner.first().unwrap().owner.clone();21612162        Self::add_token_index(collection_id, current_index, &owner).unwrap();21632164        <ItemListIndex>::insert(collection_id, current_index);21652166        // Update balance2167        let new_balance = <Balance<T>>::get(collection_id, &owner)2168            .checked_add(value)2169            .unwrap();2170        <Balance<T>>::insert(collection_id, owner.clone(), new_balance);2171    }21722173    fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: &T::AccountId) -> DispatchResult {21742175        // add to account limit2176        if <AccountItemCount<T>>::contains_key(owner) {21772178            // bound Owned tokens by a single address2179            let count = <AccountItemCount<T>>::get(owner);2180            ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);21812182            <AccountItemCount<T>>::insert(owner.clone(), count2183                .checked_add(1)2184                .ok_or(Error::<T>::NumOverflow)?);2185        }2186        else {2187            <AccountItemCount<T>>::insert(owner.clone(), 1);2188        }21892190        let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner);2191        if list_exists {2192            let mut list = <AddressTokens<T>>::get(collection_id, owner);2193            let item_contains = list.contains(&item_index.clone());21942195            if !item_contains {2196                list.push(item_index.clone());2197            }21982199            <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2200        } else {2201            let mut itm = Vec::new();2202            itm.push(item_index.clone());2203            <AddressTokens<T>>::insert(collection_id, owner.clone(), itm);2204        }22052206        Ok(())2207    }22082209    fn remove_token_index(2210        collection_id: CollectionId,2211        item_index: TokenId,2212        owner: &T::AccountId,2213    ) -> DispatchResult {22142215        // update counter2216        <AccountItemCount<T>>::insert(owner.clone(), 2217            <AccountItemCount<T>>::get(owner)2218            .checked_sub(1)2219            .ok_or(Error::<T>::NumOverflow)?);222022212222        let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner);2223        if list_exists {2224            let mut list = <AddressTokens<T>>::get(collection_id, owner);2225            let item_contains = list.contains(&item_index.clone());22262227            if item_contains {2228                list.retain(|&item| item != item_index);2229                <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2230            }2231        }22322233        Ok(())2234    }22352236    fn move_token_index(2237        collection_id: CollectionId,2238        item_index: TokenId,2239        old_owner: &T::AccountId,2240        new_owner: &T::AccountId,2241    ) -> DispatchResult {2242        Self::remove_token_index(collection_id, item_index, old_owner)?;2243        Self::add_token_index(collection_id, item_index, new_owner)?;22442245        Ok(())2246    }2247    2248    fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult {2249        if <ContractOwner<T>>::contains_key(contract.clone()) {2250            let owner = <ContractOwner<T>>::get(contract);2251            ensure!(account == owner, Error::<T>::NoPermission);2252        } else {2253            fail!(Error::<T>::NoPermission);2254        }22552256        Ok(())2257    }2258}22592260////////////////////////////////////////////////////////////////////////////////////////////////////2261// Economic models2262// #region22632264/// Fee multiplier.2265pub type Multiplier = FixedU128;22662267type BalanceOf<T> = <<T as transaction_payment::Config>::OnChargeTransaction as transaction_payment::OnChargeTransaction<T>>::Balance;22682269/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2270/// in the queue.2271#[derive(Encode, Decode, Clone, Eq, PartialEq)]2272pub struct ChargeTransactionPayment<T: Config>(#[codec(compact)] BalanceOf<T>);22732274impl<T: Config + Send + Sync> sp_std::fmt::Debug 2275    for ChargeTransactionPayment<T>2276{2277	#[cfg(feature = "std")]2278	fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2279		write!(f, "ChargeTransactionPayment<{:?}>", self.0)2280	}2281	#[cfg(not(feature = "std"))]2282	fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2283		Ok(())2284	}2285}22862287impl<T: Config> ChargeTransactionPayment<T>2288where2289    T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2290    BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2291    T::AccountId: AsRef<[u8]>,2292    T::AccountId: UncheckedFrom<T::Hash>,2293{2294    fn traditional_fee(2295        len: usize,2296        info: &DispatchInfoOf<T::Call>,2297        tip: BalanceOf<T>,2298    ) -> BalanceOf<T>2299    where2300        T::Call: Dispatchable<Info = DispatchInfo>,2301    {2302        <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2303    }23042305	fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2306        let weight_saturation = T::BlockWeights::get().max_block / info.weight.max(1);2307        let max_block_length = *T::BlockLength::get().max.get(DispatchClass::Normal);2308        let len_saturation = max_block_length as u64 / (len as u64).max(1);2309        let coefficient: BalanceOf<T> = weight_saturation2310            .min(len_saturation)2311            .saturated_into::<BalanceOf<T>>();2312        final_fee2313            .saturating_mul(coefficient)2314            .saturated_into::<TransactionPriority>()2315    }23162317    fn withdraw_fee(2318        &self,2319        who: &T::AccountId,2320        call: &T::Call,2321        info: &DispatchInfoOf<T::Call>,2322        len: usize,2323	) -> Result<2324		(2325			BalanceOf<T>,2326			<<T as transaction_payment::Config>::OnChargeTransaction as transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,2327		),2328		TransactionValidityError,2329	> {2330        let tip = self.0;23312332        // Set fee based on call type. Creating collection costs 1 Unique.2333        // All other transactions have traditional fees so far2334        // let fee = match call.is_sub_type() {2335        //     Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2336        //     _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2337        //                                                 // _ => <BalanceOf<T>>::from(100)2338        // };2339        let fee = Self::traditional_fee(len, info, tip);23402341        // Only mess with balances if fee is not zero.2342        if fee.is_zero() {2343            return <<T as transaction_payment::Config>::OnChargeTransaction as transaction_payment::OnChargeTransaction<T>>::withdraw_fee(who, call, info, fee, tip)2344			.map(|i| (fee, i));2345        }23462347        // Determine who is paying transaction fee based on ecnomic model2348        // Parse call to extract collection ID and access collection sponsor2349        let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2350            Some(Call::create_item(collection_id, _owner, _properties)) => {23512352                // sponsor timeout2353                let block_number = <system::Module<T>>::block_number() as T::BlockNumber;23542355                let limit = <Collection<T>>::get(collection_id).limits.sponsor_transfer_timeout;2356                let mut sponsored = true;2357                if <CreateItemBasket<T>>::contains_key((collection_id, &who)) {2358                    let last_tx_block = <CreateItemBasket<T>>::get((collection_id, &who));2359                    let limit_time = last_tx_block + limit.into();2360                    if block_number <= limit_time {2361                        sponsored = false;2362                    }2363                }2364                if sponsored {2365                    <CreateItemBasket<T>>::insert((collection_id, who.clone()), block_number);2366                }23672368                // check free create limit2369                if (<Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)) &&2370                   (<Collection<T>>::get(collection_id).sponsor_confirmed) &&2371                   (sponsored)2372                {2373                    <Collection<T>>::get(collection_id).sponsor2374                } else {2375                    T::AccountId::default()2376                }2377            }2378            Some(Call::transfer(_new_owner, collection_id, item_id, _value)) => {2379                2380                let mut sponsor_transfer = false;2381                if <Collection<T>>::get(collection_id).sponsor_confirmed {23822383                    let collection_limits = <Collection<T>>::get(collection_id).limits;2384                    let collection_mode = <Collection<T>>::get(collection_id).mode;2385    2386                    // sponsor timeout2387                    let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2388                    sponsor_transfer = match collection_mode {2389                        CollectionMode::NFT => {2390    2391                            // get correct limit2392                            let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2393                                collection_limits.sponsor_transfer_timeout2394                            } else {2395                                ChainLimit::get().nft_sponsor_transfer_timeout2396                            };2397    2398                            let mut sponsored = true;2399                            if <NftTransferBasket<T>>::contains_key(collection_id, item_id) {2400                                let last_tx_block = <NftTransferBasket<T>>::get(collection_id, item_id);2401                                let limit_time = last_tx_block + limit.into();2402                                if block_number <= limit_time {2403                                    sponsored = false;2404                                }2405                            }2406                            if sponsored {2407                                <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);2408                            }24092410                            sponsored2411                        }2412                        CollectionMode::Fungible(_) => {2413    2414                            // get correct limit2415                            let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2416                                collection_limits.sponsor_transfer_timeout2417                            } else {2418                                ChainLimit::get().fungible_sponsor_transfer_timeout2419                            };2420    2421                            let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2422                            let mut sponsored = true;2423                            if <FungibleTransferBasket<T>>::contains_key(collection_id, who) {2424                                let last_tx_block = <FungibleTransferBasket<T>>::get(collection_id, who);2425                                let limit_time = last_tx_block + limit.into();2426                                if block_number <= limit_time {2427                                    sponsored = false;2428                                }2429                            }2430                            if sponsored {2431                                <FungibleTransferBasket<T>>::insert(collection_id, who, block_number);2432                            }24332434                            sponsored2435                        }2436                        CollectionMode::ReFungible => {2437    2438                            // get correct limit2439                            let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2440                                collection_limits.sponsor_transfer_timeout2441                            } else {2442                                ChainLimit::get().refungible_sponsor_transfer_timeout2443                            };2444    2445                            let mut sponsored = true;2446                            if <ReFungibleTransferBasket<T>>::contains_key(collection_id, item_id) {2447                                let last_tx_block = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);2448                                let limit_time = last_tx_block + limit.into();2449                                if block_number <= limit_time {2450                                    sponsored = false;2451                                }2452                            }2453                            if sponsored {2454                                <ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);2455                            }24562457                            sponsored2458                        }2459                        _ => {2460                            false2461                        },2462                    };2463                }24642465                if !sponsor_transfer {2466                    T::AccountId::default()2467                } else {2468                    <Collection<T>>::get(collection_id).sponsor2469                }2470            }24712472            _ => T::AccountId::default(),2473        };24742475        // Sponsor smart contracts2476        sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {24772478            // On instantiation: set the contract owner2479            Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, _data, salt)) => {24802481                let new_contract_address = <pallet_contracts::Module<T>>::contract_address(2482                    &who,2483                    code_hash,2484                    salt,2485                );2486                <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());24872488                T::AccountId::default()2489            },24902491            // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2492            Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {24932494                let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());24952496                let owned_contract = <ContractOwner<T>>::contains_key(called_contract.clone())2497                  && <ContractOwner<T>>::get(called_contract.clone()) == *who;2498                let white_list_enabled = <ContractWhiteListEnabled<T>>::contains_key(called_contract.clone()) && <ContractWhiteListEnabled<T>>::get(called_contract.clone());2499                  2500                if !owned_contract && white_list_enabled {2501                    if !<ContractWhiteList<T>>::contains_key(called_contract.clone(), who) {2502                        return Err(InvalidTransaction::Call.into());2503                    }2504                }25052506                let mut sponsor_transfer = false;2507                if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2508                    let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2509                    let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2510                    let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2511                    let limit_time = last_tx_block + rate_limit;25122513                    if block_number >= limit_time {2514                        <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2515                        sponsor_transfer = true;2516                    }2517                } else {2518                    sponsor_transfer = false;2519                }2520               2521                2522                let mut sp = T::AccountId::default();2523                if sponsor_transfer {2524                    if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2525                        if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2526                            sp = called_contract;2527                        }2528                    }2529                }25302531                sp2532            },25332534            _ => sponsor,2535        };25362537        let mut who_pays_fee: T::AccountId = sponsor.clone();2538        if sponsor == T::AccountId::default() {2539            who_pays_fee = who.clone();2540        }25412542		<<T as transaction_payment::Config>::OnChargeTransaction as transaction_payment::OnChargeTransaction<T>>::withdraw_fee(&who_pays_fee, call, info, fee, tip)2543			.map(|i| (fee, i))2544    }2545}254625472548impl<T: Config + Send + Sync> SignedExtension2549    for ChargeTransactionPayment<T>2550where2551    BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2552    T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2553    T::AccountId: AsRef<[u8]>,2554    T::AccountId: UncheckedFrom<T::Hash>,2555{2556    const IDENTIFIER: &'static str = "ChargeTransactionPayment";2557    type AccountId = T::AccountId;2558    type Call = T::Call;2559    type AdditionalSigned = ();2560    type Pre = (2561        // tip2562        BalanceOf<T>,2563        // who pays fee2564        Self::AccountId,2565		// imbalance resulting from withdrawing the fee2566		<<T as transaction_payment::Config>::OnChargeTransaction as transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,2567    );2568    fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2569        Ok(())2570    }25712572    fn validate(2573        &self,2574        who: &Self::AccountId,2575        call: &Self::Call,2576        info: &DispatchInfoOf<Self::Call>,2577        len: usize,2578    ) -> TransactionValidity {2579		let (fee, _) = self.withdraw_fee(who, call, info, len)?;2580		Ok(ValidTransaction {2581			priority: Self::get_priority(len, info, fee),2582			..Default::default()2583		})2584    }25852586    fn pre_dispatch(2587        self,2588        who: &Self::AccountId,2589        call: &Self::Call,2590        info: &DispatchInfoOf<Self::Call>,2591        len: usize,2592    ) -> Result<Self::Pre, TransactionValidityError> {2593        let (_fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2594        Ok((self.0, who.clone(), imbalance))2595    }25962597    fn post_dispatch(2598        pre: Self::Pre,2599        info: &DispatchInfoOf<Self::Call>,2600        post_info: &PostDispatchInfoOf<Self::Call>,2601        len: usize,2602        _result: &DispatchResult,2603    ) -> Result<(), TransactionValidityError> {2604		let (tip, who, imbalance) = pre;2605		let actual_fee = transaction_payment::Module::<T>::compute_actual_fee(2606			len as u32,2607			info,2608			post_info,2609			tip,2610		);2611		<T as transaction_payment::Config>::OnChargeTransaction::correct_and_deposit_fee(&who, info, post_info, actual_fee, tip, imbalance)?;2612		Ok(())2613    }2614}26152616// #endregion
after · pallets/nft/src/lib.rs
1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]78#![cfg_attr(not(feature = "std"), no_std)]910#[cfg(feature = "std")]11pub use std::*;1213#[cfg(feature = "std")]14pub use serde::*;1516use codec::{Decode, Encode};17pub use frame_support::{18    construct_runtime, decl_event, decl_module, decl_storage, decl_error,19    dispatch::DispatchResult,20    ensure, fail, parameter_types,21    traits::{22        Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,23        Randomness, IsSubType,24    },25    weights::{26        constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},27        DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,28        WeightToFeePolynomial, DispatchClass,29    },30    StorageValue,31};3233use frame_system::{self as system, ensure_signed, ensure_root};34use sp_runtime::sp_std::prelude::Vec;35use sp_runtime::{36    traits::{37        DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SaturatedConversion, SignedExtension, Zero,38    },39    transaction_validity::{40        TransactionPriority, InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,41    },42    FixedPointOperand, FixedU128,43};44use sp_runtime::traits::StaticLookup;45use pallet_contracts::chain_extension::UncheckedFrom;46use transaction_payment::OnChargeTransaction;4748#[cfg(test)]49mod mock;5051#[cfg(test)]52mod tests;5354mod default_weights;5556pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;57pub const MAX_REFUNGIBLE_PIECES: u128 = 1_000_000_000_000_000_000_000;58pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;59pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;6061// Structs62// #region6364pub type CollectionId = u32;65pub type TokenId = u32;66pub type DecimalPoints = u8;6768#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]69#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]70pub enum CollectionMode {71    Invalid,72    NFT,73    // decimal points74    Fungible(DecimalPoints),75    ReFungible,76}7778impl Default for CollectionMode {79    fn default() -> Self {80        Self::Invalid81    }82}8384impl Into<u8> for CollectionMode {85    fn into(self) -> u8 {86        match self {87            CollectionMode::Invalid => 0,88            CollectionMode::NFT => 1,89            CollectionMode::Fungible(_) => 2,90            CollectionMode::ReFungible => 3,91        }92    }93}9495#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]96#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]97pub enum AccessMode {98    Normal,99    WhiteList,100}101impl Default for AccessMode {102    fn default() -> Self {103        Self::Normal104    }105}106107#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]108#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]109pub enum SchemaVersion {110    ImageURL,111    Unique,112}113impl Default for SchemaVersion {114    fn default() -> Self {115        Self::ImageURL116    }117}118119#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]120#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]121pub struct Ownership<AccountId> {122    pub owner: AccountId,123    pub fraction: u128,124}125126#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]127#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]128pub struct CollectionType<AccountId> {129    pub owner: AccountId,130    pub mode: CollectionMode,131    pub access: AccessMode,132    pub decimal_points: DecimalPoints,133    pub name: Vec<u16>,        // 64 include null escape char134    pub description: Vec<u16>, // 256 include null escape char135    pub token_prefix: Vec<u8>, // 16 include null escape char136    pub mint_mode: bool,137    pub offchain_schema: Vec<u8>,138    pub schema_version: SchemaVersion,139    pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender140    pub sponsor_confirmed: bool, // False if sponsor address has not yet confirmed sponsorship. True otherwise.141    pub limits: CollectionLimits, // Collection private restrictions 142    pub variable_on_chain_schema: Vec<u8>, //143    pub const_on_chain_schema: Vec<u8>, //144}145146#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]147#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]148pub struct NftItemType<AccountId> {149    pub owner: AccountId,150    pub const_data: Vec<u8>,151    pub variable_data: Vec<u8>,152}153154#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]155#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]156pub struct FungibleItemType {157    pub value: u128,158}159160#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]161#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]162pub struct ReFungibleItemType<AccountId> {163    pub owner: Vec<Ownership<AccountId>>,164    pub const_data: Vec<u8>,165    pub variable_data: Vec<u8>,166}167168// #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]169// #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]170// pub struct VestingItem<AccountId, Moment> {171//     pub sender: AccountId,172//     pub recipient: AccountId,173//     pub collection_id: CollectionId,174//     pub item_id: TokenId,175//     pub amount: u64,176//     pub vesting_date: Moment,177// }178179#[derive(Encode, Decode, Debug, Clone, PartialEq)]180#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]181pub struct CollectionLimits {182    pub account_token_ownership_limit: u32,183    pub sponsored_data_size: u32,184    pub token_limit: u32,185186    // Timeouts for item types in passed blocks187    pub sponsor_transfer_timeout: u32,188    pub owner_can_transfer: bool,189    pub owner_can_destroy: bool,190}191192impl Default for CollectionLimits {193    fn default() -> CollectionLimits {194        CollectionLimits { 195            account_token_ownership_limit: 10_000_000, 196            token_limit: u32::max_value(),197            sponsored_data_size: u32::max_value(), 198            sponsor_transfer_timeout: 14400,199            owner_can_transfer: true,200            owner_can_destroy: true201        }202    }203}204205#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]206#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]207pub struct ChainLimits {208    pub collection_numbers_limit: u32,209    pub account_token_ownership_limit: u32,210    pub collections_admins_limit: u64,211    pub custom_data_limit: u32,212213    // Timeouts for item types in passed blocks214    pub nft_sponsor_transfer_timeout: u32,215    pub fungible_sponsor_transfer_timeout: u32,216    pub refungible_sponsor_transfer_timeout: u32,217218    // Schema limits219    pub offchain_schema_limit: u32,220    pub variable_on_chain_schema_limit: u32,221    pub const_on_chain_schema_limit: u32,222}223224pub trait WeightInfo {225	fn create_collection() -> Weight;226	fn destroy_collection() -> Weight;227	fn add_to_white_list() -> Weight;228	fn remove_from_white_list() -> Weight;229    fn set_public_access_mode() -> Weight;230    fn set_mint_permission() -> Weight;231    fn change_collection_owner() -> Weight;232    fn add_collection_admin() -> Weight;233    fn remove_collection_admin() -> Weight;234    fn set_collection_sponsor() -> Weight;235    fn confirm_sponsorship() -> Weight;236    fn remove_collection_sponsor() -> Weight;237    fn create_item(s: usize) -> Weight;238    fn burn_item() -> Weight;239    fn transfer() -> Weight;240    fn approve() -> Weight;241    fn transfer_from() -> Weight;242    fn set_offchain_schema() -> Weight;243    fn set_const_on_chain_schema() -> Weight;244    fn set_variable_on_chain_schema() -> Weight;245    fn set_variable_meta_data() -> Weight;246    fn enable_contract_sponsoring() -> Weight;247    fn set_schema_version() -> Weight;248    fn set_chain_limits() -> Weight;249    fn set_contract_sponsoring_rate_limit() -> Weight;250    fn toggle_contract_white_list() -> Weight;251    fn add_to_contract_white_list() -> Weight;252    fn remove_from_contract_white_list() -> Weight;253    fn set_collection_limits() -> Weight;254}255256#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]257#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]258pub struct CreateNftData {259    pub const_data: Vec<u8>,260    pub variable_data: Vec<u8>,261}262263#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]264#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]265pub struct CreateFungibleData {266    pub value: u128,267}268269#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]270#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]271pub struct CreateReFungibleData {272    pub const_data: Vec<u8>,273    pub variable_data: Vec<u8>,274    pub pieces: u128,275}276277#[derive(Encode, Decode, Debug, Clone, PartialEq)]278#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]279pub enum CreateItemData {280    NFT(CreateNftData),281    Fungible(CreateFungibleData),282    ReFungible(CreateReFungibleData),283}284285impl CreateItemData {286    pub fn len(&self) -> usize {287        let len = match self {288            CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),289            CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),290            _ => 0291        };292        293        return len;294    }295}296297impl From<CreateNftData> for CreateItemData {298    fn from(item: CreateNftData) -> Self {299        CreateItemData::NFT(item)300    }301}302303impl From<CreateReFungibleData> for CreateItemData {304    fn from(item: CreateReFungibleData) -> Self {305        CreateItemData::ReFungible(item)306    }307}308309impl From<CreateFungibleData> for CreateItemData {310    fn from(item: CreateFungibleData) -> Self {311        CreateItemData::Fungible(item)312    }313}314315316decl_error! {317	/// Error for non-fungible-token module.318	pub enum Error for Module<T: Config> {319        /// Total collections bound exceeded.320        TotalCollectionsLimitExceeded,321		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.322        CollectionDecimalPointLimitExceeded, 323        /// Collection name can not be longer than 63 char.324        CollectionNameLimitExceeded, 325        /// Collection description can not be longer than 255 char.326        CollectionDescriptionLimitExceeded, 327        /// Token prefix can not be longer than 15 char.328        CollectionTokenPrefixLimitExceeded,329        /// This collection does not exist.330        CollectionNotFound,331        /// Item not exists.332        TokenNotFound,333        /// Admin not found334        AdminNotFound,335        /// Arithmetic calculation overflow.336        NumOverflow,       337        /// Account already has admin role.338        AlreadyAdmin,  339        /// You do not own this collection.340        NoPermission,341        /// This address is not set as sponsor, use setCollectionSponsor first.342        ConfirmUnsetSponsorFail,343        /// Collection is not in mint mode.344        PublicMintingNotAllowed,345        /// Sender parameter and item owner must be equal.346        MustBeTokenOwner,347        /// Item balance not enough.348        TokenValueTooLow,349        /// Size of item is too large.350        NftSizeLimitExceeded,351        /// No approve found352        ApproveNotFound,353        /// Requested value more than approved.354        TokenValueNotEnough,355        /// Only approved addresses can call this method.356        ApproveRequired,357        /// Address is not in white list.358        AddresNotInWhiteList,359        /// Number of collection admins bound exceeded.360        CollectionAdminsLimitExceeded,361        /// Owned tokens by a single address bound exceeded.362        AddressOwnershipLimitExceeded,363        /// Length of items properties must be greater than 0.364        EmptyArgument,365        /// const_data exceeded data limit.366        TokenConstDataLimitExceeded,367        /// variable_data exceeded data limit.368        TokenVariableDataLimitExceeded,369        /// Not NFT item data used to mint in NFT collection.370        NotNftDataUsedToMintNftCollectionToken,371        /// Not Fungible item data used to mint in Fungible collection.372        NotFungibleDataUsedToMintFungibleCollectionToken,373        /// Not Re Fungible item data used to mint in Re Fungible collection.374        NotReFungibleDataUsedToMintReFungibleCollectionToken,375        /// Unexpected collection type.376        UnexpectedCollectionType,377        /// Can't store metadata in fungible tokens.378        CantStoreMetadataInFungibleTokens,379        /// Collection token limit exceeded380        CollectionTokenLimitExceeded,381        /// Account token limit exceeded per collection382        AccountTokenLimitExceeded,383        /// Collection limit bounds per collection exceeded384        CollectionLimitBoundsExceeded,385        /// Schema data size limit bound exceeded386        SchemaDataLimitExceeded,387        /// Maximum refungibility exceeded388        WrongRefungiblePieces389	}390}391392pub trait Config: system::Config + Sized + transaction_payment::Config + pallet_contracts::Config {393    type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;394395    /// Weight information for extrinsics in this pallet.396	type WeightInfo: WeightInfo;397}398399#[cfg(feature = "runtime-benchmarks")]400mod benchmarking;401402// #endregion403404decl_storage! {405    trait Store for Module<T: Config> as Nft {406407        // Private members408        NextCollectionID: CollectionId;409        CreatedCollectionCount: u32;410        ChainVersion: u64;411        ItemListIndex: map hasher(identity) CollectionId => TokenId;412413        // Chain limits struct414        pub ChainLimit get(fn chain_limit) config(): ChainLimits;415416        // Bound counters417        CollectionCount: u32;418        pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;419420        // Basic collections421        pub Collection get(fn collection) config(): map hasher(identity) CollectionId => CollectionType<T::AccountId>;422        pub AdminList get(fn admin_list_collection): map hasher(identity) CollectionId => Vec<T::AccountId>;423        pub WhiteList get(fn white_list): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => bool;424425        /// Balance owner per collection map426        pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;427428        /// second parameter: item id + owner account id + spender account id429        pub Allowances get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId, T::AccountId) => u128;430431        /// Item collections432        pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;433        pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => FungibleItemType;434        pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;435436        /// Index list437        pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;438439        /// Tokens transfer baskets440        pub CreateItemBasket get(fn create_item_basket): map hasher(twox_64_concat) (CollectionId, T::AccountId) => T::BlockNumber;441        pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;442        pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;443        pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;444445        // Contract Sponsorship and Ownership446        pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId;447        pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;448        pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;449        pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;450        pub ContractWhiteListEnabled get(fn contract_white_list_enabled): map hasher(twox_64_concat) T::AccountId => bool; 451        pub ContractWhiteList get(fn contract_white_list): double_map hasher(twox_64_concat) T::AccountId, hasher(twox_64_concat) T::AccountId => bool; 452    }453    add_extra_genesis {454        build(|config: &GenesisConfig<T>| {455            // Modification of storage456            for (_num, _c) in &config.collection {457                <Module<T>>::init_collection(_c);458            }459460            for (_num, _c, _i) in &config.nft_item_id {461                <Module<T>>::init_nft_token(*_c, _i);462            }463464            for (collection_id, account_id, fungible_item) in &config.fungible_item_id {465                <Module<T>>::init_fungible_token(*collection_id, account_id, fungible_item);466            }467468            for (_num, _c, _i) in &config.refungible_item_id {469                <Module<T>>::init_refungible_token(*_c, _i);470            }471        })472    }473}474475decl_event!(476    pub enum Event<T>477    where478        AccountId = <T as system::Config>::AccountId,479    {480        /// New collection was created481        /// 482        /// # Arguments483        /// 484        /// * collection_id: Globally unique identifier of newly created collection.485        /// 486        /// * mode: [CollectionMode] converted into u8.487        /// 488        /// * account_id: Collection owner.489        Created(CollectionId, u8, AccountId),490491        /// New item was created.492        /// 493        /// # Arguments494        /// 495        /// * collection_id: Id of the collection where item was created.496        /// 497        /// * item_id: Id of an item. Unique within the collection.498        ///499        /// * recipient: Owner of newly created item 500        ItemCreated(CollectionId, TokenId, AccountId),501502        /// Collection item was burned.503        /// 504        /// # Arguments505        /// 506        /// collection_id.507        /// 508        /// item_id: Identifier of burned NFT.509        ItemDestroyed(CollectionId, TokenId),510511        /// Item was transferred512        ///513        /// * collection_id: Id of collection to which item is belong514        ///515        /// * item_id: Id of an item516        ///517        /// * sender: Original owner of item518        ///519        /// * recipient: New owner of item520        ///521        /// * amount: Always 1 for NFT522        Transfer(CollectionId, TokenId, AccountId, AccountId, u128),523    }524);525526decl_module! {527    pub struct Module<T: Config> for enum Call 528    where 529        origin: T::Origin530    {531        fn deposit_event() = default;532        type Error = Error<T>;533534        fn on_initialize(now: T::BlockNumber) -> Weight {535536            if ChainVersion::get() < 2537            {538                let value = NextCollectionID::get();539                CreatedCollectionCount::put(value);540                ChainVersion::put(2);541            }542543            0544        }545546        /// 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.547        /// 548        /// # Permissions549        /// 550        /// * Anyone.551        /// 552        /// # Arguments553        /// 554        /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.555        /// 556        /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.557        /// 558        /// * token_prefix: UTF-8 string with token prefix.559        /// 560        /// * mode: [CollectionMode] collection type and type dependent data.561        // returns collection ID562        #[weight = <T as Config>::WeightInfo::create_collection()]563        pub fn create_collection(origin,564                                 collection_name: Vec<u16>,565                                 collection_description: Vec<u16>,566                                 token_prefix: Vec<u8>,567                                 mode: CollectionMode) -> DispatchResult {568569            // Anyone can create a collection570            let who = ensure_signed(origin)?;571572            let decimal_points = match mode {573                CollectionMode::Fungible(points) => points,574                _ => 0575            };576577            // bound Total number of collections578            ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);579580            // check params581            ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);582            ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);583            ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);584            ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);585586            // Generate next collection ID587            let next_id = CreatedCollectionCount::get()588                .checked_add(1)589                .ok_or(Error::<T>::NumOverflow)?;590591            // bound counter592            let total = CollectionCount::get()593                .checked_add(1)594                .ok_or(Error::<T>::NumOverflow)?;595596            CreatedCollectionCount::put(next_id);597            CollectionCount::put(total);598599            // Create new collection600            let new_collection = CollectionType {601                owner: who.clone(),602                name: collection_name,603                mode: mode.clone(),604                mint_mode: false,605                access: AccessMode::Normal,606                description: collection_description,607                decimal_points: decimal_points,608                token_prefix: token_prefix,609                offchain_schema: Vec::new(),610                schema_version: SchemaVersion::ImageURL,611                sponsor: T::AccountId::default(),612                sponsor_confirmed: false,613                variable_on_chain_schema: Vec::new(),614                const_on_chain_schema: Vec::new(),615                limits: CollectionLimits::default()616            };617618            // Add new collection to map619            <Collection<T>>::insert(next_id, new_collection);620621            // call event622            Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));623624            Ok(())625        }626627        /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.628        /// 629        /// # Permissions630        /// 631        /// * Collection Owner.632        /// 633        /// # Arguments634        /// 635        /// * collection_id: collection to destroy.636        #[weight = <T as Config>::WeightInfo::destroy_collection()]637        pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {638639            let sender = ensure_signed(origin)?;640            Self::check_owner_permissions(collection_id, sender)?;641642            <AddressTokens<T>>::remove_prefix(collection_id);643            <Allowances<T>>::remove_prefix(collection_id);644            <Balance<T>>::remove_prefix(collection_id);645            <ItemListIndex>::remove(collection_id);646            <AdminList<T>>::remove(collection_id);647            <Collection<T>>::remove(collection_id);648            <WhiteList<T>>::remove_prefix(collection_id);649650            <NftItemList<T>>::remove_prefix(collection_id);651            <FungibleItemList<T>>::remove_prefix(collection_id);652            <ReFungibleItemList<T>>::remove_prefix(collection_id);653654            <NftTransferBasket<T>>::remove_prefix(collection_id);655            <FungibleTransferBasket<T>>::remove_prefix(collection_id);656            <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);657658            if CollectionCount::get() > 0659            {660                // bound couter661                let total = CollectionCount::get()662                    .checked_sub(1)663                    .ok_or(Error::<T>::NumOverflow)?;664665                CollectionCount::put(total);666            }667668            Ok(())669        }670671        /// Add an address to white list.672        /// 673        /// # Permissions674        /// 675        /// * Collection Owner676        /// * Collection Admin677        /// 678        /// # Arguments679        /// 680        /// * collection_id.681        /// 682        /// * address.683        #[weight = <T as Config>::WeightInfo::add_to_white_list()]684        pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{685686            let sender = ensure_signed(origin)?;687            Self::check_owner_or_admin_permissions(collection_id, sender)?;688689            <WhiteList<T>>::insert(collection_id, address, true);690            691            Ok(())692        }693694        /// Remove an address from white list.695        /// 696        /// # Permissions697        /// 698        /// * Collection Owner699        /// * Collection Admin700        /// 701        /// # Arguments702        /// 703        /// * collection_id.704        /// 705        /// * address.706        #[weight = <T as Config>::WeightInfo::remove_from_white_list()]707        pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{708709            let sender = ensure_signed(origin)?;710            Self::check_owner_or_admin_permissions(collection_id, sender)?;711712            <WhiteList<T>>::remove(collection_id, address);713714            Ok(())715        }716717        /// Toggle between normal and white list access for the methods with access for `Anyone`.718        /// 719        /// # Permissions720        /// 721        /// * Collection Owner.722        /// 723        /// # Arguments724        /// 725        /// * collection_id.726        /// 727        /// * mode: [AccessMode]728        #[weight = <T as Config>::WeightInfo::set_public_access_mode()]729        pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult730        {731            let sender = ensure_signed(origin)?;732733            Self::check_owner_permissions(collection_id, sender)?;734            let mut target_collection = <Collection<T>>::get(collection_id);735            target_collection.access = mode;736            <Collection<T>>::insert(collection_id, target_collection);737738            Ok(())739        }740741        /// Allows Anyone to create tokens if:742        /// * White List is enabled, and743        /// * Address is added to white list, and744        /// * This method was called with True parameter745        /// 746        /// # Permissions747        /// * Collection Owner748        ///749        /// # Arguments750        /// 751        /// * collection_id.752        /// 753        /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.754        #[weight = <T as Config>::WeightInfo::set_mint_permission()]755        pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult756        {757            let sender = ensure_signed(origin)?;758759            Self::check_owner_permissions(collection_id, sender)?;760            let mut target_collection = <Collection<T>>::get(collection_id);761            target_collection.mint_mode = mint_permission;762            <Collection<T>>::insert(collection_id, target_collection);763764            Ok(())765        }766767        /// Change the owner of the collection.768        /// 769        /// # Permissions770        /// 771        /// * Collection Owner.772        /// 773        /// # Arguments774        /// 775        /// * collection_id.776        /// 777        /// * new_owner.778        #[weight = <T as Config>::WeightInfo::change_collection_owner()]779        pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {780781            let sender = ensure_signed(origin)?;782            Self::check_owner_permissions(collection_id, sender)?;783            let mut target_collection = <Collection<T>>::get(collection_id);784            target_collection.owner = new_owner;785            <Collection<T>>::insert(collection_id, target_collection);786787            Ok(())788        }789790        /// Adds an admin of the Collection.791        /// 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. 792        /// 793        /// # Permissions794        /// 795        /// * Collection Owner.796        /// * Collection Admin.797        /// 798        /// # Arguments799        /// 800        /// * collection_id: ID of the Collection to add admin for.801        /// 802        /// * new_admin_id: Address of new admin to add.803        #[weight = <T as Config>::WeightInfo::add_collection_admin()]804        pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {805806            let sender = ensure_signed(origin)?;807            Self::check_owner_or_admin_permissions(collection_id, sender)?;808            let mut admin_arr: Vec<T::AccountId> = Vec::new();809810            if <AdminList<T>>::contains_key(collection_id)811            {812                admin_arr = <AdminList<T>>::get(collection_id);813                ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);814            }815816            // Number of collection admins817            ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);818819            admin_arr.push(new_admin_id);820            <AdminList<T>>::insert(collection_id, admin_arr);821822            Ok(())823        }824825        /// 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.826        ///827        /// # Permissions828        /// 829        /// * Collection Owner.830        /// * Collection Admin.831        /// 832        /// # Arguments833        /// 834        /// * collection_id: ID of the Collection to remove admin for.835        /// 836        /// * account_id: Address of admin to remove.837        #[weight = <T as Config>::WeightInfo::remove_collection_admin()]838        pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {839840            let sender = ensure_signed(origin)?;841            Self::check_owner_or_admin_permissions(collection_id, sender)?;842            ensure!(<AdminList<T>>::contains_key(collection_id), Error::<T>::AdminNotFound);843844            let mut admin_arr = <AdminList<T>>::get(collection_id);845            admin_arr.retain(|i| *i != account_id);846            <AdminList<T>>::insert(collection_id, admin_arr);847848            Ok(())849        }850851        /// # Permissions852        /// 853        /// * Collection Owner854        /// 855        /// # Arguments856        /// 857        /// * collection_id.858        /// 859        /// * new_sponsor.860        #[weight = <T as Config>::WeightInfo::set_collection_sponsor()]861        pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {862863            let sender = ensure_signed(origin)?;864            ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);865866            let mut target_collection = <Collection<T>>::get(collection_id);867            ensure!(sender == target_collection.owner, Error::<T>::NoPermission);868869            target_collection.sponsor = new_sponsor;870            target_collection.sponsor_confirmed = false;871            <Collection<T>>::insert(collection_id, target_collection);872873            Ok(())874        }875876        /// # Permissions877        /// 878        /// * Sponsor.879        /// 880        /// # Arguments881        /// 882        /// * collection_id.883        #[weight = <T as Config>::WeightInfo::confirm_sponsorship()]884        pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {885886            let sender = ensure_signed(origin)?;887            ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);888889            let mut target_collection = <Collection<T>>::get(collection_id);890            ensure!(sender == target_collection.sponsor, Error::<T>::ConfirmUnsetSponsorFail);891892            target_collection.sponsor_confirmed = true;893            <Collection<T>>::insert(collection_id, target_collection);894895            Ok(())896        }897898        /// Switch back to pay-per-own-transaction model.899        ///900        /// # Permissions901        ///902        /// * Collection owner.903        /// 904        /// # Arguments905        /// 906        /// * collection_id.907        #[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]908        pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {909910            let sender = ensure_signed(origin)?;911            ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);912913            let mut target_collection = <Collection<T>>::get(collection_id);914            ensure!(sender == target_collection.owner, Error::<T>::NoPermission);915916            target_collection.sponsor = T::AccountId::default();917            target_collection.sponsor_confirmed = false;918            <Collection<T>>::insert(collection_id, target_collection);919920            Ok(())921        }922923        /// This method creates a concrete instance of NFT Collection created with CreateCollection method.924        /// 925        /// # Permissions926        /// 927        /// * Collection Owner.928        /// * Collection Admin.929        /// * Anyone if930        ///     * White List is enabled, and931        ///     * Address is added to white list, and932        ///     * MintPermission is enabled (see SetMintPermission method)933        /// 934        /// # Arguments935        /// 936        /// * collection_id: ID of the collection.937        /// 938        /// * owner: Address, initial owner of the NFT.939        ///940        /// * data: Token data to store on chain.941        // #[weight =942        // (130_000_000 as Weight)943        // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))944        // .saturating_add(RocksDbWeight::get().reads(10 as Weight))945        // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]946947        #[weight = <T as Config>::WeightInfo::create_item(data.len())]948        pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {949950            let sender = ensure_signed(origin)?;951952            Self::collection_exists(collection_id)?;953954            let target_collection = <Collection<T>>::get(collection_id);955956            Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;957            Self::validate_create_item_args(&target_collection, &data)?;958            Self::create_item_no_validation(collection_id, owner, data)?;959960            Ok(())961        }962963        /// This method creates multiple instances of NFT Collection created with CreateCollection method.964        /// 965        /// # Permissions966        /// 967        /// * Collection Owner.968        /// * Collection Admin.969        /// * Anyone if970        ///     * White List is enabled, and971        ///     * Address is added to white list, and972        ///     * MintPermission is enabled (see SetMintPermission method)973        /// 974        /// # Arguments975        /// 976        /// * collection_id: ID of the collection.977        /// 978        /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].979        /// 980        /// * owner: Address, initial owner of the NFT.981        #[weight = <T as Config>::WeightInfo::create_item(items_data.into_iter()982                               .map(|data| { data.len() })983                               .sum())]984        pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {985986            ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);987            let sender = ensure_signed(origin)?;988989            Self::collection_exists(collection_id)?;990            let target_collection = <Collection<T>>::get(collection_id);991992            Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;993994            for data in &items_data {995                Self::validate_create_item_args(&target_collection, data)?;996            }997            for data in &items_data {998                Self::create_item_no_validation(collection_id, owner.clone(), data.clone())?;999            }10001001            Ok(())1002        }10031004        /// Destroys a concrete instance of NFT.1005        /// 1006        /// # Permissions1007        /// 1008        /// * Collection Owner.1009        /// * Collection Admin.1010        /// * Current NFT Owner.1011        /// 1012        /// # Arguments1013        /// 1014        /// * collection_id: ID of the collection.1015        /// 1016        /// * item_id: ID of NFT to burn.1017        #[weight = <T as Config>::WeightInfo::burn_item()]1018        pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {10191020            let sender = ensure_signed(origin)?;1021            Self::collection_exists(collection_id)?;10221023            // Transfer permissions check1024            let target_collection = <Collection<T>>::get(collection_id);1025            ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1026                Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1027                Error::<T>::NoPermission);10281029            if target_collection.access == AccessMode::WhiteList {1030                Self::check_white_list(collection_id, &sender)?;1031            }10321033            match target_collection.mode1034            {1035                CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1036                CollectionMode::Fungible(_)  => Self::burn_fungible_item(&sender, collection_id, value)?,1037                CollectionMode::ReFungible  => Self::burn_refungible_item(collection_id, item_id, &sender)?,1038                _ => ()1039            };10401041            // call event1042            Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));10431044            Ok(())1045        }10461047        /// Change ownership of the token.1048        /// 1049        /// # Permissions1050        /// 1051        /// * Collection Owner1052        /// * Collection Admin1053        /// * Current NFT owner1054        ///1055        /// # Arguments1056        /// 1057        /// * recipient: Address of token recipient.1058        /// 1059        /// * collection_id.1060        /// 1061        /// * item_id: ID of the item1062        ///     * Non-Fungible Mode: Required.1063        ///     * Fungible Mode: Ignored.1064        ///     * Re-Fungible Mode: Required.1065        /// 1066        /// * value: Amount to transfer.1067        ///     * Non-Fungible Mode: Ignored1068        ///     * Fungible Mode: Must specify transferred amount1069        ///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1070        #[weight = <T as Config>::WeightInfo::transfer()]1071        pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {1072            let sender = ensure_signed(origin)?;1073            Self::transfer_internal(sender, recipient, collection_id, item_id, value)1074        }10751076        /// Set, change, or remove approved address to transfer the ownership of the NFT.1077        /// 1078        /// # Permissions1079        /// 1080        /// * Collection Owner1081        /// * Collection Admin1082        /// * Current NFT owner1083        /// 1084        /// # Arguments1085        /// 1086        /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1087        /// 1088        /// * collection_id.1089        /// 1090        /// * item_id: ID of the item.1091        #[weight = <T as Config>::WeightInfo::approve()]1092        pub fn approve(origin, spender: T::AccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {10931094            let sender = ensure_signed(origin)?;10951096            Self::collection_exists(collection_id)?;1097            Self::token_exists(collection_id, item_id, &sender)?;10981099            // Transfer permissions check1100            let target_collection = <Collection<T>>::get(collection_id);1101            ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1102                Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1103                Error::<T>::NoPermission);11041105            if target_collection.access == AccessMode::WhiteList {1106                Self::check_white_list(collection_id, &sender)?;1107                Self::check_white_list(collection_id, &spender)?;1108            }11091110            let allowance_exists = <Allowances<T>>::contains_key(collection_id, (item_id, &sender, &spender));1111            let mut allowance: u128 = amount;1112            if allowance_exists {1113                allowance += <Allowances<T>>::get(collection_id, (item_id, &sender, &spender));1114            }1115            <Allowances<T>>::insert(collection_id, (item_id, sender.clone(), spender.clone()), allowance);11161117            Ok(())1118        }1119        1120        /// 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.1121        /// 1122        /// # Permissions1123        /// * Collection Owner1124        /// * Collection Admin1125        /// * Current NFT owner1126        /// * Address approved by current NFT owner1127        /// 1128        /// # Arguments1129        /// 1130        /// * from: Address that owns token.1131        /// 1132        /// * recipient: Address of token recipient.1133        /// 1134        /// * collection_id.1135        /// 1136        /// * item_id: ID of the item.1137        /// 1138        /// * value: Amount to transfer.1139        #[weight = <T as Config>::WeightInfo::transfer_from()]1140        pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {11411142            let sender = ensure_signed(origin)?;1143            let mut appoved_transfer = false;11441145            // Check approval1146            let mut approval: u128 = 0;1147            if <Allowances<T>>::contains_key(collection_id, (item_id, &from, &sender)) {1148                approval = <Allowances<T>>::get(collection_id, (item_id, &from, &sender));1149                ensure!(approval >= value, Error::<T>::TokenValueNotEnough);1150                appoved_transfer = true;1151            }11521153            let target_collection = <Collection<T>>::get(collection_id);11541155            // Limits check1156            Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;11571158            // Transfer permissions check         1159            ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1160                Error::<T>::NoPermission);11611162            if target_collection.access == AccessMode::WhiteList {1163                Self::check_white_list(collection_id, &sender)?;1164                Self::check_white_list(collection_id, &recipient)?;1165            }11661167            // Reduce approval by transferred amount or remove if remaining approval drops to 01168            if approval.checked_sub(value).unwrap_or(0) > 0 {1169                <Allowances<T>>::insert(collection_id, (item_id, &from, &sender), approval - value);1170            }1171            else {1172                <Allowances<T>>::remove(collection_id, (item_id, &from, &sender));1173            }11741175            match target_collection.mode1176            {1177                CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1178                CollectionMode::Fungible(_)  => Self::transfer_fungible(collection_id, value, &from, &recipient)?,1179                CollectionMode::ReFungible  => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1180                _ => ()1181            };11821183            Ok(())1184        }11851186        // #[weight = 0]1187        // pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {11881189        //     // let no_perm_mes = "You do not have permissions to modify this collection";1190        //     // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1191        //     // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1192        //     // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);11931194        //     // // on_nft_received  call11951196        //     // Self::transfer(origin, collection_id, item_id, new_owner)?;11971198        //     Ok(())1199        // }12001201        /// Set off-chain data schema.1202        /// 1203        /// # Permissions1204        /// 1205        /// * Collection Owner1206        /// * Collection Admin1207        /// 1208        /// # Arguments1209        /// 1210        /// * collection_id.1211        /// 1212        /// * schema: String representing the offchain data schema.1213        #[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1214        pub fn set_variable_meta_data (1215            origin,1216            collection_id: CollectionId,1217            item_id: TokenId,1218            data: Vec<u8>1219        ) -> DispatchResult {1220            let sender = ensure_signed(origin)?;1221            1222            Self::collection_exists(collection_id)?;1223            Self::token_exists(collection_id, item_id, &sender)?;12241225            ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);12261227            // Modify permissions check1228            let target_collection = <Collection<T>>::get(collection_id);1229            ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1230                Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1231                Error::<T>::NoPermission);12321233            match target_collection.mode1234            {1235                CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1236                CollectionMode::ReFungible  => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1237                CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1238                _ => fail!(Error::<T>::UnexpectedCollectionType)1239            };12401241            Ok(())1242        }1243 1244        /// Set schema standard1245        /// ImageURL1246        /// Unique1247        /// 1248        /// # Permissions1249        /// 1250        /// * Collection Owner1251        /// * Collection Admin1252        /// 1253        /// # Arguments1254        /// 1255        /// * collection_id.1256        /// 1257        /// * schema: SchemaVersion: enum1258        #[weight = <T as Config>::WeightInfo::set_schema_version()]1259        pub fn set_schema_version(1260            origin,1261            collection_id: CollectionId,1262            version: SchemaVersion1263        ) -> DispatchResult {1264            let sender = ensure_signed(origin)?;1265            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;1266            let mut target_collection = <Collection<T>>::get(collection_id);1267            target_collection.schema_version = version;1268            <Collection<T>>::insert(collection_id, target_collection);12691270            Ok(())1271        }12721273        /// Set off-chain data schema.1274        /// 1275        /// # Permissions1276        /// 1277        /// * Collection Owner1278        /// * Collection Admin1279        /// 1280        /// # Arguments1281        /// 1282        /// * collection_id.1283        /// 1284        /// * schema: String representing the offchain data schema.1285        #[weight = <T as Config>::WeightInfo::set_offchain_schema()]1286        pub fn set_offchain_schema(1287            origin,1288            collection_id: CollectionId,1289            schema: Vec<u8>1290        ) -> DispatchResult {1291            let sender = ensure_signed(origin)?;1292            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12931294            // check schema limit1295            ensure!(schema.len() as u32 <= ChainLimit::get().offchain_schema_limit, "");12961297            let mut target_collection = <Collection<T>>::get(collection_id);1298            target_collection.offchain_schema = schema;1299            <Collection<T>>::insert(collection_id, target_collection);13001301            Ok(())1302        }13031304        /// Set const on-chain data schema.1305        /// 1306        /// # Permissions1307        /// 1308        /// * Collection Owner1309        /// * Collection Admin1310        /// 1311        /// # Arguments1312        /// 1313        /// * collection_id.1314        /// 1315        /// * schema: String representing the const on-chain data schema.1316        #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1317        pub fn set_const_on_chain_schema (1318            origin,1319            collection_id: CollectionId,1320            schema: Vec<u8>1321        ) -> DispatchResult {1322            let sender = ensure_signed(origin)?;1323            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13241325            // check schema limit1326            ensure!(schema.len() as u32 <= ChainLimit::get().const_on_chain_schema_limit, "");13271328            let mut target_collection = <Collection<T>>::get(collection_id);1329            target_collection.const_on_chain_schema = schema;1330            <Collection<T>>::insert(collection_id, target_collection);13311332            Ok(())1333        }13341335        /// Set variable on-chain data schema.1336        /// 1337        /// # Permissions1338        /// 1339        /// * Collection Owner1340        /// * Collection Admin1341        /// 1342        /// # Arguments1343        /// 1344        /// * collection_id.1345        /// 1346        /// * schema: String representing the variable on-chain data schema.1347        #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1348        pub fn set_variable_on_chain_schema (1349            origin,1350            collection_id: CollectionId,1351            schema: Vec<u8>1352        ) -> DispatchResult {1353            let sender = ensure_signed(origin)?;1354            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13551356            // check schema limit1357            ensure!(schema.len() as u32 <= ChainLimit::get().variable_on_chain_schema_limit, "");13581359            let mut target_collection = <Collection<T>>::get(collection_id);1360            target_collection.variable_on_chain_schema = schema;1361            <Collection<T>>::insert(collection_id, target_collection);13621363            Ok(())1364        }13651366        // Sudo permissions function1367        #[weight = <T as Config>::WeightInfo::set_chain_limits()]1368        pub fn set_chain_limits(1369            origin,1370            limits: ChainLimits1371        ) -> DispatchResult {13721373            #[cfg(not(feature = "runtime-benchmarks"))]1374            ensure_root(origin)?;13751376            <ChainLimit>::put(limits);1377            Ok(())1378        }13791380        /// Enable smart contract self-sponsoring.1381        /// 1382        /// # Permissions1383        /// 1384        /// * Contract Owner1385        /// 1386        /// # Arguments1387        /// 1388        /// * contract address1389        /// * enable flag1390        /// 1391        #[weight = <T as Config>::WeightInfo::enable_contract_sponsoring()]1392        pub fn enable_contract_sponsoring(1393            origin,1394            contract_address: T::AccountId,1395            enable: bool1396        ) -> DispatchResult {13971398            let sender = ensure_signed(origin)?;13991400            #[cfg(feature = "runtime-benchmarks")]1401            <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14021403            Self::ensure_contract_owned(sender, &contract_address)?;14041405            <ContractSelfSponsoring<T>>::insert(contract_address, enable);1406            Ok(())1407        }14081409        /// Set the rate limit for contract sponsoring to specified number of blocks.1410        /// 1411        /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1412        /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1413        /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1414        /// from contract endowment if there are at least B blocks between such transactions. 1415        /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1416        /// 1417        /// # Permissions1418        /// 1419        /// * Contract Owner1420        /// 1421        /// # Arguments1422        /// 1423        /// -`contract_address`: Address of the contract to sponsor1424        /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1425        /// 1426        #[weight = <T as Config>::WeightInfo::set_contract_sponsoring_rate_limit()]1427        pub fn set_contract_sponsoring_rate_limit(1428            origin,1429            contract_address: T::AccountId,1430            rate_limit: T::BlockNumber1431        ) -> DispatchResult {1432            let sender = ensure_signed(origin)?;14331434            #[cfg(feature = "runtime-benchmarks")]1435            <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14361437            Self::ensure_contract_owned(sender, &contract_address)?;1438            <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1439            Ok(())1440        }14411442        /// Enable the white list for a contract. Only addresses added to the white list with addToContractWhiteList will be able to call this smart contract.1443        /// 1444        /// # Permissions1445        /// 1446        /// * Address that deployed smart contract.1447        /// 1448        /// # Arguments1449        /// 1450        /// -`contract_address`: Address of the contract.1451        /// 1452        /// - `enable`: .  1453        #[weight = <T as Config>::WeightInfo::toggle_contract_white_list()]1454        pub fn toggle_contract_white_list(1455            origin,1456            contract_address: T::AccountId,1457            enable: bool1458        ) -> DispatchResult {1459            let sender = ensure_signed(origin)?;14601461            #[cfg(feature = "runtime-benchmarks")]1462            <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14631464            Self::ensure_contract_owned(sender, &contract_address)?;1465            <ContractWhiteListEnabled<T>>::insert(contract_address, enable);1466            Ok(())1467        }1468        1469        /// Add an address to smart contract white list.1470        /// 1471        /// # Permissions1472        /// 1473        /// * Address that deployed smart contract.1474        /// 1475        /// # Arguments1476        /// 1477        /// -`contract_address`: Address of the contract.1478        ///1479        /// -`account_address`: Address to add.1480        #[weight = <T as Config>::WeightInfo::add_to_contract_white_list()]1481        pub fn add_to_contract_white_list(1482            origin,1483            contract_address: T::AccountId,1484            account_address: T::AccountId1485        ) -> DispatchResult {1486            let sender = ensure_signed(origin)?;14871488            #[cfg(feature = "runtime-benchmarks")]1489            <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());1490            1491            Self::ensure_contract_owned(sender, &contract_address)?;      1492            <ContractWhiteList<T>>::insert(contract_address, account_address, true);1493            Ok(())1494        }14951496        /// Remove an address from smart contract white list.1497        /// 1498        /// # Permissions1499        /// 1500        /// * Address that deployed smart contract.1501        /// 1502        /// # Arguments1503        /// 1504        /// -`contract_address`: Address of the contract.1505        ///1506        /// -`account_address`: Address to remove.1507        #[weight = <T as Config>::WeightInfo::remove_from_contract_white_list()]1508        pub fn remove_from_contract_white_list(1509            origin,1510            contract_address: T::AccountId,1511            account_address: T::AccountId1512        ) -> DispatchResult {1513            let sender = ensure_signed(origin)?;15141515            #[cfg(feature = "runtime-benchmarks")]1516            <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15171518            Self::ensure_contract_owned(sender, &contract_address)?;1519            <ContractWhiteList<T>>::remove(contract_address, account_address);1520            Ok(())1521        }15221523        #[weight = <T as Config>::WeightInfo::set_collection_limits()]1524        pub fn set_collection_limits(1525            origin,1526            collection_id: u32,1527            limits: CollectionLimits,1528        ) -> DispatchResult {1529            let sender = ensure_signed(origin)?;1530            Self::check_owner_permissions(collection_id, sender.clone())?;1531            let mut target_collection = <Collection<T>>::get(collection_id);1532            let chain_limits = ChainLimit::get();1533            let climits = target_collection.limits;15341535            // collection bounds1536            ensure!(limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1537                limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP,  1538                Error::<T>::CollectionLimitBoundsExceeded);15391540            // token_limit   check  prev1541            ensure!(climits.token_limit > limits.token_limit && 1542                limits.token_limit <= chain_limits.account_token_ownership_limit, 1543                Error::<T>::AccountTokenLimitExceeded);15441545            target_collection.limits = limits;1546            <Collection<T>>::insert(collection_id, target_collection);15471548            Ok(())1549        } 1550    }1551}15521553impl<T: Config> Module<T> {15541555    pub fn transfer_internal(sender: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {15561557        let target_collection = <Collection<T>>::get(collection_id);15581559        // Limits check1560        Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;15611562        // Transfer permissions check1563        ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1564            Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1565            Error::<T>::NoPermission);15661567        if target_collection.access == AccessMode::WhiteList {1568            Self::check_white_list(collection_id, &sender)?;1569            Self::check_white_list(collection_id, &recipient)?;1570        }15711572        match target_collection.mode1573        {1574            CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient.clone())?,1575            CollectionMode::Fungible(_)  => Self::transfer_fungible(collection_id, value, &sender, &recipient)?,1576            CollectionMode::ReFungible  => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient.clone())?,1577            _ => ()1578        };15791580        Self::deposit_event(RawEvent::Transfer(collection_id, item_id, sender, recipient, value));15811582        Ok(())1583    }158415851586    fn is_correct_transfer(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, recipient: &T::AccountId) -> DispatchResult {15871588        // check token limit and account token limit1589        let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1590        ensure!(collection.limits.account_token_ownership_limit > account_items,  Error::<T>::AccountTokenLimitExceeded);1591        1592        Ok(())1593    }15941595    fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {15961597        // check token limit and account token limit1598        let total_items: u32 = ItemListIndex::get(collection_id);1599        let account_items: u32 = <AddressTokens<T>>::get(collection_id, owner).len() as u32;1600        ensure!(collection.limits.token_limit > total_items,  Error::<T>::CollectionTokenLimitExceeded);1601        ensure!(collection.limits.account_token_ownership_limit > account_items,  Error::<T>::AccountTokenLimitExceeded);16021603        if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1604            ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1605            Self::check_white_list(collection_id, owner)?;1606            Self::check_white_list(collection_id, sender)?;1607        }16081609        Ok(())1610    }16111612    fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1613        match target_collection.mode1614        {1615            CollectionMode::NFT => {1616                if let CreateItemData::NFT(data) = data {1617                    // check sizes1618                    ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1619                    ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1620                } else {1621                    fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1622                }1623            },1624            CollectionMode::Fungible(_) => {1625                if let CreateItemData::Fungible(_) = data {1626                } else {1627                    fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1628                }1629            },1630            CollectionMode::ReFungible => {1631                if let CreateItemData::ReFungible(data) = data {16321633                    // check sizes1634                    ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1635                    ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);16361637                    // Check refungibility limits1638                    ensure!(data.pieces <= MAX_REFUNGIBLE_PIECES, Error::<T>::WrongRefungiblePieces);1639                    ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1640                } else {1641                    fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1642                }1643            },1644            _ => { fail!(Error::<T>::UnexpectedCollectionType); }1645        };16461647        Ok(())1648    }16491650    fn create_item_no_validation(collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1651        match data1652        {1653            CreateItemData::NFT(data) => {1654                let item = NftItemType {1655                    owner: owner.clone(),1656                    const_data: data.const_data,1657                    variable_data: data.variable_data1658                };16591660                Self::add_nft_item(collection_id, item)?;1661            },1662            CreateItemData::Fungible(data) => {1663                Self::add_fungible_item(collection_id, &owner, data.value)?;1664            },1665            CreateItemData::ReFungible(data) => {1666                let mut owner_list = Vec::new();1667                owner_list.push(Ownership {owner: owner.clone(), fraction: data.pieces});16681669                let item = ReFungibleItemType {1670                    owner: owner_list,1671                    const_data: data.const_data,1672                    variable_data: data.variable_data1673                };16741675                Self::add_refungible_item(collection_id, item)?;1676            }1677        };16781679        // call event1680        Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id), owner));16811682        Ok(())1683    }16841685    fn add_fungible_item(collection_id: CollectionId, owner: &T::AccountId, value: u128) -> DispatchResult {16861687        // Does new owner already have an account?1688        let mut balance: u128 = 0;1689        if <FungibleItemList<T>>::contains_key(collection_id, owner) {1690            balance = <FungibleItemList<T>>::get(collection_id, owner).value;1691        } 16921693        // Mint 1694        let item = FungibleItemType {1695            value: balance + value1696        };1697        <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), item);16981699        // Update balance1700        let new_balance = <Balance<T>>::get(collection_id, owner)1701            .checked_add(value)1702            .ok_or(Error::<T>::NumOverflow)?;1703        <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);17041705        Ok(())1706    }17071708    fn add_refungible_item(collection_id: CollectionId, item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1709        let current_index = <ItemListIndex>::get(collection_id)1710            .checked_add(1)1711            .ok_or(Error::<T>::NumOverflow)?;1712        let itemcopy = item.clone();17131714        let value = item.owner.first().unwrap().fraction;1715        let owner = item.owner.first().unwrap().owner.clone();17161717        Self::add_token_index(collection_id, current_index, &owner)?;17181719        <ItemListIndex>::insert(collection_id, current_index);1720        <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);17211722        // Update balance1723        let new_balance = <Balance<T>>::get(collection_id, &owner)1724            .checked_add(value)1725            .ok_or(Error::<T>::NumOverflow)?;1726        <Balance<T>>::insert(collection_id, owner.clone(), new_balance);17271728        Ok(())1729    }17301731    fn add_nft_item(collection_id: CollectionId, item: NftItemType<T::AccountId>) -> DispatchResult {1732        let current_index = <ItemListIndex>::get(collection_id)1733            .checked_add(1)1734            .ok_or(Error::<T>::NumOverflow)?;17351736        let item_owner = item.owner.clone();1737        Self::add_token_index(collection_id, current_index, &item.owner)?;17381739        <ItemListIndex>::insert(collection_id, current_index);1740        <NftItemList<T>>::insert(collection_id, current_index, item);17411742        // Update balance1743        let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1744            .checked_add(1)1745            .ok_or(Error::<T>::NumOverflow)?;1746        <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);17471748        Ok(())1749    }17501751    fn burn_refungible_item(1752        collection_id: CollectionId,1753        item_id: TokenId,1754        owner: &T::AccountId,1755    ) -> DispatchResult {1756        ensure!(1757            <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1758            Error::<T>::TokenNotFound1759        );1760        let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id);1761        let rft_balance = token1762            .owner1763            .iter()1764            .filter(|&i| i.owner == *owner)1765            .next()1766            .unwrap();1767        Self::remove_token_index(collection_id, item_id, owner)?;17681769        // update balance1770        let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.clone())1771            .checked_sub(rft_balance.fraction)1772            .ok_or(Error::<T>::NumOverflow)?;1773        <Balance<T>>::insert(collection_id, rft_balance.owner.clone(), new_balance);17741775        // Re-create owners list with sender removed1776        let index = token1777            .owner1778            .iter()1779            .position(|i| i.owner == *owner)1780            .unwrap();1781        token.owner.remove(index);1782        let owner_count = token.owner.len();17831784        // Burn the token completely if this was the last (only) owner1785        if owner_count == 0 {1786            <ReFungibleItemList<T>>::remove(collection_id, item_id);1787        }1788        else {1789            <ReFungibleItemList<T>>::insert(collection_id, item_id, token);1790        }17911792        Ok(())1793    }17941795    fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1796        ensure!(1797            <NftItemList<T>>::contains_key(collection_id, item_id),1798            Error::<T>::TokenNotFound1799        );1800        let item = <NftItemList<T>>::get(collection_id, item_id);1801        Self::remove_token_index(collection_id, item_id, &item.owner)?;18021803        // update balance1804        let new_balance = <Balance<T>>::get(collection_id, &item.owner)1805            .checked_sub(1)1806            .ok_or(Error::<T>::NumOverflow)?;1807        <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1808        <NftItemList<T>>::remove(collection_id, item_id);18091810        Ok(())1811    }18121813    fn burn_fungible_item(owner: &T::AccountId, collection_id: CollectionId, value: u128) -> DispatchResult {1814        ensure!(1815            <FungibleItemList<T>>::contains_key(collection_id, owner),1816            Error::<T>::TokenNotFound1817        );1818        let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1819        ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);18201821        // update balance1822        let new_balance = <Balance<T>>::get(collection_id, owner)1823            .checked_sub(value)1824            .ok_or(Error::<T>::NumOverflow)?;1825        <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);18261827        if balance.value - value > 0 {1828            balance.value -= value;1829            <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1830        }1831        else {1832            <FungibleItemList<T>>::remove(collection_id, owner);1833        }18341835        Ok(())1836    }18371838    fn collection_exists(collection_id: CollectionId) -> DispatchResult {1839        ensure!(1840            <Collection<T>>::contains_key(collection_id),1841            Error::<T>::CollectionNotFound1842        );1843        Ok(())1844    }18451846    fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult {1847        Self::collection_exists(collection_id)?;18481849        let target_collection = <Collection<T>>::get(collection_id);1850        ensure!(1851            subject == target_collection.owner,1852            Error::<T>::NoPermission1853        );18541855        Ok(())1856    }18571858    fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool {1859        let target_collection = <Collection<T>>::get(collection_id);1860        let mut result: bool = subject == target_collection.owner;1861        let exists = <AdminList<T>>::contains_key(collection_id);18621863        if !result & exists {1864            if <AdminList<T>>::get(collection_id).contains(&subject) {1865                result = true1866            }1867        }18681869        result1870    }18711872    fn check_owner_or_admin_permissions(1873        collection_id: CollectionId,1874        subject: T::AccountId,1875    ) -> DispatchResult {1876        Self::collection_exists(collection_id)?;1877        let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());18781879        ensure!(1880            result,1881            Error::<T>::NoPermission1882        );1883        Ok(())1884    }18851886    fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool {1887        let target_collection = <Collection<T>>::get(collection_id);18881889        match target_collection.mode {1890            CollectionMode::NFT => {1891                <NftItemList<T>>::get(collection_id, item_id).owner == subject1892            }1893            CollectionMode::Fungible(_) => {1894                <FungibleItemList<T>>::contains_key(collection_id, &subject)1895            }1896            CollectionMode::ReFungible => {1897                <ReFungibleItemList<T>>::get(collection_id, item_id)1898                    .owner1899                    .iter()1900                    .any(|i| i.owner == subject)1901            }1902            CollectionMode::Invalid => false,1903        }1904    }19051906    fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult {1907        let mes = Error::<T>::AddresNotInWhiteList;1908        ensure!(<WhiteList<T>>::contains_key(collection_id, address), mes);19091910        Ok(())1911    }19121913    /// Check if token exists. In case of Fungible, check if there is an entry for 1914    /// the owner in fungible balances double map1915    fn token_exists(1916        collection_id: CollectionId,1917        item_id: TokenId,1918        owner: &T::AccountId1919    ) -> DispatchResult {1920        let target_collection = <Collection<T>>::get(collection_id);1921        let exists = match target_collection.mode1922        {1923            CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),1924            CollectionMode::Fungible(_)  => <FungibleItemList<T>>::contains_key(collection_id, owner),1925            CollectionMode::ReFungible  => <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1926            _ => false1927        };19281929        ensure!(exists == true, Error::<T>::TokenNotFound);1930        Ok(())1931    }19321933    fn transfer_fungible(1934        collection_id: CollectionId,1935        value: u128,1936        owner: &T::AccountId,1937        recipient: &T::AccountId,1938    ) -> DispatchResult {1939        Self::token_exists(collection_id, 0, owner)?;19401941        let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1942        ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);19431944        // Send balance to recipient (updates balanceOf of recipient)1945        Self::add_fungible_item(collection_id, recipient, value)?;19461947        // update balanceOf of sender1948        <Balance<T>>::insert(collection_id, (*owner).clone(), balance.value - value);19491950        // Reduce or remove sender1951        if balance.value == value {1952            <FungibleItemList<T>>::remove(collection_id, owner);1953        }1954        else {1955            balance.value -= value;1956            <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1957        }19581959        Ok(())1960    }19611962    fn transfer_refungible(1963        collection_id: CollectionId,1964        item_id: TokenId,1965        value: u128,1966        owner: T::AccountId,1967        new_owner: T::AccountId,1968    ) -> DispatchResult {1969        Self::token_exists(collection_id, item_id, &owner)?;19701971        let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1972        let item = full_item1973            .owner1974            .iter()1975            .filter(|i| i.owner == owner)1976            .next()1977            .ok_or(Error::<T>::NumOverflow)?;1978        let amount = item.fraction;19791980        ensure!(amount >= value, Error::<T>::TokenValueTooLow);19811982        // update balance1983        let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1984            .checked_sub(value)1985            .ok_or(Error::<T>::NumOverflow)?;1986        <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19871988        let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())1989            .checked_add(value)1990            .ok_or(Error::<T>::NumOverflow)?;1991        <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);19921993        let old_owner = item.owner.clone();1994        let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);19951996        // transfer1997        if amount == value && !new_owner_has_account {1998            // change owner1999            // new owner do not have account2000            let mut new_full_item = full_item.clone();2001            new_full_item2002                .owner2003                .iter_mut()2004                .find(|i| i.owner == owner)2005                .unwrap()2006                .owner = new_owner.clone();2007            <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);20082009            // update index collection2010            Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;2011        } else {2012            let mut new_full_item = full_item.clone();2013            new_full_item2014                .owner2015                .iter_mut()2016                .find(|i| i.owner == owner)2017                .unwrap()2018                .fraction -= value;20192020            // separate amount2021            if new_owner_has_account {2022                // new owner has account2023                new_full_item2024                    .owner2025                    .iter_mut()2026                    .find(|i| i.owner == new_owner)2027                    .unwrap()2028                    .fraction += value;2029            } else {2030                // new owner do not have account2031                new_full_item.owner.push(Ownership {2032                    owner: new_owner.clone(),2033                    fraction: value,2034                });2035                Self::add_token_index(collection_id, item_id, &new_owner)?;2036            }20372038            <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2039        }20402041        Ok(())2042    }20432044    fn transfer_nft(2045        collection_id: CollectionId,2046        item_id: TokenId,2047        sender: T::AccountId,2048        new_owner: T::AccountId,2049    ) -> DispatchResult {2050        Self::token_exists(collection_id, item_id, &sender)?;20512052        let mut item = <NftItemList<T>>::get(collection_id, item_id);20532054        ensure!(2055            sender == item.owner,2056            Error::<T>::MustBeTokenOwner2057        );20582059        // update balance2060        let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())2061            .checked_sub(1)2062            .ok_or(Error::<T>::NumOverflow)?;2063        <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);20642065        let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())2066            .checked_add(1)2067            .ok_or(Error::<T>::NumOverflow)?;2068        <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);20692070        // change owner2071        let old_owner = item.owner.clone();2072        item.owner = new_owner.clone();2073        <NftItemList<T>>::insert(collection_id, item_id, item);20742075        // update index collection2076        Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;20772078        Ok(())2079    }2080    2081    fn set_re_fungible_variable_data(2082        collection_id: CollectionId,2083        item_id: TokenId,2084        data: Vec<u8>2085    ) -> DispatchResult {2086        let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);20872088        item.variable_data = data;20892090        <ReFungibleItemList<T>>::insert(collection_id, item_id, item);20912092        Ok(())2093    }20942095    fn set_nft_variable_data(2096        collection_id: CollectionId,2097        item_id: TokenId,2098        data: Vec<u8>2099    ) -> DispatchResult {2100        let mut item = <NftItemList<T>>::get(collection_id, item_id);2101        2102        item.variable_data = data;21032104        <NftItemList<T>>::insert(collection_id, item_id, item);2105        2106        Ok(())2107    }21082109    fn init_collection(item: &CollectionType<T::AccountId>) {2110        // check params2111        assert!(2112            item.decimal_points <= MAX_DECIMAL_POINTS,2113            "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2114        );2115        assert!(2116            item.name.len() <= 64,2117            "Collection name can not be longer than 63 char"2118        );2119        assert!(2120            item.name.len() <= 256,2121            "Collection description can not be longer than 255 char"2122        );2123        assert!(2124            item.token_prefix.len() <= 16,2125            "Token prefix can not be longer than 15 char"2126        );21272128        // Generate next collection ID2129        let next_id = CreatedCollectionCount::get()2130            .checked_add(1)2131            .unwrap();21322133        CreatedCollectionCount::put(next_id);2134    }21352136    fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::AccountId>) {2137        let current_index = <ItemListIndex>::get(collection_id)2138            .checked_add(1)2139            .unwrap();21402141        let item_owner = item.owner.clone();2142        Self::add_token_index(collection_id, current_index, &item.owner).unwrap();21432144        <ItemListIndex>::insert(collection_id, current_index);21452146        // Update balance2147        let new_balance = <Balance<T>>::get(collection_id, &item_owner)2148            .checked_add(1)2149            .unwrap();2150        <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2151    }21522153    fn init_fungible_token(collection_id: CollectionId, owner: &T::AccountId, item: &FungibleItemType) {2154        let current_index = <ItemListIndex>::get(collection_id)2155            .checked_add(1)2156            .unwrap();21572158        Self::add_token_index(collection_id, current_index, owner).unwrap();21592160        <ItemListIndex>::insert(collection_id, current_index);21612162        // Update balance2163        let new_balance = <Balance<T>>::get(collection_id, owner)2164            .checked_add(item.value)2165            .unwrap();2166        <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);2167    }21682169    fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::AccountId>) {2170        let current_index = <ItemListIndex>::get(collection_id)2171            .checked_add(1)2172            .unwrap();21732174        let value = item.owner.first().unwrap().fraction;2175        let owner = item.owner.first().unwrap().owner.clone();21762177        Self::add_token_index(collection_id, current_index, &owner).unwrap();21782179        <ItemListIndex>::insert(collection_id, current_index);21802181        // Update balance2182        let new_balance = <Balance<T>>::get(collection_id, &owner)2183            .checked_add(value)2184            .unwrap();2185        <Balance<T>>::insert(collection_id, owner.clone(), new_balance);2186    }21872188    fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: &T::AccountId) -> DispatchResult {21892190        // add to account limit2191        if <AccountItemCount<T>>::contains_key(owner) {21922193            // bound Owned tokens by a single address2194            let count = <AccountItemCount<T>>::get(owner);2195            ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);21962197            <AccountItemCount<T>>::insert(owner.clone(), count2198                .checked_add(1)2199                .ok_or(Error::<T>::NumOverflow)?);2200        }2201        else {2202            <AccountItemCount<T>>::insert(owner.clone(), 1);2203        }22042205        let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner);2206        if list_exists {2207            let mut list = <AddressTokens<T>>::get(collection_id, owner);2208            let item_contains = list.contains(&item_index.clone());22092210            if !item_contains {2211                list.push(item_index.clone());2212            }22132214            <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2215        } else {2216            let mut itm = Vec::new();2217            itm.push(item_index.clone());2218            <AddressTokens<T>>::insert(collection_id, owner.clone(), itm);2219        }22202221        Ok(())2222    }22232224    fn remove_token_index(2225        collection_id: CollectionId,2226        item_index: TokenId,2227        owner: &T::AccountId,2228    ) -> DispatchResult {22292230        // update counter2231        <AccountItemCount<T>>::insert(owner.clone(), 2232            <AccountItemCount<T>>::get(owner)2233            .checked_sub(1)2234            .ok_or(Error::<T>::NumOverflow)?);223522362237        let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner);2238        if list_exists {2239            let mut list = <AddressTokens<T>>::get(collection_id, owner);2240            let item_contains = list.contains(&item_index.clone());22412242            if item_contains {2243                list.retain(|&item| item != item_index);2244                <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2245            }2246        }22472248        Ok(())2249    }22502251    fn move_token_index(2252        collection_id: CollectionId,2253        item_index: TokenId,2254        old_owner: &T::AccountId,2255        new_owner: &T::AccountId,2256    ) -> DispatchResult {2257        Self::remove_token_index(collection_id, item_index, old_owner)?;2258        Self::add_token_index(collection_id, item_index, new_owner)?;22592260        Ok(())2261    }2262    2263    fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult {2264        if <ContractOwner<T>>::contains_key(contract.clone()) {2265            let owner = <ContractOwner<T>>::get(contract);2266            ensure!(account == owner, Error::<T>::NoPermission);2267        } else {2268            fail!(Error::<T>::NoPermission);2269        }22702271        Ok(())2272    }2273}22742275////////////////////////////////////////////////////////////////////////////////////////////////////2276// Economic models2277// #region22782279/// Fee multiplier.2280pub type Multiplier = FixedU128;22812282type BalanceOf<T> = <<T as transaction_payment::Config>::OnChargeTransaction as transaction_payment::OnChargeTransaction<T>>::Balance;22832284/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2285/// in the queue.2286#[derive(Encode, Decode, Clone, Eq, PartialEq)]2287pub struct ChargeTransactionPayment<T: Config>(#[codec(compact)] BalanceOf<T>);22882289impl<T: Config + Send + Sync> sp_std::fmt::Debug 2290    for ChargeTransactionPayment<T>2291{2292	#[cfg(feature = "std")]2293	fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2294		write!(f, "ChargeTransactionPayment<{:?}>", self.0)2295	}2296	#[cfg(not(feature = "std"))]2297	fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2298		Ok(())2299	}2300}23012302impl<T: Config> ChargeTransactionPayment<T>2303where2304    T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2305    BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2306    T::AccountId: AsRef<[u8]>,2307    T::AccountId: UncheckedFrom<T::Hash>,2308{2309    fn traditional_fee(2310        len: usize,2311        info: &DispatchInfoOf<T::Call>,2312        tip: BalanceOf<T>,2313    ) -> BalanceOf<T>2314    where2315        T::Call: Dispatchable<Info = DispatchInfo>,2316    {2317        <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2318    }23192320	fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2321        let weight_saturation = T::BlockWeights::get().max_block / info.weight.max(1);2322        let max_block_length = *T::BlockLength::get().max.get(DispatchClass::Normal);2323        let len_saturation = max_block_length as u64 / (len as u64).max(1);2324        let coefficient: BalanceOf<T> = weight_saturation2325            .min(len_saturation)2326            .saturated_into::<BalanceOf<T>>();2327        final_fee2328            .saturating_mul(coefficient)2329            .saturated_into::<TransactionPriority>()2330    }23312332    fn withdraw_fee(2333        &self,2334        who: &T::AccountId,2335        call: &T::Call,2336        info: &DispatchInfoOf<T::Call>,2337        len: usize,2338	) -> Result<2339		(2340			BalanceOf<T>,2341			<<T as transaction_payment::Config>::OnChargeTransaction as transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,2342		),2343		TransactionValidityError,2344	> {2345        let tip = self.0;23462347        // Set fee based on call type. Creating collection costs 1 Unique.2348        // All other transactions have traditional fees so far2349        // let fee = match call.is_sub_type() {2350        //     Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2351        //     _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2352        //                                                 // _ => <BalanceOf<T>>::from(100)2353        // };2354        let fee = Self::traditional_fee(len, info, tip);23552356        // Only mess with balances if fee is not zero.2357        if fee.is_zero() {2358            return <<T as transaction_payment::Config>::OnChargeTransaction as transaction_payment::OnChargeTransaction<T>>::withdraw_fee(who, call, info, fee, tip)2359			.map(|i| (fee, i));2360        }23612362        // Determine who is paying transaction fee based on ecnomic model2363        // Parse call to extract collection ID and access collection sponsor2364        let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2365            Some(Call::create_item(collection_id, _owner, _properties)) => {23662367                // sponsor timeout2368                let block_number = <system::Module<T>>::block_number() as T::BlockNumber;23692370                let limit = <Collection<T>>::get(collection_id).limits.sponsor_transfer_timeout;2371                let mut sponsored = true;2372                if <CreateItemBasket<T>>::contains_key((collection_id, &who)) {2373                    let last_tx_block = <CreateItemBasket<T>>::get((collection_id, &who));2374                    let limit_time = last_tx_block + limit.into();2375                    if block_number <= limit_time {2376                        sponsored = false;2377                    }2378                }2379                if sponsored {2380                    <CreateItemBasket<T>>::insert((collection_id, who.clone()), block_number);2381                }23822383                // check free create limit2384                if (<Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)) &&2385                   (<Collection<T>>::get(collection_id).sponsor_confirmed) &&2386                   (sponsored)2387                {2388                    <Collection<T>>::get(collection_id).sponsor2389                } else {2390                    T::AccountId::default()2391                }2392            }2393            Some(Call::transfer(_new_owner, collection_id, item_id, _value)) => {2394                2395                let mut sponsor_transfer = false;2396                if <Collection<T>>::get(collection_id).sponsor_confirmed {23972398                    let collection_limits = <Collection<T>>::get(collection_id).limits;2399                    let collection_mode = <Collection<T>>::get(collection_id).mode;2400    2401                    // sponsor timeout2402                    let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2403                    sponsor_transfer = match collection_mode {2404                        CollectionMode::NFT => {2405    2406                            // get correct limit2407                            let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2408                                collection_limits.sponsor_transfer_timeout2409                            } else {2410                                ChainLimit::get().nft_sponsor_transfer_timeout2411                            };2412    2413                            let mut sponsored = true;2414                            if <NftTransferBasket<T>>::contains_key(collection_id, item_id) {2415                                let last_tx_block = <NftTransferBasket<T>>::get(collection_id, item_id);2416                                let limit_time = last_tx_block + limit.into();2417                                if block_number <= limit_time {2418                                    sponsored = false;2419                                }2420                            }2421                            if sponsored {2422                                <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);2423                            }24242425                            sponsored2426                        }2427                        CollectionMode::Fungible(_) => {2428    2429                            // get correct limit2430                            let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2431                                collection_limits.sponsor_transfer_timeout2432                            } else {2433                                ChainLimit::get().fungible_sponsor_transfer_timeout2434                            };2435    2436                            let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2437                            let mut sponsored = true;2438                            if <FungibleTransferBasket<T>>::contains_key(collection_id, who) {2439                                let last_tx_block = <FungibleTransferBasket<T>>::get(collection_id, who);2440                                let limit_time = last_tx_block + limit.into();2441                                if block_number <= limit_time {2442                                    sponsored = false;2443                                }2444                            }2445                            if sponsored {2446                                <FungibleTransferBasket<T>>::insert(collection_id, who, block_number);2447                            }24482449                            sponsored2450                        }2451                        CollectionMode::ReFungible => {2452    2453                            // get correct limit2454                            let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2455                                collection_limits.sponsor_transfer_timeout2456                            } else {2457                                ChainLimit::get().refungible_sponsor_transfer_timeout2458                            };2459    2460                            let mut sponsored = true;2461                            if <ReFungibleTransferBasket<T>>::contains_key(collection_id, item_id) {2462                                let last_tx_block = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);2463                                let limit_time = last_tx_block + limit.into();2464                                if block_number <= limit_time {2465                                    sponsored = false;2466                                }2467                            }2468                            if sponsored {2469                                <ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);2470                            }24712472                            sponsored2473                        }2474                        _ => {2475                            false2476                        },2477                    };2478                }24792480                if !sponsor_transfer {2481                    T::AccountId::default()2482                } else {2483                    <Collection<T>>::get(collection_id).sponsor2484                }2485            }24862487            _ => T::AccountId::default(),2488        };24892490        // Sponsor smart contracts2491        sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {24922493            // On instantiation: set the contract owner2494            Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, _data, salt)) => {24952496                let new_contract_address = <pallet_contracts::Module<T>>::contract_address(2497                    &who,2498                    code_hash,2499                    salt,2500                );2501                <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());25022503                T::AccountId::default()2504            },25052506            // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2507            Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {25082509                let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());25102511                let owned_contract = <ContractOwner<T>>::contains_key(called_contract.clone())2512                  && <ContractOwner<T>>::get(called_contract.clone()) == *who;2513                let white_list_enabled = <ContractWhiteListEnabled<T>>::contains_key(called_contract.clone()) && <ContractWhiteListEnabled<T>>::get(called_contract.clone());2514                  2515                if !owned_contract && white_list_enabled {2516                    if !<ContractWhiteList<T>>::contains_key(called_contract.clone(), who) {2517                        return Err(InvalidTransaction::Call.into());2518                    }2519                }25202521                let mut sponsor_transfer = false;2522                if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2523                    let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2524                    let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2525                    let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2526                    let limit_time = last_tx_block + rate_limit;25272528                    if block_number >= limit_time {2529                        <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2530                        sponsor_transfer = true;2531                    }2532                } else {2533                    sponsor_transfer = false;2534                }2535               2536                2537                let mut sp = T::AccountId::default();2538                if sponsor_transfer {2539                    if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2540                        if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2541                            sp = called_contract;2542                        }2543                    }2544                }25452546                sp2547            },25482549            _ => sponsor,2550        };25512552        let mut who_pays_fee: T::AccountId = sponsor.clone();2553        if sponsor == T::AccountId::default() {2554            who_pays_fee = who.clone();2555        }25562557		<<T as transaction_payment::Config>::OnChargeTransaction as transaction_payment::OnChargeTransaction<T>>::withdraw_fee(&who_pays_fee, call, info, fee, tip)2558			.map(|i| (fee, i))2559    }2560}256125622563impl<T: Config + Send + Sync> SignedExtension2564    for ChargeTransactionPayment<T>2565where2566    BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2567    T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2568    T::AccountId: AsRef<[u8]>,2569    T::AccountId: UncheckedFrom<T::Hash>,2570{2571    const IDENTIFIER: &'static str = "ChargeTransactionPayment";2572    type AccountId = T::AccountId;2573    type Call = T::Call;2574    type AdditionalSigned = ();2575    type Pre = (2576        // tip2577        BalanceOf<T>,2578        // who pays fee2579        Self::AccountId,2580		// imbalance resulting from withdrawing the fee2581		<<T as transaction_payment::Config>::OnChargeTransaction as transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,2582    );2583    fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2584        Ok(())2585    }25862587    fn validate(2588        &self,2589        who: &Self::AccountId,2590        call: &Self::Call,2591        info: &DispatchInfoOf<Self::Call>,2592        len: usize,2593    ) -> TransactionValidity {2594		let (fee, _) = self.withdraw_fee(who, call, info, len)?;2595		Ok(ValidTransaction {2596			priority: Self::get_priority(len, info, fee),2597			..Default::default()2598		})2599    }26002601    fn pre_dispatch(2602        self,2603        who: &Self::AccountId,2604        call: &Self::Call,2605        info: &DispatchInfoOf<Self::Call>,2606        len: usize,2607    ) -> Result<Self::Pre, TransactionValidityError> {2608        let (_fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2609        Ok((self.0, who.clone(), imbalance))2610    }26112612    fn post_dispatch(2613        pre: Self::Pre,2614        info: &DispatchInfoOf<Self::Call>,2615        post_info: &PostDispatchInfoOf<Self::Call>,2616        len: usize,2617        _result: &DispatchResult,2618    ) -> Result<(), TransactionValidityError> {2619		let (tip, who, imbalance) = pre;2620		let actual_fee = transaction_payment::Module::<T>::compute_actual_fee(2621			len as u32,2622			info,2623			post_info,2624			tip,2625		);2626		<T as transaction_payment::Config>::OnChargeTransaction::correct_and_deposit_fee(&who, info, post_info, actual_fee, tip, imbalance)?;2627		Ok(())2628    }2629}26302631// #endregion
modifiedtests/src/util/helpers.tsdiffbeforeafterboth
--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -39,6 +39,15 @@
   recipient: string;
 }
 
+interface TransferResult {
+  success: boolean;
+  collectionId: number;
+  itemId: number;
+  sender: string;
+  recipient: string;
+  value: bigint;
+}
+
 interface IReFungibleOwner {
   Fraction: BN;
   Owner: number[];
@@ -115,6 +124,31 @@
   return result;
 }
 
+export function getTransferResult(events: EventRecord[]): TransferResult {
+  const result: TransferResult = {
+    success: false,
+    collectionId: 0,
+    itemId: 0,
+    sender: '',
+    recipient: '',
+    value: 0n,
+  };
+
+  events.forEach(({event: {data, method, section}}) => {
+    if (method === 'ExtrinsicSuccess') {
+      result.success = true;
+    } else if (section === 'nft' && method === 'Transfer') {
+      result.collectionId = +data[0].toString();
+      result.itemId = +data[1].toString();
+      result.sender = data[2].toString();
+      result.recipient = data[3].toString();
+      result.value = BigInt(data[4].toString());
+    }
+  });
+
+  return result;
+}
+
 interface Invalid {
   type: 'Invalid';
 }
@@ -624,9 +658,14 @@
     }
     const transferTx = await api.tx.nft.transfer(recipient.address, collectionId, tokenId, value);
     const events = await submitTransactionAsync(sender, transferTx);
-    const result = getCreateItemResult(events);
+    const result = getTransferResult(events);
     // tslint:disable-next-line:no-unused-expression
     expect(result.success).to.be.true;
+    expect(result.collectionId).to.be.equal(collectionId);
+    expect(result.itemId).to.be.equal(tokenId);
+    expect(result.sender).to.be.equal(sender.address);
+    expect(result.recipient).to.be.equal(recipient.address);
+    expect(result.value.toString()).to.be.equal(value.toString());
     if (type === 'NFT') {
       const nftItemData = await api.query.nft.nftItemList(collectionId, tokenId) as unknown as ITokenDataType;
       expect(nftItemData.Owner.toString()).to.be.equal(recipient.address);