git.delta.rocks / unique-network / refs/commits / 96967bb10fde

difftreelog

Integration tests for setCollectionSponsor

Greg Zaitsev2020-12-23parent: #8686594.patch.diff
in: master

6 files changed

modifiednode/src/chain_spec.rsdiffbeforeafterboth
--- a/node/src/chain_spec.rs
+++ b/node/src/chain_spec.rs
@@ -193,7 +193,7 @@
 					offchain_schema: vec![],
 					schema_version: SchemaVersion::default(),
                     sponsor: get_account_id_from_seed::<sr25519::Public>("Alice"),
-                    unconfirmed_sponsor: get_account_id_from_seed::<sr25519::Public>("Alice"),
+                    sponsor_confirmed: true,
                     const_on_chain_schema: vec![],
 					variable_on_chain_schema: vec![],
 					limits: CollectionLimits::default()
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, WithdrawReason,24    },25    weights::{26        constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},27        DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,28        WeightToFeePolynomial,29    },30    IsSubType, 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 pallet_contracts::ContractAddressFor;45use sp_runtime::traits::StaticLookup;4647#[cfg(test)]48mod mock;4950#[cfg(test)]51mod tests;5253mod default_weights;5455pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;56pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;57pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;5859// Structs60// #region6162pub type CollectionId = u32;63pub type TokenId = u32;64pub type DecimalPoints = u8;6566#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]67#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]68pub enum CollectionMode {69    Invalid,70    NFT,71    // decimal points72    Fungible(DecimalPoints),73    // decimal points74    ReFungible(DecimalPoints),75}7677impl Into<u8> for CollectionMode {78    fn into(self) -> u8 {79        match self {80            CollectionMode::Invalid => 0,81            CollectionMode::NFT => 1,82            CollectionMode::Fungible(_) => 2,83            CollectionMode::ReFungible(_) => 3,84        }85    }86}8788#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]89#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]90pub enum AccessMode {91    Normal,92    WhiteList,93}94impl Default for AccessMode {95    fn default() -> Self {96        Self::Normal97    }98}99100impl Default for CollectionMode {101    fn default() -> Self {102        Self::Invalid103    }104}105106#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]107#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]108pub enum SchemaVersion {109    ImageURL,110    Unique,111}112impl Default for SchemaVersion {113    fn default() -> Self {114        Self::ImageURL115    }116}117118#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]119#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]120pub struct Ownership<AccountId> {121    pub owner: AccountId,122    pub fraction: u128,123}124125#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]126#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]127pub struct CollectionType<AccountId> {128    pub owner: AccountId,129    pub mode: CollectionMode,130    pub access: AccessMode,131    pub decimal_points: DecimalPoints,132    pub name: Vec<u16>,        // 64 include null escape char133    pub description: Vec<u16>, // 256 include null escape char134    pub token_prefix: Vec<u8>, // 16 include null escape char135    pub mint_mode: bool,136    pub offchain_schema: Vec<u8>,137    pub schema_version: SchemaVersion,138    pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender139    pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship140    pub limits: CollectionLimits, // Collection private restrictions 141    pub variable_on_chain_schema: Vec<u8>, //142    pub const_on_chain_schema: Vec<u8>, //143}144145#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]146#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]147pub struct NftItemType<AccountId> {148    pub collection: CollectionId,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<AccountId> {157    pub collection: CollectionId,158    pub owner: AccountId,159    pub value: u128,160}161162#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]163#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]164pub struct ReFungibleItemType<AccountId> {165    pub collection: CollectionId,166    pub owner: Vec<Ownership<AccountId>>,167    pub const_data: Vec<u8>,168    pub variable_data: Vec<u8>,169}170171#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]172#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]173pub struct ApprovePermissions<AccountId> {174    pub approved: AccountId,175    pub amount: u128,176}177178#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]179#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]180pub struct VestingItem<AccountId, Moment> {181    pub sender: AccountId,182    pub recipient: AccountId,183    pub collection_id: CollectionId,184    pub item_id: TokenId,185    pub amount: u64,186    pub vesting_date: Moment,187}188189#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]190#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]191pub struct BasketItem<AccountId, BlockNumber> {192    pub address: AccountId,193    pub start_block: BlockNumber,194}195196#[derive(Encode, Decode, Debug, Clone, PartialEq)]197#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]198pub struct CollectionLimits {199    pub account_token_ownership_limit: u32,200    pub sponsored_data_size: u32,201    pub token_limit: u32,202203    // Timeouts for item types in passed blocks204    pub sponsor_transfer_timeout: u32,205}206207impl Default for CollectionLimits {208    fn default() -> CollectionLimits {209        CollectionLimits { 210            account_token_ownership_limit: 10_000_000, 211            token_limit: u32::max_value(),212            sponsored_data_size: u32::max_value(), 213            sponsor_transfer_timeout: 14400 }214    }215}216217#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]218#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]219pub struct ChainLimits {220    pub collection_numbers_limit: u32,221    pub account_token_ownership_limit: u32,222    pub collections_admins_limit: u64,223    pub custom_data_limit: u32,224225    // Timeouts for item types in passed blocks226    pub nft_sponsor_transfer_timeout: u32,227    pub fungible_sponsor_transfer_timeout: u32,228    pub refungible_sponsor_transfer_timeout: u32,229}230231pub trait WeightInfo {232	fn create_collection() -> Weight;233	fn destroy_collection() -> Weight;234	fn add_to_white_list() -> Weight;235	fn remove_from_white_list() -> Weight;236    fn set_public_access_mode() -> Weight;237    fn set_mint_permission() -> Weight;238    fn change_collection_owner() -> Weight;239    fn add_collection_admin() -> Weight;240    fn remove_collection_admin() -> Weight;241    fn set_collection_sponsor() -> Weight;242    fn confirm_sponsorship() -> Weight;243    fn remove_collection_sponsor() -> Weight;244    fn create_item(s: usize) -> Weight;245    fn burn_item() -> Weight;246    fn transfer() -> Weight;247    fn approve() -> Weight;248    fn transfer_from() -> Weight;249    fn set_offchain_schema() -> Weight;250    fn set_const_on_chain_schema() -> Weight;251    fn set_variable_on_chain_schema() -> Weight;252    fn set_variable_meta_data() -> Weight;253    fn enable_contract_sponsoring() -> 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}267268#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]269#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]270pub struct CreateReFungibleData {271    pub const_data: Vec<u8>,272    pub variable_data: Vec<u8>,273}274275#[derive(Encode, Decode, Debug, Clone, PartialEq)]276#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]277pub enum CreateItemData {278    NFT(CreateNftData),279    Fungible(CreateFungibleData),280    ReFungible(CreateReFungibleData),281}282283impl CreateItemData {284    pub fn len(&self) -> usize {285        let len = match self {286            CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),287            CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),288            _ => 0289        };290        291        return len;292    }293}294295impl From<CreateNftData> for CreateItemData {296    fn from(item: CreateNftData) -> Self {297        CreateItemData::NFT(item)298    }299}300301impl From<CreateReFungibleData> for CreateItemData {302    fn from(item: CreateReFungibleData) -> Self {303        CreateItemData::ReFungible(item)304    }305}306307impl From<CreateFungibleData> for CreateItemData {308    fn from(item: CreateFungibleData) -> Self {309        CreateItemData::Fungible(item)310    }311}312313314decl_error! {315	/// Error for non-fungible-token module.316	pub enum Error for Module<T: Trait> {317        /// Total collections bound exceeded.318        TotalCollectionsLimitExceeded,319		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.320        CollectionDecimalPointLimitExceeded, 321        /// Collection name can not be longer than 63 char.322        CollectionNameLimitExceeded, 323        /// Collection description can not be longer than 255 char.324        CollectionDescriptionLimitExceeded, 325        /// Token prefix can not be longer than 15 char.326        CollectionTokenPrefixLimitExceeded,327        /// This collection does not exist.328        CollectionNotFound,329        /// Item not exists.330        TokenNotFound,331        /// Arithmetic calculation overflow.332        NumOverflow,       333        /// Account already has admin role.334        AlreadyAdmin,  335        /// You do not own this collection.336        NoPermission,337        /// This address is not set as sponsor, use setCollectionSponsor first.338        ConfirmUnsetSponsorFail,339        /// Collection is not in mint mode.340        PublicMintingNotAllowed,341        /// Sender parameter and item owner must be equal.342        MustBeTokenOwner,343        /// Item balance not enough.344        TokenValueTooLow,345        /// Size of item is too large.346        NftSizeLimitExceeded,347        /// No approve found348        ApproveNotFound,349        /// Requested value more than approved.350        TokenValueNotEnough,351        /// Only approved addresses can call this method.352        ApproveRequired,353        /// Address is not in white list.354        AddresNotInWhiteList,355        /// Number of collection admins bound exceeded.356        CollectionAdminsLimitExceeded,357        /// Owned tokens by a single address bound exceeded.358        AddressOwnershipLimitExceeded,359        /// Length of items properties must be greater than 0.360        EmptyArgument,361        /// const_data exceeded data limit.362        TokenConstDataLimitExceeded,363        /// variable_data exceeded data limit.364        TokenVariableDataLimitExceeded,365        /// Not NFT item data used to mint in NFT collection.366        NotNftDataUsedToMintNftCollectionToken,367        /// Not Fungible item data used to mint in Fungible collection.368        NotFungibleDataUsedToMintFungibleCollectionToken,369        /// Not Re Fungible item data used to mint in Re Fungible collection.370        NotReFungibleDataUsedToMintReFungibleCollectionToken,371        /// Unexpected collection type.372        UnexpectedCollectionType,373        /// Can't store metadata in fungible tokens.374        CantStoreMetadataInFungibleTokens,375        /// Collection token limit exceeded376        CollectionTokenLimitExceeded,377        /// Account token limit exceeded per collection378        AccountTokenLimitExceeded,379        /// Collection limit bounds per collection exceeded380        CollectionLimitBoundsExceeded381	}382}383384pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait {385    type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;386387    /// Weight information for extrinsics in this pallet.388	type WeightInfo: WeightInfo;389}390391#[cfg(feature = "runtime-benchmarks")]392mod benchmarking;393394// #endregion395396decl_storage! {397    trait Store for Module<T: Trait> as Nft {398399        // Private members400        NextCollectionID: CollectionId;401        CreatedCollectionCount: u32;402        ChainVersion: u64;403        ItemListIndex: map hasher(identity) CollectionId => TokenId;404405        // Chain limits struct406        pub ChainLimit get(fn chain_limit) config(): ChainLimits;407408        // Bound counters409        CollectionCount: u32;410        pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;411412        // Basic collections413        pub Collection get(fn collection) config(): map hasher(identity) CollectionId => CollectionType<T::AccountId>;414        pub AdminList get(fn admin_list_collection): map hasher(identity) CollectionId => Vec<T::AccountId>;415        pub WhiteList get(fn white_list): map hasher(identity) CollectionId => Vec<T::AccountId>;416417        /// Balance owner per collection map418        pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;419420        /// second parameter: item id + owner account id421        pub ApprovedList get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;422423        /// Item collections424        pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;425        pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => FungibleItemType<T::AccountId>;426        pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;427428        /// Index list429        pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;430431        /// Tokens transfer baskets432        pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;433        pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => Vec<BasketItem<T::AccountId, T::BlockNumber>>;434        pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;435436        // Contract Sponsorship and Ownership437        pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId;438        pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;439        pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;440        pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;441    }442    add_extra_genesis {443        build(|config: &GenesisConfig<T>| {444            // Modification of storage445            for (_num, _c) in &config.collection {446                <Module<T>>::init_collection(_c);447            }448449            for (_num, _q, _i) in &config.nft_item_id {450                <Module<T>>::init_nft_token(_i);451            }452453            for (_num, _q, _i) in &config.fungible_item_id {454                <Module<T>>::init_fungible_token(_i);455            }456457            for (_num, _q, _i) in &config.refungible_item_id {458                <Module<T>>::init_refungible_token(_i);459            }460        })461    }462}463464decl_event!(465    pub enum Event<T>466    where467        AccountId = <T as system::Trait>::AccountId,468    {469        /// New collection was created470        /// 471        /// # Arguments472        /// 473        /// * collection_id: Globally unique identifier of newly created collection.474        /// 475        /// * mode: [CollectionMode] converted into u8.476        /// 477        /// * account_id: Collection owner.478        Created(CollectionId, u8, AccountId),479480        /// New item was created.481        /// 482        /// # Arguments483        /// 484        /// * collection_id: Id of the collection where item was created.485        /// 486        /// * item_id: Id of an item. Unique within the collection.487        ItemCreated(CollectionId, TokenId),488489        /// Collection item was burned.490        /// 491        /// # Arguments492        /// 493        /// collection_id.494        /// 495        /// item_id: Identifier of burned NFT.496        ItemDestroyed(CollectionId, TokenId),497    }498);499500decl_module! {501    pub struct Module<T: Trait> for enum Call where origin: T::Origin {502503        fn deposit_event() = default;504        type Error = Error<T>;505506        fn on_initialize(now: T::BlockNumber) -> Weight {507508            if ChainVersion::get() < 2509            {510                let value = NextCollectionID::get();511                CreatedCollectionCount::put(value);512                ChainVersion::put(2);513            }514515            0516        }517518        /// 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.519        /// 520        /// # Permissions521        /// 522        /// * Anyone.523        /// 524        /// # Arguments525        /// 526        /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.527        /// 528        /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.529        /// 530        /// * token_prefix: UTF-8 string with token prefix.531        /// 532        /// * mode: [CollectionMode] collection type and type dependent data.533        // returns collection ID534        #[weight = T::WeightInfo::create_collection()]535        pub fn create_collection(origin,536                                 collection_name: Vec<u16>,537                                 collection_description: Vec<u16>,538                                 token_prefix: Vec<u8>,539                                 mode: CollectionMode) -> DispatchResult {540541            // Anyone can create a collection542            let who = ensure_signed(origin)?;543544            let decimal_points = match mode {545                CollectionMode::Fungible(points) => points,546                CollectionMode::ReFungible(points) => points,547                _ => 0548            };549550            // bound Total number of collections551            ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);552553            // check params554            ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);555556            let mut name = collection_name.to_vec();557            name.push(0);558            ensure!(name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);559560            let mut description = collection_description.to_vec();561            description.push(0);562            ensure!(name.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);563564            let mut prefix = token_prefix.to_vec();565            prefix.push(0);566            ensure!(prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);567568            // Generate next collection ID569            let next_id = CreatedCollectionCount::get()570                .checked_add(1)571                .ok_or(Error::<T>::NumOverflow)?;572573            // bound counter574            let total = CollectionCount::get()575                .checked_add(1)576                .ok_or(Error::<T>::NumOverflow)?;577578            CreatedCollectionCount::put(next_id);579            CollectionCount::put(total);580581            // Create new collection582            let new_collection = CollectionType {583                owner: who.clone(),584                name: name,585                mode: mode.clone(),586                mint_mode: false,587                access: AccessMode::Normal,588                description: description,589                decimal_points: decimal_points,590                token_prefix: prefix,591                offchain_schema: Vec::new(),592                schema_version: SchemaVersion::ImageURL,593                sponsor: T::AccountId::default(),594                unconfirmed_sponsor: T::AccountId::default(),595                variable_on_chain_schema: Vec::new(),596                const_on_chain_schema: Vec::new(),597                limits: CollectionLimits::default(),598            };599600            // Add new collection to map601            <Collection<T>>::insert(next_id, new_collection);602603            // call event604            Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));605606            Ok(())607        }608609        /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.610        /// 611        /// # Permissions612        /// 613        /// * Collection Owner.614        /// 615        /// # Arguments616        /// 617        /// * collection_id: collection to destroy.618        #[weight = T::WeightInfo::destroy_collection()]619        pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {620621            let sender = ensure_signed(origin)?;622            Self::check_owner_permissions(collection_id, sender)?;623624            <AddressTokens<T>>::remove_prefix(collection_id);625            <ApprovedList<T>>::remove_prefix(collection_id);626            <Balance<T>>::remove_prefix(collection_id);627            <ItemListIndex>::remove(collection_id);628            <AdminList<T>>::remove(collection_id);629            <Collection<T>>::remove(collection_id);630            <WhiteList<T>>::remove(collection_id);631632            <NftItemList<T>>::remove_prefix(collection_id);633            <FungibleItemList<T>>::remove_prefix(collection_id);634            <ReFungibleItemList<T>>::remove_prefix(collection_id);635636            <NftTransferBasket<T>>::remove_prefix(collection_id);637            <FungibleTransferBasket<T>>::remove_prefix(collection_id);638            <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);639640            if CollectionCount::get() > 0641            {642                // bound couter643                let total = CollectionCount::get()644                    .checked_sub(1)645                    .ok_or(Error::<T>::NumOverflow)?;646647                CollectionCount::put(total);648            }649650            Ok(())651        }652653        /// Add an address to white list.654        /// 655        /// # Permissions656        /// 657        /// * Collection Owner658        /// * Collection Admin659        /// 660        /// # Arguments661        /// 662        /// * collection_id.663        /// 664        /// * address.665        #[weight = T::WeightInfo::add_to_white_list()]666        pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{667668            let sender = ensure_signed(origin)?;669            Self::check_owner_or_admin_permissions(collection_id, sender)?;670671            let mut white_list_collection: Vec<T::AccountId>;672            if <WhiteList<T>>::contains_key(collection_id) {673                white_list_collection = <WhiteList<T>>::get(collection_id);674                if !white_list_collection.contains(&address.clone())675                {676                    white_list_collection.push(address.clone());677                }678            }679            else {680                white_list_collection = Vec::new();681                white_list_collection.push(address.clone());682            }683684            <WhiteList<T>>::insert(collection_id, white_list_collection);685            Ok(())686        }687688        /// Remove an address from white list.689        /// 690        /// # Permissions691        /// 692        /// * Collection Owner693        /// * Collection Admin694        /// 695        /// # Arguments696        /// 697        /// * collection_id.698        /// 699        /// * address.700        #[weight = T::WeightInfo::remove_from_white_list()]701        pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{702703            let sender = ensure_signed(origin)?;704            Self::check_owner_or_admin_permissions(collection_id, sender)?;705706            if <WhiteList<T>>::contains_key(collection_id) {707                let mut white_list_collection = <WhiteList<T>>::get(collection_id);708                if white_list_collection.contains(&address.clone())709                {710                    white_list_collection.retain(|i| *i != address.clone());711                    <WhiteList<T>>::insert(collection_id, white_list_collection);712                }713            }714715            Ok(())716        }717718        /// Toggle between normal and white list access for the methods with access for `Anyone`.719        /// 720        /// # Permissions721        /// 722        /// * Collection Owner.723        /// 724        /// # Arguments725        /// 726        /// * collection_id.727        /// 728        /// * mode: [AccessMode]729        #[weight = T::WeightInfo::set_public_access_mode()]730        pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult731        {732            let sender = ensure_signed(origin)?;733734            Self::check_owner_permissions(collection_id, sender)?;735            let mut target_collection = <Collection<T>>::get(collection_id);736            target_collection.access = mode;737            <Collection<T>>::insert(collection_id, target_collection);738739            Ok(())740        }741742        /// Allows Anyone to create tokens if:743        /// * White List is enabled, and744        /// * Address is added to white list, and745        /// * This method was called with True parameter746        /// 747        /// # Permissions748        /// * Collection Owner749        ///750        /// # Arguments751        /// 752        /// * collection_id.753        /// 754        /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.755        #[weight = T::WeightInfo::set_mint_permission()]756        pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult757        {758            let sender = ensure_signed(origin)?;759760            Self::check_owner_permissions(collection_id, sender)?;761            let mut target_collection = <Collection<T>>::get(collection_id);762            target_collection.mint_mode = mint_permission;763            <Collection<T>>::insert(collection_id, target_collection);764765            Ok(())766        }767768        /// Change the owner of the collection.769        /// 770        /// # Permissions771        /// 772        /// * Collection Owner.773        /// 774        /// # Arguments775        /// 776        /// * collection_id.777        /// 778        /// * new_owner.779        #[weight = T::WeightInfo::change_collection_owner()]780        pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {781782            let sender = ensure_signed(origin)?;783            Self::check_owner_permissions(collection_id, sender)?;784            let mut target_collection = <Collection<T>>::get(collection_id);785            target_collection.owner = new_owner;786            <Collection<T>>::insert(collection_id, target_collection);787788            Ok(())789        }790791        /// Adds an admin of the Collection.792        /// 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. 793        /// 794        /// # Permissions795        /// 796        /// * Collection Owner.797        /// * Collection Admin.798        /// 799        /// # Arguments800        /// 801        /// * collection_id: ID of the Collection to add admin for.802        /// 803        /// * new_admin_id: Address of new admin to add.804        #[weight = T::WeightInfo::add_collection_admin()]805        pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {806807            let sender = ensure_signed(origin)?;808            Self::check_owner_or_admin_permissions(collection_id, sender)?;809            let mut admin_arr: Vec<T::AccountId> = Vec::new();810811            if <AdminList<T>>::contains_key(collection_id)812            {813                admin_arr = <AdminList<T>>::get(collection_id);814                ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);815            }816817            // Number of collection admins818            ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);819820            admin_arr.push(new_admin_id);821            <AdminList<T>>::insert(collection_id, admin_arr);822823            Ok(())824        }825826        /// 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.827        ///828        /// # Permissions829        /// 830        /// * Collection Owner.831        /// * Collection Admin.832        /// 833        /// # Arguments834        /// 835        /// * collection_id: ID of the Collection to remove admin for.836        /// 837        /// * account_id: Address of admin to remove.838        #[weight = T::WeightInfo::remove_collection_admin()]839        pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {840841            let sender = ensure_signed(origin)?;842            Self::check_owner_or_admin_permissions(collection_id, sender)?;843844            if <AdminList<T>>::contains_key(collection_id)845            {846                let mut admin_arr = <AdminList<T>>::get(collection_id);847                admin_arr.retain(|i| *i != account_id);848                <AdminList<T>>::insert(collection_id, admin_arr);849            }850851            Ok(())852        }853854        /// # Permissions855        /// 856        /// * Collection Owner857        /// 858        /// # Arguments859        /// 860        /// * collection_id.861        /// 862        /// * new_sponsor.863        #[weight = T::WeightInfo::set_collection_sponsor()]864        pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {865866            let sender = ensure_signed(origin)?;867            ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);868869            let mut target_collection = <Collection<T>>::get(collection_id);870            ensure!(sender == target_collection.owner, Error::<T>::NoPermission);871872            target_collection.unconfirmed_sponsor = new_sponsor;873            <Collection<T>>::insert(collection_id, target_collection);874875            Ok(())876        }877878        /// # Permissions879        /// 880        /// * Sponsor.881        /// 882        /// # Arguments883        /// 884        /// * collection_id.885        #[weight = T::WeightInfo::confirm_sponsorship()]886        pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {887888            let sender = ensure_signed(origin)?;889            ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);890891            let mut target_collection = <Collection<T>>::get(collection_id);892            ensure!(sender == target_collection.unconfirmed_sponsor, Error::<T>::ConfirmUnsetSponsorFail);893894            target_collection.sponsor = target_collection.unconfirmed_sponsor;895            target_collection.unconfirmed_sponsor = T::AccountId::default();896            <Collection<T>>::insert(collection_id, target_collection);897898            Ok(())899        }900901        /// Switch back to pay-per-own-transaction model.902        ///903        /// # Permissions904        ///905        /// * Collection owner.906        /// 907        /// # Arguments908        /// 909        /// * collection_id.910        #[weight = T::WeightInfo::remove_collection_sponsor()]911        pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {912913            let sender = ensure_signed(origin)?;914            ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);915916            let mut target_collection = <Collection<T>>::get(collection_id);917            ensure!(sender == target_collection.owner, Error::<T>::NoPermission);918919            target_collection.sponsor = T::AccountId::default();920            <Collection<T>>::insert(collection_id, target_collection);921922            Ok(())923        }924925        /// This method creates a concrete instance of NFT Collection created with CreateCollection method.926        /// 927        /// # Permissions928        /// 929        /// * Collection Owner.930        /// * Collection Admin.931        /// * Anyone if932        ///     * White List is enabled, and933        ///     * Address is added to white list, and934        ///     * MintPermission is enabled (see SetMintPermission method)935        /// 936        /// # Arguments937        /// 938        /// * collection_id: ID of the collection.939        /// 940        /// * owner: Address, initial owner of the NFT.941        ///942        /// * data: Token data to store on chain.943        // #[weight =944        // (130_000_000 as Weight)945        // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))946        // .saturating_add(RocksDbWeight::get().reads(10 as Weight))947        // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]948949        #[weight = T::WeightInfo::create_item(data.len())]950        pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {951952            let sender = ensure_signed(origin)?;953954            Self::collection_exists(collection_id)?;955956            let target_collection = <Collection<T>>::get(collection_id);957958            Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;959            Self::validate_create_item_args(&target_collection, &data)?;960            Self::create_item_no_validation(collection_id, &target_collection, owner, data)?;961962            Ok(())963        }964965        /// This method creates multiple instances of NFT Collection created with CreateCollection method.966        /// 967        /// # Permissions968        /// 969        /// * Collection Owner.970        /// * Collection Admin.971        /// * Anyone if972        ///     * White List is enabled, and973        ///     * Address is added to white list, and974        ///     * MintPermission is enabled (see SetMintPermission method)975        /// 976        /// # Arguments977        /// 978        /// * collection_id: ID of the collection.979        /// 980        /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].981        /// 982        /// * owner: Address, initial owner of the NFT.983        #[weight = T::WeightInfo::create_item(items_data.into_iter()984                               .map(|data| { data.len() })985                               .sum())]986        pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {987988            ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);989            let sender = ensure_signed(origin)?;990991            Self::collection_exists(collection_id)?;992            let target_collection = <Collection<T>>::get(collection_id);993994            Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;995996            for data in &items_data {997                Self::validate_create_item_args(&target_collection, data)?;998            }999            for data in &items_data {1000                Self::create_item_no_validation(collection_id, &target_collection, owner.clone(), data.clone())?;1001            }10021003            Ok(())1004        }10051006        /// Destroys a concrete instance of NFT.1007        /// 1008        /// # Permissions1009        /// 1010        /// * Collection Owner.1011        /// * Collection Admin.1012        /// * Current NFT Owner.1013        /// 1014        /// # Arguments1015        /// 1016        /// * collection_id: ID of the collection.1017        /// 1018        /// * item_id: ID of NFT to burn.1019        #[weight = T::WeightInfo::burn_item()]1020        pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {10211022            let sender = ensure_signed(origin)?;1023            Self::collection_exists(collection_id)?;10241025            // Transfer permissions check1026            let target_collection = <Collection<T>>::get(collection_id);1027            ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1028                Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1029                Error::<T>::NoPermission);10301031            if target_collection.access == AccessMode::WhiteList {1032                Self::check_white_list(collection_id, &sender)?;1033            }10341035            match target_collection.mode1036            {1037                CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1038                CollectionMode::Fungible(_)  => Self::burn_fungible_item(collection_id, item_id)?,1039                CollectionMode::ReFungible(_)  => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,1040                _ => ()1041            };10421043            // call event1044            Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));10451046            Ok(())1047        }10481049        /// Change ownership of the token.1050        /// 1051        /// # Permissions1052        /// 1053        /// * Collection Owner1054        /// * Collection Admin1055        /// * Current NFT owner1056        ///1057        /// # Arguments1058        /// 1059        /// * recipient: Address of token recipient.1060        /// 1061        /// * collection_id.1062        /// 1063        /// * item_id: ID of the item1064        ///     * Non-Fungible Mode: Required.1065        ///     * Fungible Mode: Ignored.1066        ///     * Re-Fungible Mode: Required.1067        /// 1068        /// * value: Amount to transfer.1069        ///     * Non-Fungible Mode: Ignored1070        ///     * Fungible Mode: Must specify transferred amount1071        ///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1072        #[weight = T::WeightInfo::transfer()]1073        pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {10741075            let sender = ensure_signed(origin)?;1076            let target_collection = <Collection<T>>::get(collection_id);10771078            // Limits check1079            Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;10801081            // Transfer permissions check1082            ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1083                Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1084                Error::<T>::NoPermission);10851086            if target_collection.access == AccessMode::WhiteList {1087                Self::check_white_list(collection_id, &sender)?;1088                Self::check_white_list(collection_id, &recipient)?;1089            }10901091            match target_collection.mode1092            {1093                CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1094                CollectionMode::Fungible(_)  => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,1095                CollectionMode::ReFungible(_)  => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1096                _ => ()1097            };10981099            Ok(())1100        }11011102        /// Set, change, or remove approved address to transfer the ownership of the NFT.1103        /// 1104        /// # Permissions1105        /// 1106        /// * Collection Owner1107        /// * Collection Admin1108        /// * Current NFT owner1109        /// 1110        /// # Arguments1111        /// 1112        /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1113        /// 1114        /// * collection_id.1115        /// 1116        /// * item_id: ID of the item.1117        #[weight = T::WeightInfo::approve()]1118        pub fn approve(origin, approved: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {11191120            let sender = ensure_signed(origin)?;11211122            // Transfer permissions check1123            let target_collection = <Collection<T>>::get(collection_id);1124            ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1125                Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1126                Error::<T>::NoPermission);11271128            if target_collection.access == AccessMode::WhiteList {1129                Self::check_white_list(collection_id, &sender)?;1130                Self::check_white_list(collection_id, &approved)?;1131            }11321133            // amount param stub1134            let amount = 100000000;11351136            let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));1137            if list_exists {11381139                let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));1140                let item_contains = list.iter().any(|i| i.approved == approved);11411142                if !item_contains {1143                    list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1144                    <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1145                }1146            } else {11471148                let mut list = Vec::new();1149                list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1150                <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1151            }11521153            Ok(())1154        }1155        1156        /// 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.1157        /// 1158        /// # Permissions1159        /// * Collection Owner1160        /// * Collection Admin1161        /// * Current NFT owner1162        /// * Address approved by current NFT owner1163        /// 1164        /// # Arguments1165        /// 1166        /// * from: Address that owns token.1167        /// 1168        /// * recipient: Address of token recipient.1169        /// 1170        /// * collection_id.1171        /// 1172        /// * item_id: ID of the item.1173        /// 1174        /// * value: Amount to transfer.1175        #[weight = T::WeightInfo::transfer_from()]1176        pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {11771178            let sender = ensure_signed(origin)?;1179            let mut appoved_transfer = false;11801181            // Check approve1182            if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {1183                let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));1184                let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());1185                if opt_item.is_some()1186                {1187                    appoved_transfer = true;1188                    ensure!(opt_item.unwrap().amount >= value, Error::<T>::TokenValueNotEnough);1189                }1190            }11911192            let target_collection = <Collection<T>>::get(collection_id);11931194            // Limits check1195            Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;11961197            // Transfer permissions check         1198            ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1199            Error::<T>::NoPermission);12001201            if target_collection.access == AccessMode::WhiteList {1202                Self::check_white_list(collection_id, &sender)?;1203                Self::check_white_list(collection_id, &recipient)?;1204            }12051206            // remove approve1207            let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))1208                .into_iter().filter(|i| i.approved != sender.clone()).collect();1209            <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);121012111212            match target_collection.mode1213            {1214                CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1215                CollectionMode::Fungible(_)  => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,1216                CollectionMode::ReFungible(_)  => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1217                _ => ()1218            };12191220            Ok(())1221        }12221223        #[weight = 0]1224        pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {12251226            // let no_perm_mes = "You do not have permissions to modify this collection";1227            // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1228            // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1229            // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);12301231            // // on_nft_received  call12321233            // Self::transfer(origin, collection_id, item_id, new_owner)?;12341235            Ok(())1236        }12371238        /// Set off-chain data schema.1239        /// 1240        /// # Permissions1241        /// 1242        /// * Collection Owner1243        /// * Collection Admin1244        /// 1245        /// # Arguments1246        /// 1247        /// * collection_id.1248        /// 1249        /// * schema: String representing the offchain data schema.1250        #[weight = T::WeightInfo::set_variable_meta_data()]1251        pub fn set_variable_meta_data (1252            origin,1253            collection_id: CollectionId,1254            item_id: TokenId,1255            data: Vec<u8>1256        ) -> DispatchResult {1257            let sender = ensure_signed(origin)?;1258            1259            Self::collection_exists(collection_id)?;1260            1261            ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);12621263            // Modify permissions check1264            let target_collection = <Collection<T>>::get(collection_id);1265            ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1266                Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1267                Error::<T>::NoPermission);12681269            Self::item_exists(collection_id, item_id, &target_collection.mode)?;12701271            match target_collection.mode1272            {1273                CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1274                CollectionMode::ReFungible(_)  => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1275                CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1276                _ => fail!(Error::<T>::UnexpectedCollectionType)1277            };12781279            Ok(())1280        }1281 1282        /// Set schema standard1283        /// ImageURL1284        /// Unique1285        /// 1286        /// # Permissions1287        /// 1288        /// * Collection Owner1289        /// * Collection Admin1290        /// 1291        /// # Arguments1292        /// 1293        /// * collection_id.1294        /// 1295        /// * schema: SchemaVersion: enum1296        #[weight = 0]1297        pub fn set_schema_version(1298            origin,1299            collection_id: CollectionId,1300            version: SchemaVersion1301        ) -> DispatchResult {1302            let sender = ensure_signed(origin)?;1303            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;1304            let mut target_collection = <Collection<T>>::get(collection_id);1305            target_collection.schema_version = version;1306            <Collection<T>>::insert(collection_id, target_collection);13071308            Ok(())1309        }13101311        /// Set off-chain data schema.1312        /// 1313        /// # Permissions1314        /// 1315        /// * Collection Owner1316        /// * Collection Admin1317        /// 1318        /// # Arguments1319        /// 1320        /// * collection_id.1321        /// 1322        /// * schema: String representing the offchain data schema.1323        #[weight = T::WeightInfo::set_offchain_schema()]1324        pub fn set_offchain_schema(1325            origin,1326            collection_id: CollectionId,1327            schema: Vec<u8>1328        ) -> DispatchResult {1329            let sender = ensure_signed(origin)?;1330            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13311332            let mut target_collection = <Collection<T>>::get(collection_id);1333            target_collection.offchain_schema = schema;1334            <Collection<T>>::insert(collection_id, target_collection);13351336            Ok(())1337        }13381339        /// Set const on-chain data schema.1340        /// 1341        /// # Permissions1342        /// 1343        /// * Collection Owner1344        /// * Collection Admin1345        /// 1346        /// # Arguments1347        /// 1348        /// * collection_id.1349        /// 1350        /// * schema: String representing the const on-chain data schema.1351        #[weight = T::WeightInfo::set_const_on_chain_schema()]1352        pub fn set_const_on_chain_schema (1353            origin,1354            collection_id: CollectionId,1355            schema: Vec<u8>1356        ) -> DispatchResult {1357            let sender = ensure_signed(origin)?;1358            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13591360            let mut target_collection = <Collection<T>>::get(collection_id);1361            target_collection.const_on_chain_schema = schema;1362            <Collection<T>>::insert(collection_id, target_collection);13631364            Ok(())1365        }13661367        /// Set variable on-chain data schema.1368        /// 1369        /// # Permissions1370        /// 1371        /// * Collection Owner1372        /// * Collection Admin1373        /// 1374        /// # Arguments1375        /// 1376        /// * collection_id.1377        /// 1378        /// * schema: String representing the variable on-chain data schema.1379        #[weight = T::WeightInfo::set_const_on_chain_schema()]1380        pub fn set_variable_on_chain_schema (1381            origin,1382            collection_id: CollectionId,1383            schema: Vec<u8>1384        ) -> DispatchResult {1385            let sender = ensure_signed(origin)?;1386            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13871388            let mut target_collection = <Collection<T>>::get(collection_id);1389            target_collection.variable_on_chain_schema = schema;1390            <Collection<T>>::insert(collection_id, target_collection);13911392            Ok(())1393        }13941395        // Sudo permissions function1396        #[weight = 0]1397        pub fn set_chain_limits(1398            origin,1399            limits: ChainLimits1400        ) -> DispatchResult {1401            ensure_root(origin)?;1402            <ChainLimit>::put(limits);1403            Ok(())1404        }14051406        /// Enable smart contract self-sponsoring.1407        /// 1408        /// # Permissions1409        /// 1410        /// * Contract Owner1411        /// 1412        /// # Arguments1413        /// 1414        /// * contract address1415        /// * enable flag1416        /// 1417        #[weight = T::WeightInfo::enable_contract_sponsoring()]1418        pub fn enable_contract_sponsoring(1419            origin,1420            contract_address: T::AccountId,1421            enable: bool1422        ) -> DispatchResult {14231424            let sender = ensure_signed(origin)?;14251426            #[cfg(feature = "runtime-benchmarks")]1427            <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14281429            let mut is_owner = false;1430            if <ContractOwner<T>>::contains_key(contract_address.clone()) {1431                let owner = <ContractOwner<T>>::get(&contract_address);1432                is_owner = sender == owner;1433            }1434            ensure!(is_owner, Error::<T>::NoPermission);14351436            <ContractSelfSponsoring<T>>::insert(contract_address, enable);1437            Ok(())1438        }14391440        /// Set the rate limit for contract sponsoring to specified number of blocks.1441        /// 1442        /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1443        /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1444        /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1445        /// from contract endowment if there are at least B blocks between such transactions. 1446        /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1447        /// 1448        /// # Permissions1449        /// 1450        /// * Contract Owner1451        /// 1452        /// # Arguments1453        /// 1454        /// -`contract_address`: Address of the contract to sponsor1455        /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1456        /// 1457        #[weight = 0]1458        pub fn set_contract_sponsoring_rate_limit(1459            origin,1460            contract_address: T::AccountId,1461            rate_limit: T::BlockNumber1462        ) -> DispatchResult {1463            let sender = ensure_signed(origin)?;1464            let mut is_owner = false;1465            if <ContractOwner<T>>::contains_key(contract_address.clone()) {1466                let owner = <ContractOwner<T>>::get(&contract_address);1467                is_owner = sender == owner;1468            }1469            ensure!(is_owner, Error::<T>::NoPermission);14701471            <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1472            Ok(())1473        }14741475        #[weight = 0]1476        pub fn set_collection_limits(1477            origin,1478            collection_id: u32,1479            limits: CollectionLimits,1480        ) -> DispatchResult {1481            let sender = ensure_signed(origin)?;1482            Self::check_owner_permissions(collection_id, sender.clone())?;1483            let mut target_collection = <Collection<T>>::get(collection_id);1484            let chain_limits = ChainLimit::get();1485            let climits = target_collection.limits;14861487            // collection bounds1488            ensure!(limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1489                limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP,  1490                Error::<T>::CollectionLimitBoundsExceeded);14911492            // token_limit   check  prev1493            ensure!(climits.token_limit > limits.token_limit && 1494                limits.token_limit <= chain_limits.account_token_ownership_limit, 1495                Error::<T>::AccountTokenLimitExceeded);14961497            target_collection.limits = limits;1498            <Collection<T>>::insert(collection_id, target_collection);14991500            Ok(())1501        } 1502    }1503}15041505impl<T: Trait> Module<T> {15061507    fn is_correct_transfer(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, recipient: &T::AccountId) -> DispatchResult {15081509        // check token limit and account token limit1510        let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1511        ensure!(collection.limits.account_token_ownership_limit > account_items,  Error::<T>::AccountTokenLimitExceeded);1512        1513        Ok(())1514    }15151516    fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {15171518        // check token limit and account token limit1519        let total_items: u32 = ItemListIndex::get(collection_id);1520        let account_items: u32 = <AddressTokens<T>>::get(collection_id, owner).len() as u32;1521        ensure!(collection.limits.token_limit > total_items,  Error::<T>::CollectionTokenLimitExceeded);1522        ensure!(collection.limits.account_token_ownership_limit > account_items,  Error::<T>::AccountTokenLimitExceeded);15231524        if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1525            ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1526            Self::check_white_list(collection_id, owner)?;1527            Self::check_white_list(collection_id, sender)?;1528        }15291530        Ok(())1531    }15321533    fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1534        match target_collection.mode1535        {1536            CollectionMode::NFT => {1537                if let CreateItemData::NFT(data) = data {1538                    // check sizes1539                    ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1540                    ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1541                } else {1542                    fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1543                }1544            },1545            CollectionMode::Fungible(_) => {1546                if let CreateItemData::Fungible(_) = data {1547                } else {1548                    fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1549                }1550            },1551            CollectionMode::ReFungible(_) => {1552                if let CreateItemData::ReFungible(data) = data {15531554                    // check sizes1555                    ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1556                    ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1557                } else {1558                    fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1559                }1560            },1561            _ => { fail!(Error::<T>::UnexpectedCollectionType); }1562        };15631564        Ok(())1565    }15661567    fn create_item_no_validation(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1568        match data1569        {1570            CreateItemData::NFT(data) => {1571                let item = NftItemType {1572                    collection: collection_id,1573                    owner,1574                    const_data: data.const_data,1575                    variable_data: data.variable_data1576                };15771578                Self::add_nft_item(item)?;1579            },1580            CreateItemData::Fungible(_) => {1581                let item = FungibleItemType {1582                    collection: collection_id,1583                    owner,1584                    value: (10 as u128).pow(collection.decimal_points as u32)1585                };15861587                Self::add_fungible_item(item)?;1588            },1589            CreateItemData::ReFungible(data) => {1590                let mut owner_list = Vec::new();1591                let value = (10 as u128).pow(collection.decimal_points as u32);1592                owner_list.push(Ownership {owner: owner.clone(), fraction: value});15931594                let item = ReFungibleItemType {1595                    collection: collection_id,1596                    owner: owner_list,1597                    const_data: data.const_data,1598                    variable_data: data.variable_data1599                };16001601                Self::add_refungible_item(item)?;1602            }1603        };16041605        // call event1606        Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));16071608        Ok(())1609    }16101611    fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {1612        let current_index = <ItemListIndex>::get(item.collection)1613            .checked_add(1)1614            .ok_or(Error::<T>::NumOverflow)?;1615        let itemcopy = item.clone();1616        let owner = item.owner.clone();16171618        Self::add_token_index(item.collection, current_index, owner.clone())?;16191620        <ItemListIndex>::insert(item.collection, current_index);1621        <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);16221623        // Add current block1624        let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();1625        <FungibleTransferBasket<T>>::insert(item.collection, current_index, v);1626        1627        // Update balance1628        let new_balance = <Balance<T>>::get(item.collection, owner.clone())1629            .checked_add(item.value)1630            .ok_or(Error::<T>::NumOverflow)?;1631        <Balance<T>>::insert(item.collection, owner.clone(), new_balance);16321633        Ok(())1634    }16351636    fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1637        let current_index = <ItemListIndex>::get(item.collection)1638            .checked_add(1)1639            .ok_or(Error::<T>::NumOverflow)?;1640        let itemcopy = item.clone();16411642        let value = item.owner.first().unwrap().fraction;1643        let owner = item.owner.first().unwrap().owner.clone();16441645        Self::add_token_index(item.collection, current_index, owner.clone())?;16461647        <ItemListIndex>::insert(item.collection, current_index);1648        <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);16491650        // Add current block1651        let block_number: T::BlockNumber = 0.into();1652        <ReFungibleTransferBasket<T>>::insert(item.collection, current_index, block_number);16531654        // Update balance1655        let new_balance = <Balance<T>>::get(item.collection, owner.clone())1656            .checked_add(value)1657            .ok_or(Error::<T>::NumOverflow)?;1658        <Balance<T>>::insert(item.collection, owner.clone(), new_balance);16591660        Ok(())1661    }16621663    fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {1664        let current_index = <ItemListIndex>::get(item.collection)1665            .checked_add(1)1666            .ok_or(Error::<T>::NumOverflow)?;16671668        let item_owner = item.owner.clone();1669        let collection_id = item.collection.clone();1670        Self::add_token_index(collection_id, current_index, item.owner.clone())?;16711672        <ItemListIndex>::insert(collection_id, current_index);1673        <NftItemList<T>>::insert(collection_id, current_index, item);16741675        // Add current block1676        let block_number: T::BlockNumber = 0.into();1677        <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);16781679        // Update balance1680        let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1681            .checked_add(1)1682            .ok_or(Error::<T>::NumOverflow)?;1683        <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);16841685        Ok(())1686    }16871688    fn burn_refungible_item(1689        collection_id: CollectionId,1690        item_id: TokenId,1691        owner: T::AccountId,1692    ) -> DispatchResult {1693        ensure!(1694            <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1695            Error::<T>::TokenNotFound1696        );1697        let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1698        let item = collection1699            .owner1700            .iter()1701            .filter(|&i| i.owner == owner)1702            .next()1703            .unwrap();1704        Self::remove_token_index(collection_id, item_id, owner.clone())?;17051706        // remove approve list1707        <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));17081709        // update balance1710        let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1711            .checked_sub(item.fraction)1712            .ok_or(Error::<T>::NumOverflow)?;1713        <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);17141715        <ReFungibleItemList<T>>::remove(collection_id, item_id);17161717        Ok(())1718    }17191720    fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1721        ensure!(1722            <NftItemList<T>>::contains_key(collection_id, item_id),1723            Error::<T>::TokenNotFound1724        );1725        let item = <NftItemList<T>>::get(collection_id, item_id);1726        Self::remove_token_index(collection_id, item_id, item.owner.clone())?;17271728        // remove approve list1729        <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));17301731        // update balance1732        let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1733            .checked_sub(1)1734            .ok_or(Error::<T>::NumOverflow)?;1735        <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1736        <NftItemList<T>>::remove(collection_id, item_id);17371738        Ok(())1739    }17401741    fn burn_fungible_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1742        ensure!(1743            <FungibleItemList<T>>::contains_key(collection_id, item_id),1744            Error::<T>::TokenNotFound1745        );1746        let item = <FungibleItemList<T>>::get(collection_id, item_id);1747        Self::remove_token_index(collection_id, item_id, item.owner.clone())?;17481749        // remove approve list1750        <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));17511752        // update balance1753        let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1754            .checked_sub(item.value)1755            .ok_or(Error::<T>::NumOverflow)?;1756        <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);17571758        <FungibleItemList<T>>::remove(collection_id, item_id);17591760        Ok(())1761    }17621763    fn collection_exists(collection_id: CollectionId) -> DispatchResult {1764        ensure!(1765            <Collection<T>>::contains_key(collection_id),1766            Error::<T>::CollectionNotFound1767        );1768        Ok(())1769    }17701771    fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult {1772        Self::collection_exists(collection_id)?;17731774        let target_collection = <Collection<T>>::get(collection_id);1775        ensure!(1776            subject == target_collection.owner,1777            Error::<T>::NoPermission1778        );17791780        Ok(())1781    }17821783    fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool {1784        let target_collection = <Collection<T>>::get(collection_id);1785        let mut result: bool = subject == target_collection.owner;1786        let exists = <AdminList<T>>::contains_key(collection_id);17871788        if !result & exists {1789            if <AdminList<T>>::get(collection_id).contains(&subject) {1790                result = true1791            }1792        }17931794        result1795    }17961797    fn check_owner_or_admin_permissions(1798        collection_id: CollectionId,1799        subject: T::AccountId,1800    ) -> DispatchResult {1801        Self::collection_exists(collection_id)?;1802        let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());18031804        ensure!(1805            result,1806            Error::<T>::NoPermission1807        );1808        Ok(())1809    }18101811    fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool {1812        let target_collection = <Collection<T>>::get(collection_id);18131814        match target_collection.mode {1815            CollectionMode::NFT => {1816                <NftItemList<T>>::get(collection_id, item_id).owner == subject1817            }1818            CollectionMode::Fungible(_) => {1819                <FungibleItemList<T>>::get(collection_id, item_id).owner == subject1820            }1821            CollectionMode::ReFungible(_) => {1822                <ReFungibleItemList<T>>::get(collection_id, item_id)1823                    .owner1824                    .iter()1825                    .any(|i| i.owner == subject)1826            }1827            CollectionMode::Invalid => false,1828        }1829    }18301831    fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult {1832        let mes = Error::<T>::AddresNotInWhiteList;1833        ensure!(<WhiteList<T>>::contains_key(collection_id), mes);1834        let wl = <WhiteList<T>>::get(collection_id);1835        ensure!(wl.contains(address), mes);18361837        Ok(())1838    }18391840    fn transfer_fungible(1841        collection_id: CollectionId,1842        item_id: TokenId,1843        value: u128,1844        owner: T::AccountId,1845        new_owner: T::AccountId,1846    ) -> DispatchResult {1847        ensure!(1848            <FungibleItemList<T>>::contains_key(collection_id, item_id),1849            Error::<T>::TokenNotFound1850        );18511852        let full_item = <FungibleItemList<T>>::get(collection_id, item_id);1853        let amount = full_item.value;18541855        ensure!(amount >= value, Error::<T>::TokenValueTooLow);18561857        // update balance1858        let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())1859            .checked_sub(value)1860            .ok_or(Error::<T>::NumOverflow)?;1861        <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);18621863        let mut new_owner_account_id = 0;1864        let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());1865        if new_owner_items.len() > 0 {1866            new_owner_account_id = new_owner_items[0];1867        }18681869        // transfer1870        if amount == value && new_owner_account_id == 0 {1871            // change owner1872            // new owner do not have account1873            let mut new_full_item = full_item.clone();1874            new_full_item.owner = new_owner.clone();1875            <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);18761877            // update balance1878            let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1879                .checked_add(value)1880                .ok_or(Error::<T>::NumOverflow)?;1881            <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);18821883            // update index collection1884            Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;1885        } else {1886            let mut new_full_item = full_item.clone();1887            new_full_item.value -= value;18881889            // separate amount1890            if new_owner_account_id > 0 {1891                // new owner has account1892                let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);1893                item.value += value;18941895                // update balance1896                let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1897                    .checked_add(value)1898                    .ok_or(Error::<T>::NumOverflow)?;1899                <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);19001901                <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1902            } else {1903                // new owner do not have account1904                let item = FungibleItemType {1905                    collection: collection_id,1906                    owner: new_owner.clone(),1907                    value1908                };19091910                Self::add_fungible_item(item)?;1911            }19121913            if amount == value {1914                Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;19151916                // remove approve list1917                <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1918                <FungibleItemList<T>>::remove(collection_id, item_id);1919            }19201921            <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1922        }19231924        Ok(())1925    }19261927    fn transfer_refungible(1928        collection_id: CollectionId,1929        item_id: TokenId,1930        value: u128,1931        owner: T::AccountId,1932        new_owner: T::AccountId,1933    ) -> DispatchResult {1934        ensure!(1935            <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1936            Error::<T>::TokenNotFound1937        );19381939        let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1940        let item = full_item1941            .owner1942            .iter()1943            .filter(|i| i.owner == owner)1944            .next()1945            .ok_or(Error::<T>::NumOverflow)?;1946        let amount = item.fraction;19471948        ensure!(amount >= value, Error::<T>::TokenValueTooLow);19491950        // update balance1951        let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1952            .checked_sub(value)1953            .ok_or(Error::<T>::NumOverflow)?;1954        <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19551956        let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1957            .checked_add(value)1958            .ok_or(Error::<T>::NumOverflow)?;1959        <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);19601961        let old_owner = item.owner.clone();1962        let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);19631964        // transfer1965        if amount == value && !new_owner_has_account {1966            // change owner1967            // new owner do not have account1968            let mut new_full_item = full_item.clone();1969            new_full_item1970                .owner1971                .iter_mut()1972                .find(|i| i.owner == owner)1973                .unwrap()1974                .owner = new_owner.clone();1975            <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);19761977            // update index collection1978            Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1979        } else {1980            let mut new_full_item = full_item.clone();1981            new_full_item1982                .owner1983                .iter_mut()1984                .find(|i| i.owner == owner)1985                .unwrap()1986                .fraction -= value;19871988            // separate amount1989            if new_owner_has_account {1990                // new owner has account1991                new_full_item1992                    .owner1993                    .iter_mut()1994                    .find(|i| i.owner == new_owner)1995                    .unwrap()1996                    .fraction += value;1997            } else {1998                // new owner do not have account1999                new_full_item.owner.push(Ownership {2000                    owner: new_owner.clone(),2001                    fraction: value,2002                });2003                Self::add_token_index(collection_id, item_id, new_owner.clone())?;2004            }20052006            <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2007        }20082009        Ok(())2010    }20112012    fn transfer_nft(2013        collection_id: CollectionId,2014        item_id: TokenId,2015        sender: T::AccountId,2016        new_owner: T::AccountId,2017    ) -> DispatchResult {2018        ensure!(2019            <NftItemList<T>>::contains_key(collection_id, item_id),2020            Error::<T>::TokenNotFound2021        );20222023        let mut item = <NftItemList<T>>::get(collection_id, item_id);20242025        ensure!(2026            sender == item.owner,2027            Error::<T>::MustBeTokenOwner2028        );20292030        // update balance2031        let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())2032            .checked_sub(1)2033            .ok_or(Error::<T>::NumOverflow)?;2034        <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);20352036        let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())2037            .checked_add(1)2038            .ok_or(Error::<T>::NumOverflow)?;2039        <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);20402041        // change owner2042        let old_owner = item.owner.clone();2043        item.owner = new_owner.clone();2044        <NftItemList<T>>::insert(collection_id, item_id, item);20452046        // update index collection2047        Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;20482049        // reset approved list2050        <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));2051        Ok(())2052    }2053    2054    fn item_exists(2055        collection_id: CollectionId,2056        item_id: TokenId,2057        mode: &CollectionMode2058    ) -> DispatchResult {2059        match mode {2060            CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2061            CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2062            CollectionMode::Fungible(_) => ensure!(<FungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2063            _ => ()2064        };2065        2066        Ok(())2067    }20682069    fn set_re_fungible_variable_data(2070        collection_id: CollectionId,2071        item_id: TokenId,2072        data: Vec<u8>2073    ) -> DispatchResult {2074        let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);20752076        item.variable_data = data;20772078        <ReFungibleItemList<T>>::insert(collection_id, item_id, item);20792080        Ok(())2081    }20822083    fn set_nft_variable_data(2084        collection_id: CollectionId,2085        item_id: TokenId,2086        data: Vec<u8>2087    ) -> DispatchResult {2088        let mut item = <NftItemList<T>>::get(collection_id, item_id);2089        2090        item.variable_data = data;20912092        <NftItemList<T>>::insert(collection_id, item_id, item);2093        2094        Ok(())2095    }20962097    fn init_collection(item: &CollectionType<T::AccountId>) {2098        // check params2099        assert!(2100            item.decimal_points <= MAX_DECIMAL_POINTS,2101            "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2102        );2103        assert!(2104            item.name.len() <= 64,2105            "Collection name can not be longer than 63 char"2106        );2107        assert!(2108            item.name.len() <= 256,2109            "Collection description can not be longer than 255 char"2110        );2111        assert!(2112            item.token_prefix.len() <= 16,2113            "Token prefix can not be longer than 15 char"2114        );21152116        // Generate next collection ID2117        let next_id = CreatedCollectionCount::get()2118            .checked_add(1)2119            .unwrap();21202121        CreatedCollectionCount::put(next_id);2122    }21232124    fn init_nft_token(item: &NftItemType<T::AccountId>) {2125        let current_index = <ItemListIndex>::get(item.collection)2126            .checked_add(1)2127            .unwrap();21282129        let item_owner = item.owner.clone();2130        let collection_id = item.collection.clone();2131        Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();21322133        <ItemListIndex>::insert(collection_id, current_index);21342135        // Update balance2136        let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())2137            .checked_add(1)2138            .unwrap();2139        <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2140    }21412142    fn init_fungible_token(item: &FungibleItemType<T::AccountId>) {2143        let current_index = <ItemListIndex>::get(item.collection)2144            .checked_add(1)2145            .unwrap();2146        let owner = item.owner.clone();21472148        Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();21492150        <ItemListIndex>::insert(item.collection, current_index);21512152        // Update balance2153        let new_balance = <Balance<T>>::get(item.collection, owner.clone())2154            .checked_add(item.value)2155            .unwrap();2156        <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2157    }21582159    fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>) {2160        let current_index = <ItemListIndex>::get(item.collection)2161            .checked_add(1)2162            .unwrap();21632164        let value = item.owner.first().unwrap().fraction;2165        let owner = item.owner.first().unwrap().owner.clone();21662167        Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();21682169        <ItemListIndex>::insert(item.collection, current_index);21702171        // Update balance2172        let new_balance = <Balance<T>>::get(item.collection, owner.clone())2173            .checked_add(value)2174            .unwrap();2175        <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2176    }21772178    fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: T::AccountId) -> DispatchResult {21792180        // add to account limit2181        if <AccountItemCount<T>>::contains_key(owner.clone()) {21822183            // bound Owned tokens by a single address2184            let count = <AccountItemCount<T>>::get(owner.clone());2185            ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);21862187            <AccountItemCount<T>>::insert(owner.clone(), count2188                .checked_add(1)2189                .ok_or(Error::<T>::NumOverflow)?);2190        }2191        else {2192            <AccountItemCount<T>>::insert(owner.clone(), 1);2193        }21942195        let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2196        if list_exists {2197            let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2198            let item_contains = list.contains(&item_index.clone());21992200            if !item_contains {2201                list.push(item_index.clone());2202            }22032204            <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2205        } else {2206            let mut itm = Vec::new();2207            itm.push(item_index.clone());2208            <AddressTokens<T>>::insert(collection_id, owner, itm);2209            2210        }22112212        Ok(())2213    }22142215    fn remove_token_index(2216        collection_id: CollectionId,2217        item_index: TokenId,2218        owner: T::AccountId,2219    ) -> DispatchResult {22202221        // update counter2222        <AccountItemCount<T>>::insert(owner.clone(), 2223            <AccountItemCount<T>>::get(owner.clone())2224            .checked_sub(1)2225            .ok_or(Error::<T>::NumOverflow)?);222622272228        let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2229        if list_exists {2230            let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2231            let item_contains = list.contains(&item_index.clone());22322233            if item_contains {2234                list.retain(|&item| item != item_index);2235                <AddressTokens<T>>::insert(collection_id, owner, list);2236            }2237        }22382239        Ok(())2240    }22412242    fn move_token_index(2243        collection_id: CollectionId,2244        item_index: TokenId,2245        old_owner: T::AccountId,2246        new_owner: T::AccountId,2247    ) -> DispatchResult {2248        Self::remove_token_index(collection_id, item_index, old_owner)?;2249        Self::add_token_index(collection_id, item_index, new_owner)?;22502251        Ok(())2252    }2253}22542255////////////////////////////////////////////////////////////////////////////////////////////////////2256// Economic models2257// #region22582259/// Fee multiplier.2260pub type Multiplier = FixedU128;22612262type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2263    <T as system::Trait>::AccountId,2264>>::Balance;2265type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2266    <T as system::Trait>::AccountId,2267>>::NegativeImbalance;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: Trait + Send + Sync>(2273    #[codec(compact)] BalanceOf<T>2274);22752276impl<T: Trait + Send + Sync> sp_std::fmt::Debug2277    for ChargeTransactionPayment<T>2278{2279    #[cfg(feature = "std")]2280    fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2281        write!(f, "ChargeTransactionPayment<{:?}>", self.0)2282    }2283    #[cfg(not(feature = "std"))]2284    fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2285        Ok(())2286    }2287}22882289impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2290where2291    T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2292    BalanceOf<T>: Send + Sync + FixedPointOperand,2293{2294    /// utility constructor. Used only in client/factory code.2295    pub fn from(fee: BalanceOf<T>) -> Self {2296        Self(fee)2297    }22982299    pub fn traditional_fee(2300        len: usize,2301        info: &DispatchInfoOf<T::Call>,2302        tip: BalanceOf<T>,2303    ) -> BalanceOf<T>2304    where2305        T::Call: Dispatchable<Info = DispatchInfo>,2306    {2307        <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2308    }23092310	fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2311		let weight_saturation = T::MaximumBlockWeight::get() / info.weight.max(1);2312		let len_saturation = T::MaximumBlockLength::get() as u64 / (len as u64).max(1);2313		let coefficient: BalanceOf<T> = weight_saturation.min(len_saturation).saturated_into::<BalanceOf<T>>();2314		final_fee.saturating_mul(coefficient).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<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2324        let tip = self.0;23252326        // Set fee based on call type. Creating collection costs 1 Unique.2327        // All other transactions have traditional fees so far2328        // let fee = match call.is_sub_type() {2329        //     Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2330        //     _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2331        //                                                 // _ => <BalanceOf<T>>::from(100)2332        // };2333        let fee = Self::traditional_fee(len, info, tip);23342335        // Determine who is paying transaction fee based on ecnomic model2336        // Parse call to extract collection ID and access collection sponsor2337        let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2338            Some(Call::create_item(collection_id, _owner, _properties)) => {23392340                // check free create limit2341                if <Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)2342                {2343                    <Collection<T>>::get(collection_id).sponsor2344                } else {2345                    T::AccountId::default()2346                }2347            }2348            Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {2349                2350                let _collection_limits = <Collection<T>>::get(collection_id).limits;2351                let _collection_mode = <Collection<T>>::get(collection_id).mode;23522353                // sponsor timeout2354                let sponsor_transfer = match _collection_mode {2355                    CollectionMode::NFT => {23562357                        // get correct limit2358                        let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2359                            _collection_limits.sponsor_transfer_timeout2360                        } else {2361                            ChainLimit::get().nft_sponsor_transfer_timeout2362                        };23632364                        let basket = <NftTransferBasket<T>>::get(collection_id, _item_id);2365                        let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2366                        let limit_time = basket + limit.into();2367                        if block_number >= limit_time {2368                            <NftTransferBasket<T>>::insert(collection_id, _item_id, block_number);2369                            true2370                        }2371                        else {2372                            false2373                        }2374                    }2375                    CollectionMode::Fungible(_) => {23762377                        // get correct limit2378                        let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2379                            _collection_limits.sponsor_transfer_timeout2380                        } else {2381                            ChainLimit::get().fungible_sponsor_transfer_timeout2382                        };23832384                        let mut basket = <FungibleTransferBasket<T>>::get(collection_id, _item_id);2385                        let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2386                        if basket.iter().any(|i| i.address == _new_owner.clone())2387                        {2388                            let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone();2389                            let limit_time = item.start_block + limit.into();2390                            if block_number >= limit_time {2391                                basket.retain(|x| x.address == item.address);2392                                basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() });2393                                <FungibleTransferBasket<T>>::insert(collection_id, _item_id, basket);2394                                true2395                            }2396                            else {2397                                false2398                            }2399                        }2400                        else {2401                            basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()});2402                            true2403                        }2404                    }2405                    CollectionMode::ReFungible(_) => {24062407                        // get correct limit2408                        let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2409                            _collection_limits.sponsor_transfer_timeout2410                        } else {2411                            ChainLimit::get().refungible_sponsor_transfer_timeout2412                        };24132414                        let basket = <ReFungibleTransferBasket<T>>::get(collection_id, _item_id);2415                        let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2416                        let limit_time = basket + limit.into();2417                        if block_number >= limit_time {2418                            <ReFungibleTransferBasket<T>>::insert(collection_id, _item_id, block_number);2419                            true2420                        } else {2421                            false2422                        }2423                    }2424                    _ => {2425                        false2426                    },2427                };24282429                if !sponsor_transfer {2430                    T::AccountId::default()2431                } else {2432                    <Collection<T>>::get(collection_id).sponsor2433                }2434            }24352436            _ => T::AccountId::default(),2437        };24382439        // Sponsor smart contracts2440        sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {24412442            // On instantiation: set the contract owner2443            Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {24442445                let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2446                    code_hash,2447                    &data,2448                    &who,2449                );2450                <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());24512452                T::AccountId::default()2453            },24542455            // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2456            Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {24572458                let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());24592460                let mut sponsor_transfer = false;2461                if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2462                    let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2463                    let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2464                    let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2465                    let limit_time = last_tx_block + rate_limit;24662467                    if block_number >= limit_time {2468                        <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2469                        sponsor_transfer = true;2470                    }2471                } else {2472                    sponsor_transfer = false;2473                }2474               2475                2476                let mut sp = T::AccountId::default();2477                if sponsor_transfer {2478                    if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2479                        if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2480                            sp = called_contract;2481                        }2482                    }2483                }24842485                sp2486            },24872488            _ => sponsor,2489        };24902491        let mut who_pays_fee: T::AccountId = sponsor.clone();2492        if sponsor == T::AccountId::default() {2493            who_pays_fee = who.clone();2494        }24952496        // Only mess with balances if fee is not zero.2497        if fee.is_zero() {2498            return Ok((fee, None));2499        }25002501        match <T as transaction_payment::Trait>::Currency::withdraw(2502            &who_pays_fee,2503            fee,2504            if tip.is_zero() {2505                WithdrawReason::TransactionPayment.into()2506            } else {2507                WithdrawReason::TransactionPayment | WithdrawReason::Tip2508            },2509            ExistenceRequirement::KeepAlive,2510        ) {2511            Ok(imbalance) => Ok((fee, Some(imbalance))),2512            Err(_) => Err(InvalidTransaction::Payment.into()),2513        }2514    }2515}251625172518impl<T: Trait + Send + Sync> SignedExtension2519    for ChargeTransactionPayment<T>2520where2521    BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2522    T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2523{2524    const IDENTIFIER: &'static str = "ChargeTransactionPayment";2525    type AccountId = T::AccountId;2526    type Call = T::Call;2527    type AdditionalSigned = ();2528    type Pre = (2529        BalanceOf<T>,2530        Self::AccountId,2531        Option<NegativeImbalanceOf<T>>,2532        BalanceOf<T>,2533    );2534    fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2535        Ok(())2536    }25372538    fn validate(2539        &self,2540        who: &Self::AccountId,2541        call: &Self::Call,2542        info: &DispatchInfoOf<Self::Call>,2543        len: usize,2544    ) -> TransactionValidity {2545		let (fee, _) = self.withdraw_fee(who, call, info, len)?;2546		Ok(ValidTransaction {2547			priority: Self::get_priority(len, info, fee),2548			..Default::default()2549		})2550    }25512552    fn pre_dispatch(2553        self,2554        who: &Self::AccountId,2555        call: &Self::Call,2556        info: &DispatchInfoOf<Self::Call>,2557        len: usize,2558    ) -> Result<Self::Pre, TransactionValidityError> {2559        let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2560        Ok((self.0, who.clone(), imbalance, fee))2561    }25622563    fn post_dispatch(2564        pre: Self::Pre,2565        info: &DispatchInfoOf<Self::Call>,2566        post_info: &PostDispatchInfoOf<Self::Call>,2567        len: usize,2568        _result: &DispatchResult,2569    ) -> Result<(), TransactionValidityError> {2570        let (tip, who, imbalance, fee) = pre;2571        if let Some(payed) = imbalance {2572            let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2573                len as u32, info, post_info, tip,2574            );2575            let refund = fee.saturating_sub(actual_fee);2576            let actual_payment =2577                match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2578                    &who, refund,2579                ) {2580                    Ok(refund_imbalance) => {2581                        // The refund cannot be larger than the up front payed max weight.2582                        // `PostDispatchInfo::calc_unspent` guards against such a case.2583                        match payed.offset(refund_imbalance) {2584                            Ok(actual_payment) => actual_payment,2585                            Err(_) => return Err(InvalidTransaction::Payment.into()),2586                        }2587                    }2588                    // We do not recreate the account using the refund. The up front payment2589                    // is gone in that case.2590                    Err(_) => payed,2591                };2592            let imbalances = actual_payment.split(tip);2593            <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2594                Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2595            );2596        }2597        Ok(())2598    }2599}26002601// #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, WithdrawReason,24    },25    weights::{26        constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},27        DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,28        WeightToFeePolynomial,29    },30    IsSubType, 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 pallet_contracts::ContractAddressFor;45use sp_runtime::traits::StaticLookup;4647#[cfg(test)]48mod mock;4950#[cfg(test)]51mod tests;5253mod default_weights;5455pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;56pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;57pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;5859// Structs60// #region6162pub type CollectionId = u32;63pub type TokenId = u32;64pub type DecimalPoints = u8;6566#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]67#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]68pub enum CollectionMode {69    Invalid,70    NFT,71    // decimal points72    Fungible(DecimalPoints),73    // decimal points74    ReFungible(DecimalPoints),75}7677impl Into<u8> for CollectionMode {78    fn into(self) -> u8 {79        match self {80            CollectionMode::Invalid => 0,81            CollectionMode::NFT => 1,82            CollectionMode::Fungible(_) => 2,83            CollectionMode::ReFungible(_) => 3,84        }85    }86}8788#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]89#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]90pub enum AccessMode {91    Normal,92    WhiteList,93}94impl Default for AccessMode {95    fn default() -> Self {96        Self::Normal97    }98}99100impl Default for CollectionMode {101    fn default() -> Self {102        Self::Invalid103    }104}105106#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]107#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]108pub enum SchemaVersion {109    ImageURL,110    Unique,111}112impl Default for SchemaVersion {113    fn default() -> Self {114        Self::ImageURL115    }116}117118#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]119#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]120pub struct Ownership<AccountId> {121    pub owner: AccountId,122    pub fraction: u128,123}124125#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]126#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]127pub struct CollectionType<AccountId> {128    pub owner: AccountId,129    pub mode: CollectionMode,130    pub access: AccessMode,131    pub decimal_points: DecimalPoints,132    pub name: Vec<u16>,        // 64 include null escape char133    pub description: Vec<u16>, // 256 include null escape char134    pub token_prefix: Vec<u8>, // 16 include null escape char135    pub mint_mode: bool,136    pub offchain_schema: Vec<u8>,137    pub schema_version: SchemaVersion,138    pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender139    pub sponsor_confirmed: bool, // False if sponsor address has not yet confirmed sponsorship. True otherwise.140    pub limits: CollectionLimits, // Collection private restrictions 141    pub variable_on_chain_schema: Vec<u8>, //142    pub const_on_chain_schema: Vec<u8>, //143}144145#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]146#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]147pub struct NftItemType<AccountId> {148    pub collection: CollectionId,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<AccountId> {157    pub collection: CollectionId,158    pub owner: AccountId,159    pub value: u128,160}161162#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]163#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]164pub struct ReFungibleItemType<AccountId> {165    pub collection: CollectionId,166    pub owner: Vec<Ownership<AccountId>>,167    pub const_data: Vec<u8>,168    pub variable_data: Vec<u8>,169}170171#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]172#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]173pub struct ApprovePermissions<AccountId> {174    pub approved: AccountId,175    pub amount: u128,176}177178#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]179#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]180pub struct VestingItem<AccountId, Moment> {181    pub sender: AccountId,182    pub recipient: AccountId,183    pub collection_id: CollectionId,184    pub item_id: TokenId,185    pub amount: u64,186    pub vesting_date: Moment,187}188189#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]190#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]191pub struct BasketItem<AccountId, BlockNumber> {192    pub address: AccountId,193    pub start_block: BlockNumber,194}195196#[derive(Encode, Decode, Debug, Clone, PartialEq)]197#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]198pub struct CollectionLimits {199    pub account_token_ownership_limit: u32,200    pub sponsored_data_size: u32,201    pub token_limit: u32,202203    // Timeouts for item types in passed blocks204    pub sponsor_transfer_timeout: u32,205}206207impl Default for CollectionLimits {208    fn default() -> CollectionLimits {209        CollectionLimits { 210            account_token_ownership_limit: 10_000_000, 211            token_limit: u32::max_value(),212            sponsored_data_size: u32::max_value(), 213            sponsor_transfer_timeout: 14400 }214    }215}216217#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]218#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]219pub struct ChainLimits {220    pub collection_numbers_limit: u32,221    pub account_token_ownership_limit: u32,222    pub collections_admins_limit: u64,223    pub custom_data_limit: u32,224225    // Timeouts for item types in passed blocks226    pub nft_sponsor_transfer_timeout: u32,227    pub fungible_sponsor_transfer_timeout: u32,228    pub refungible_sponsor_transfer_timeout: u32,229}230231pub trait WeightInfo {232	fn create_collection() -> Weight;233	fn destroy_collection() -> Weight;234	fn add_to_white_list() -> Weight;235	fn remove_from_white_list() -> Weight;236    fn set_public_access_mode() -> Weight;237    fn set_mint_permission() -> Weight;238    fn change_collection_owner() -> Weight;239    fn add_collection_admin() -> Weight;240    fn remove_collection_admin() -> Weight;241    fn set_collection_sponsor() -> Weight;242    fn confirm_sponsorship() -> Weight;243    fn remove_collection_sponsor() -> Weight;244    fn create_item(s: usize) -> Weight;245    fn burn_item() -> Weight;246    fn transfer() -> Weight;247    fn approve() -> Weight;248    fn transfer_from() -> Weight;249    fn set_offchain_schema() -> Weight;250    fn set_const_on_chain_schema() -> Weight;251    fn set_variable_on_chain_schema() -> Weight;252    fn set_variable_meta_data() -> Weight;253    fn enable_contract_sponsoring() -> 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}267268#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]269#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]270pub struct CreateReFungibleData {271    pub const_data: Vec<u8>,272    pub variable_data: Vec<u8>,273}274275#[derive(Encode, Decode, Debug, Clone, PartialEq)]276#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]277pub enum CreateItemData {278    NFT(CreateNftData),279    Fungible(CreateFungibleData),280    ReFungible(CreateReFungibleData),281}282283impl CreateItemData {284    pub fn len(&self) -> usize {285        let len = match self {286            CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),287            CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),288            _ => 0289        };290        291        return len;292    }293}294295impl From<CreateNftData> for CreateItemData {296    fn from(item: CreateNftData) -> Self {297        CreateItemData::NFT(item)298    }299}300301impl From<CreateReFungibleData> for CreateItemData {302    fn from(item: CreateReFungibleData) -> Self {303        CreateItemData::ReFungible(item)304    }305}306307impl From<CreateFungibleData> for CreateItemData {308    fn from(item: CreateFungibleData) -> Self {309        CreateItemData::Fungible(item)310    }311}312313314decl_error! {315	/// Error for non-fungible-token module.316	pub enum Error for Module<T: Trait> {317        /// Total collections bound exceeded.318        TotalCollectionsLimitExceeded,319		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.320        CollectionDecimalPointLimitExceeded, 321        /// Collection name can not be longer than 63 char.322        CollectionNameLimitExceeded, 323        /// Collection description can not be longer than 255 char.324        CollectionDescriptionLimitExceeded, 325        /// Token prefix can not be longer than 15 char.326        CollectionTokenPrefixLimitExceeded,327        /// This collection does not exist.328        CollectionNotFound,329        /// Item not exists.330        TokenNotFound,331        /// Arithmetic calculation overflow.332        NumOverflow,       333        /// Account already has admin role.334        AlreadyAdmin,  335        /// You do not own this collection.336        NoPermission,337        /// This address is not set as sponsor, use setCollectionSponsor first.338        ConfirmUnsetSponsorFail,339        /// Collection is not in mint mode.340        PublicMintingNotAllowed,341        /// Sender parameter and item owner must be equal.342        MustBeTokenOwner,343        /// Item balance not enough.344        TokenValueTooLow,345        /// Size of item is too large.346        NftSizeLimitExceeded,347        /// No approve found348        ApproveNotFound,349        /// Requested value more than approved.350        TokenValueNotEnough,351        /// Only approved addresses can call this method.352        ApproveRequired,353        /// Address is not in white list.354        AddresNotInWhiteList,355        /// Number of collection admins bound exceeded.356        CollectionAdminsLimitExceeded,357        /// Owned tokens by a single address bound exceeded.358        AddressOwnershipLimitExceeded,359        /// Length of items properties must be greater than 0.360        EmptyArgument,361        /// const_data exceeded data limit.362        TokenConstDataLimitExceeded,363        /// variable_data exceeded data limit.364        TokenVariableDataLimitExceeded,365        /// Not NFT item data used to mint in NFT collection.366        NotNftDataUsedToMintNftCollectionToken,367        /// Not Fungible item data used to mint in Fungible collection.368        NotFungibleDataUsedToMintFungibleCollectionToken,369        /// Not Re Fungible item data used to mint in Re Fungible collection.370        NotReFungibleDataUsedToMintReFungibleCollectionToken,371        /// Unexpected collection type.372        UnexpectedCollectionType,373        /// Can't store metadata in fungible tokens.374        CantStoreMetadataInFungibleTokens,375        /// Collection token limit exceeded376        CollectionTokenLimitExceeded,377        /// Account token limit exceeded per collection378        AccountTokenLimitExceeded,379        /// Collection limit bounds per collection exceeded380        CollectionLimitBoundsExceeded381	}382}383384pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait {385    type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;386387    /// Weight information for extrinsics in this pallet.388	type WeightInfo: WeightInfo;389}390391#[cfg(feature = "runtime-benchmarks")]392mod benchmarking;393394// #endregion395396decl_storage! {397    trait Store for Module<T: Trait> as Nft {398399        // Private members400        NextCollectionID: CollectionId;401        CreatedCollectionCount: u32;402        ChainVersion: u64;403        ItemListIndex: map hasher(identity) CollectionId => TokenId;404405        // Chain limits struct406        pub ChainLimit get(fn chain_limit) config(): ChainLimits;407408        // Bound counters409        CollectionCount: u32;410        pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;411412        // Basic collections413        pub Collection get(fn collection) config(): map hasher(identity) CollectionId => CollectionType<T::AccountId>;414        pub AdminList get(fn admin_list_collection): map hasher(identity) CollectionId => Vec<T::AccountId>;415        pub WhiteList get(fn white_list): map hasher(identity) CollectionId => Vec<T::AccountId>;416417        /// Balance owner per collection map418        pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;419420        /// second parameter: item id + owner account id421        pub ApprovedList get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;422423        /// Item collections424        pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;425        pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => FungibleItemType<T::AccountId>;426        pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;427428        /// Index list429        pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;430431        /// Tokens transfer baskets432        pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;433        pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => Vec<BasketItem<T::AccountId, T::BlockNumber>>;434        pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;435436        // Contract Sponsorship and Ownership437        pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId;438        pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;439        pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;440        pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;441    }442    add_extra_genesis {443        build(|config: &GenesisConfig<T>| {444            // Modification of storage445            for (_num, _c) in &config.collection {446                <Module<T>>::init_collection(_c);447            }448449            for (_num, _q, _i) in &config.nft_item_id {450                <Module<T>>::init_nft_token(_i);451            }452453            for (_num, _q, _i) in &config.fungible_item_id {454                <Module<T>>::init_fungible_token(_i);455            }456457            for (_num, _q, _i) in &config.refungible_item_id {458                <Module<T>>::init_refungible_token(_i);459            }460        })461    }462}463464decl_event!(465    pub enum Event<T>466    where467        AccountId = <T as system::Trait>::AccountId,468    {469        /// New collection was created470        /// 471        /// # Arguments472        /// 473        /// * collection_id: Globally unique identifier of newly created collection.474        /// 475        /// * mode: [CollectionMode] converted into u8.476        /// 477        /// * account_id: Collection owner.478        Created(CollectionId, u8, AccountId),479480        /// New item was created.481        /// 482        /// # Arguments483        /// 484        /// * collection_id: Id of the collection where item was created.485        /// 486        /// * item_id: Id of an item. Unique within the collection.487        ItemCreated(CollectionId, TokenId),488489        /// Collection item was burned.490        /// 491        /// # Arguments492        /// 493        /// collection_id.494        /// 495        /// item_id: Identifier of burned NFT.496        ItemDestroyed(CollectionId, TokenId),497    }498);499500decl_module! {501    pub struct Module<T: Trait> for enum Call where origin: T::Origin {502503        fn deposit_event() = default;504        type Error = Error<T>;505506        fn on_initialize(now: T::BlockNumber) -> Weight {507508            if ChainVersion::get() < 2509            {510                let value = NextCollectionID::get();511                CreatedCollectionCount::put(value);512                ChainVersion::put(2);513            }514515            0516        }517518        /// 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.519        /// 520        /// # Permissions521        /// 522        /// * Anyone.523        /// 524        /// # Arguments525        /// 526        /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.527        /// 528        /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.529        /// 530        /// * token_prefix: UTF-8 string with token prefix.531        /// 532        /// * mode: [CollectionMode] collection type and type dependent data.533        // returns collection ID534        #[weight = T::WeightInfo::create_collection()]535        pub fn create_collection(origin,536                                 collection_name: Vec<u16>,537                                 collection_description: Vec<u16>,538                                 token_prefix: Vec<u8>,539                                 mode: CollectionMode) -> DispatchResult {540541            // Anyone can create a collection542            let who = ensure_signed(origin)?;543544            let decimal_points = match mode {545                CollectionMode::Fungible(points) => points,546                CollectionMode::ReFungible(points) => points,547                _ => 0548            };549550            // bound Total number of collections551            ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);552553            // check params554            ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);555556            let mut name = collection_name.to_vec();557            name.push(0);558            ensure!(name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);559560            let mut description = collection_description.to_vec();561            description.push(0);562            ensure!(name.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);563564            let mut prefix = token_prefix.to_vec();565            prefix.push(0);566            ensure!(prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);567568            // Generate next collection ID569            let next_id = CreatedCollectionCount::get()570                .checked_add(1)571                .ok_or(Error::<T>::NumOverflow)?;572573            // bound counter574            let total = CollectionCount::get()575                .checked_add(1)576                .ok_or(Error::<T>::NumOverflow)?;577578            CreatedCollectionCount::put(next_id);579            CollectionCount::put(total);580581            // Create new collection582            let new_collection = CollectionType {583                owner: who.clone(),584                name: name,585                mode: mode.clone(),586                mint_mode: false,587                access: AccessMode::Normal,588                description: description,589                decimal_points: decimal_points,590                token_prefix: prefix,591                offchain_schema: Vec::new(),592                schema_version: SchemaVersion::ImageURL,593                sponsor: T::AccountId::default(),594                sponsor_confirmed: false,595                variable_on_chain_schema: Vec::new(),596                const_on_chain_schema: Vec::new(),597                limits: CollectionLimits::default(),598            };599600            // Add new collection to map601            <Collection<T>>::insert(next_id, new_collection);602603            // call event604            Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));605606            Ok(())607        }608609        /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.610        /// 611        /// # Permissions612        /// 613        /// * Collection Owner.614        /// 615        /// # Arguments616        /// 617        /// * collection_id: collection to destroy.618        #[weight = T::WeightInfo::destroy_collection()]619        pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {620621            let sender = ensure_signed(origin)?;622            Self::check_owner_permissions(collection_id, sender)?;623624            <AddressTokens<T>>::remove_prefix(collection_id);625            <ApprovedList<T>>::remove_prefix(collection_id);626            <Balance<T>>::remove_prefix(collection_id);627            <ItemListIndex>::remove(collection_id);628            <AdminList<T>>::remove(collection_id);629            <Collection<T>>::remove(collection_id);630            <WhiteList<T>>::remove(collection_id);631632            <NftItemList<T>>::remove_prefix(collection_id);633            <FungibleItemList<T>>::remove_prefix(collection_id);634            <ReFungibleItemList<T>>::remove_prefix(collection_id);635636            <NftTransferBasket<T>>::remove_prefix(collection_id);637            <FungibleTransferBasket<T>>::remove_prefix(collection_id);638            <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);639640            if CollectionCount::get() > 0641            {642                // bound couter643                let total = CollectionCount::get()644                    .checked_sub(1)645                    .ok_or(Error::<T>::NumOverflow)?;646647                CollectionCount::put(total);648            }649650            Ok(())651        }652653        /// Add an address to white list.654        /// 655        /// # Permissions656        /// 657        /// * Collection Owner658        /// * Collection Admin659        /// 660        /// # Arguments661        /// 662        /// * collection_id.663        /// 664        /// * address.665        #[weight = T::WeightInfo::add_to_white_list()]666        pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{667668            let sender = ensure_signed(origin)?;669            Self::check_owner_or_admin_permissions(collection_id, sender)?;670671            let mut white_list_collection: Vec<T::AccountId>;672            if <WhiteList<T>>::contains_key(collection_id) {673                white_list_collection = <WhiteList<T>>::get(collection_id);674                if !white_list_collection.contains(&address.clone())675                {676                    white_list_collection.push(address.clone());677                }678            }679            else {680                white_list_collection = Vec::new();681                white_list_collection.push(address.clone());682            }683684            <WhiteList<T>>::insert(collection_id, white_list_collection);685            Ok(())686        }687688        /// Remove an address from white list.689        /// 690        /// # Permissions691        /// 692        /// * Collection Owner693        /// * Collection Admin694        /// 695        /// # Arguments696        /// 697        /// * collection_id.698        /// 699        /// * address.700        #[weight = T::WeightInfo::remove_from_white_list()]701        pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{702703            let sender = ensure_signed(origin)?;704            Self::check_owner_or_admin_permissions(collection_id, sender)?;705706            if <WhiteList<T>>::contains_key(collection_id) {707                let mut white_list_collection = <WhiteList<T>>::get(collection_id);708                if white_list_collection.contains(&address.clone())709                {710                    white_list_collection.retain(|i| *i != address.clone());711                    <WhiteList<T>>::insert(collection_id, white_list_collection);712                }713            }714715            Ok(())716        }717718        /// Toggle between normal and white list access for the methods with access for `Anyone`.719        /// 720        /// # Permissions721        /// 722        /// * Collection Owner.723        /// 724        /// # Arguments725        /// 726        /// * collection_id.727        /// 728        /// * mode: [AccessMode]729        #[weight = T::WeightInfo::set_public_access_mode()]730        pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult731        {732            let sender = ensure_signed(origin)?;733734            Self::check_owner_permissions(collection_id, sender)?;735            let mut target_collection = <Collection<T>>::get(collection_id);736            target_collection.access = mode;737            <Collection<T>>::insert(collection_id, target_collection);738739            Ok(())740        }741742        /// Allows Anyone to create tokens if:743        /// * White List is enabled, and744        /// * Address is added to white list, and745        /// * This method was called with True parameter746        /// 747        /// # Permissions748        /// * Collection Owner749        ///750        /// # Arguments751        /// 752        /// * collection_id.753        /// 754        /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.755        #[weight = T::WeightInfo::set_mint_permission()]756        pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult757        {758            let sender = ensure_signed(origin)?;759760            Self::check_owner_permissions(collection_id, sender)?;761            let mut target_collection = <Collection<T>>::get(collection_id);762            target_collection.mint_mode = mint_permission;763            <Collection<T>>::insert(collection_id, target_collection);764765            Ok(())766        }767768        /// Change the owner of the collection.769        /// 770        /// # Permissions771        /// 772        /// * Collection Owner.773        /// 774        /// # Arguments775        /// 776        /// * collection_id.777        /// 778        /// * new_owner.779        #[weight = T::WeightInfo::change_collection_owner()]780        pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {781782            let sender = ensure_signed(origin)?;783            Self::check_owner_permissions(collection_id, sender)?;784            let mut target_collection = <Collection<T>>::get(collection_id);785            target_collection.owner = new_owner;786            <Collection<T>>::insert(collection_id, target_collection);787788            Ok(())789        }790791        /// Adds an admin of the Collection.792        /// 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. 793        /// 794        /// # Permissions795        /// 796        /// * Collection Owner.797        /// * Collection Admin.798        /// 799        /// # Arguments800        /// 801        /// * collection_id: ID of the Collection to add admin for.802        /// 803        /// * new_admin_id: Address of new admin to add.804        #[weight = T::WeightInfo::add_collection_admin()]805        pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {806807            let sender = ensure_signed(origin)?;808            Self::check_owner_or_admin_permissions(collection_id, sender)?;809            let mut admin_arr: Vec<T::AccountId> = Vec::new();810811            if <AdminList<T>>::contains_key(collection_id)812            {813                admin_arr = <AdminList<T>>::get(collection_id);814                ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);815            }816817            // Number of collection admins818            ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);819820            admin_arr.push(new_admin_id);821            <AdminList<T>>::insert(collection_id, admin_arr);822823            Ok(())824        }825826        /// 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.827        ///828        /// # Permissions829        /// 830        /// * Collection Owner.831        /// * Collection Admin.832        /// 833        /// # Arguments834        /// 835        /// * collection_id: ID of the Collection to remove admin for.836        /// 837        /// * account_id: Address of admin to remove.838        #[weight = T::WeightInfo::remove_collection_admin()]839        pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {840841            let sender = ensure_signed(origin)?;842            Self::check_owner_or_admin_permissions(collection_id, sender)?;843844            if <AdminList<T>>::contains_key(collection_id)845            {846                let mut admin_arr = <AdminList<T>>::get(collection_id);847                admin_arr.retain(|i| *i != account_id);848                <AdminList<T>>::insert(collection_id, admin_arr);849            }850851            Ok(())852        }853854        /// # Permissions855        /// 856        /// * Collection Owner857        /// 858        /// # Arguments859        /// 860        /// * collection_id.861        /// 862        /// * new_sponsor.863        #[weight = T::WeightInfo::set_collection_sponsor()]864        pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {865866            let sender = ensure_signed(origin)?;867            ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);868869            let mut target_collection = <Collection<T>>::get(collection_id);870            ensure!(sender == target_collection.owner, Error::<T>::NoPermission);871872            target_collection.sponsor = new_sponsor;873            target_collection.sponsor_confirmed = false;874            <Collection<T>>::insert(collection_id, target_collection);875876            Ok(())877        }878879        /// # Permissions880        /// 881        /// * Sponsor.882        /// 883        /// # Arguments884        /// 885        /// * collection_id.886        #[weight = T::WeightInfo::confirm_sponsorship()]887        pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {888889            let sender = ensure_signed(origin)?;890            ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);891892            let mut target_collection = <Collection<T>>::get(collection_id);893            ensure!(sender == target_collection.sponsor, Error::<T>::ConfirmUnsetSponsorFail);894895            target_collection.sponsor_confirmed = true;896            <Collection<T>>::insert(collection_id, target_collection);897898            Ok(())899        }900901        /// Switch back to pay-per-own-transaction model.902        ///903        /// # Permissions904        ///905        /// * Collection owner.906        /// 907        /// # Arguments908        /// 909        /// * collection_id.910        #[weight = T::WeightInfo::remove_collection_sponsor()]911        pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {912913            let sender = ensure_signed(origin)?;914            ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);915916            let mut target_collection = <Collection<T>>::get(collection_id);917            ensure!(sender == target_collection.owner, Error::<T>::NoPermission);918919            target_collection.sponsor = T::AccountId::default();920            target_collection.sponsor_confirmed = false;921            <Collection<T>>::insert(collection_id, target_collection);922923            Ok(())924        }925926        /// This method creates a concrete instance of NFT Collection created with CreateCollection method.927        /// 928        /// # Permissions929        /// 930        /// * Collection Owner.931        /// * Collection Admin.932        /// * Anyone if933        ///     * White List is enabled, and934        ///     * Address is added to white list, and935        ///     * MintPermission is enabled (see SetMintPermission method)936        /// 937        /// # Arguments938        /// 939        /// * collection_id: ID of the collection.940        /// 941        /// * owner: Address, initial owner of the NFT.942        ///943        /// * data: Token data to store on chain.944        // #[weight =945        // (130_000_000 as Weight)946        // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))947        // .saturating_add(RocksDbWeight::get().reads(10 as Weight))948        // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]949950        #[weight = T::WeightInfo::create_item(data.len())]951        pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {952953            let sender = ensure_signed(origin)?;954955            Self::collection_exists(collection_id)?;956957            let target_collection = <Collection<T>>::get(collection_id);958959            Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;960            Self::validate_create_item_args(&target_collection, &data)?;961            Self::create_item_no_validation(collection_id, &target_collection, owner, data)?;962963            Ok(())964        }965966        /// This method creates multiple instances of NFT Collection created with CreateCollection method.967        /// 968        /// # Permissions969        /// 970        /// * Collection Owner.971        /// * Collection Admin.972        /// * Anyone if973        ///     * White List is enabled, and974        ///     * Address is added to white list, and975        ///     * MintPermission is enabled (see SetMintPermission method)976        /// 977        /// # Arguments978        /// 979        /// * collection_id: ID of the collection.980        /// 981        /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].982        /// 983        /// * owner: Address, initial owner of the NFT.984        #[weight = T::WeightInfo::create_item(items_data.into_iter()985                               .map(|data| { data.len() })986                               .sum())]987        pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {988989            ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);990            let sender = ensure_signed(origin)?;991992            Self::collection_exists(collection_id)?;993            let target_collection = <Collection<T>>::get(collection_id);994995            Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;996997            for data in &items_data {998                Self::validate_create_item_args(&target_collection, data)?;999            }1000            for data in &items_data {1001                Self::create_item_no_validation(collection_id, &target_collection, owner.clone(), data.clone())?;1002            }10031004            Ok(())1005        }10061007        /// Destroys a concrete instance of NFT.1008        /// 1009        /// # Permissions1010        /// 1011        /// * Collection Owner.1012        /// * Collection Admin.1013        /// * Current NFT Owner.1014        /// 1015        /// # Arguments1016        /// 1017        /// * collection_id: ID of the collection.1018        /// 1019        /// * item_id: ID of NFT to burn.1020        #[weight = T::WeightInfo::burn_item()]1021        pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {10221023            let sender = ensure_signed(origin)?;1024            Self::collection_exists(collection_id)?;10251026            // Transfer permissions check1027            let target_collection = <Collection<T>>::get(collection_id);1028            ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1029                Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1030                Error::<T>::NoPermission);10311032            if target_collection.access == AccessMode::WhiteList {1033                Self::check_white_list(collection_id, &sender)?;1034            }10351036            match target_collection.mode1037            {1038                CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1039                CollectionMode::Fungible(_)  => Self::burn_fungible_item(collection_id, item_id)?,1040                CollectionMode::ReFungible(_)  => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,1041                _ => ()1042            };10431044            // call event1045            Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));10461047            Ok(())1048        }10491050        /// Change ownership of the token.1051        /// 1052        /// # Permissions1053        /// 1054        /// * Collection Owner1055        /// * Collection Admin1056        /// * Current NFT owner1057        ///1058        /// # Arguments1059        /// 1060        /// * recipient: Address of token recipient.1061        /// 1062        /// * collection_id.1063        /// 1064        /// * item_id: ID of the item1065        ///     * Non-Fungible Mode: Required.1066        ///     * Fungible Mode: Ignored.1067        ///     * Re-Fungible Mode: Required.1068        /// 1069        /// * value: Amount to transfer.1070        ///     * Non-Fungible Mode: Ignored1071        ///     * Fungible Mode: Must specify transferred amount1072        ///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1073        #[weight = T::WeightInfo::transfer()]1074        pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {10751076            let sender = ensure_signed(origin)?;1077            let target_collection = <Collection<T>>::get(collection_id);10781079            // Limits check1080            Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;10811082            // Transfer permissions check1083            ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1084                Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1085                Error::<T>::NoPermission);10861087            if target_collection.access == AccessMode::WhiteList {1088                Self::check_white_list(collection_id, &sender)?;1089                Self::check_white_list(collection_id, &recipient)?;1090            }10911092            match target_collection.mode1093            {1094                CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1095                CollectionMode::Fungible(_)  => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,1096                CollectionMode::ReFungible(_)  => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1097                _ => ()1098            };10991100            Ok(())1101        }11021103        /// Set, change, or remove approved address to transfer the ownership of the NFT.1104        /// 1105        /// # Permissions1106        /// 1107        /// * Collection Owner1108        /// * Collection Admin1109        /// * Current NFT owner1110        /// 1111        /// # Arguments1112        /// 1113        /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1114        /// 1115        /// * collection_id.1116        /// 1117        /// * item_id: ID of the item.1118        #[weight = T::WeightInfo::approve()]1119        pub fn approve(origin, approved: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {11201121            let sender = ensure_signed(origin)?;11221123            // Transfer permissions check1124            let target_collection = <Collection<T>>::get(collection_id);1125            ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1126                Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1127                Error::<T>::NoPermission);11281129            if target_collection.access == AccessMode::WhiteList {1130                Self::check_white_list(collection_id, &sender)?;1131                Self::check_white_list(collection_id, &approved)?;1132            }11331134            // amount param stub1135            let amount = 100000000;11361137            let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));1138            if list_exists {11391140                let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));1141                let item_contains = list.iter().any(|i| i.approved == approved);11421143                if !item_contains {1144                    list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1145                    <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1146                }1147            } else {11481149                let mut list = Vec::new();1150                list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1151                <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1152            }11531154            Ok(())1155        }1156        1157        /// 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.1158        /// 1159        /// # Permissions1160        /// * Collection Owner1161        /// * Collection Admin1162        /// * Current NFT owner1163        /// * Address approved by current NFT owner1164        /// 1165        /// # Arguments1166        /// 1167        /// * from: Address that owns token.1168        /// 1169        /// * recipient: Address of token recipient.1170        /// 1171        /// * collection_id.1172        /// 1173        /// * item_id: ID of the item.1174        /// 1175        /// * value: Amount to transfer.1176        #[weight = T::WeightInfo::transfer_from()]1177        pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {11781179            let sender = ensure_signed(origin)?;1180            let mut appoved_transfer = false;11811182            // Check approve1183            if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {1184                let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));1185                let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());1186                if opt_item.is_some()1187                {1188                    appoved_transfer = true;1189                    ensure!(opt_item.unwrap().amount >= value, Error::<T>::TokenValueNotEnough);1190                }1191            }11921193            let target_collection = <Collection<T>>::get(collection_id);11941195            // Limits check1196            Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;11971198            // Transfer permissions check         1199            ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1200            Error::<T>::NoPermission);12011202            if target_collection.access == AccessMode::WhiteList {1203                Self::check_white_list(collection_id, &sender)?;1204                Self::check_white_list(collection_id, &recipient)?;1205            }12061207            // remove approve1208            let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))1209                .into_iter().filter(|i| i.approved != sender.clone()).collect();1210            <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);121112121213            match target_collection.mode1214            {1215                CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1216                CollectionMode::Fungible(_)  => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,1217                CollectionMode::ReFungible(_)  => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1218                _ => ()1219            };12201221            Ok(())1222        }12231224        #[weight = 0]1225        pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {12261227            // let no_perm_mes = "You do not have permissions to modify this collection";1228            // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1229            // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1230            // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);12311232            // // on_nft_received  call12331234            // Self::transfer(origin, collection_id, item_id, new_owner)?;12351236            Ok(())1237        }12381239        /// Set off-chain data schema.1240        /// 1241        /// # Permissions1242        /// 1243        /// * Collection Owner1244        /// * Collection Admin1245        /// 1246        /// # Arguments1247        /// 1248        /// * collection_id.1249        /// 1250        /// * schema: String representing the offchain data schema.1251        #[weight = T::WeightInfo::set_variable_meta_data()]1252        pub fn set_variable_meta_data (1253            origin,1254            collection_id: CollectionId,1255            item_id: TokenId,1256            data: Vec<u8>1257        ) -> DispatchResult {1258            let sender = ensure_signed(origin)?;1259            1260            Self::collection_exists(collection_id)?;1261            1262            ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);12631264            // Modify permissions check1265            let target_collection = <Collection<T>>::get(collection_id);1266            ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1267                Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1268                Error::<T>::NoPermission);12691270            Self::item_exists(collection_id, item_id, &target_collection.mode)?;12711272            match target_collection.mode1273            {1274                CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1275                CollectionMode::ReFungible(_)  => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1276                CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1277                _ => fail!(Error::<T>::UnexpectedCollectionType)1278            };12791280            Ok(())1281        }1282 1283        /// Set schema standard1284        /// ImageURL1285        /// Unique1286        /// 1287        /// # Permissions1288        /// 1289        /// * Collection Owner1290        /// * Collection Admin1291        /// 1292        /// # Arguments1293        /// 1294        /// * collection_id.1295        /// 1296        /// * schema: SchemaVersion: enum1297        #[weight = 0]1298        pub fn set_schema_version(1299            origin,1300            collection_id: CollectionId,1301            version: SchemaVersion1302        ) -> DispatchResult {1303            let sender = ensure_signed(origin)?;1304            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;1305            let mut target_collection = <Collection<T>>::get(collection_id);1306            target_collection.schema_version = version;1307            <Collection<T>>::insert(collection_id, target_collection);13081309            Ok(())1310        }13111312        /// Set off-chain data schema.1313        /// 1314        /// # Permissions1315        /// 1316        /// * Collection Owner1317        /// * Collection Admin1318        /// 1319        /// # Arguments1320        /// 1321        /// * collection_id.1322        /// 1323        /// * schema: String representing the offchain data schema.1324        #[weight = T::WeightInfo::set_offchain_schema()]1325        pub fn set_offchain_schema(1326            origin,1327            collection_id: CollectionId,1328            schema: Vec<u8>1329        ) -> DispatchResult {1330            let sender = ensure_signed(origin)?;1331            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13321333            let mut target_collection = <Collection<T>>::get(collection_id);1334            target_collection.offchain_schema = schema;1335            <Collection<T>>::insert(collection_id, target_collection);13361337            Ok(())1338        }13391340        /// Set const on-chain data schema.1341        /// 1342        /// # Permissions1343        /// 1344        /// * Collection Owner1345        /// * Collection Admin1346        /// 1347        /// # Arguments1348        /// 1349        /// * collection_id.1350        /// 1351        /// * schema: String representing the const on-chain data schema.1352        #[weight = T::WeightInfo::set_const_on_chain_schema()]1353        pub fn set_const_on_chain_schema (1354            origin,1355            collection_id: CollectionId,1356            schema: Vec<u8>1357        ) -> DispatchResult {1358            let sender = ensure_signed(origin)?;1359            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13601361            let mut target_collection = <Collection<T>>::get(collection_id);1362            target_collection.const_on_chain_schema = schema;1363            <Collection<T>>::insert(collection_id, target_collection);13641365            Ok(())1366        }13671368        /// Set variable on-chain data schema.1369        /// 1370        /// # Permissions1371        /// 1372        /// * Collection Owner1373        /// * Collection Admin1374        /// 1375        /// # Arguments1376        /// 1377        /// * collection_id.1378        /// 1379        /// * schema: String representing the variable on-chain data schema.1380        #[weight = T::WeightInfo::set_const_on_chain_schema()]1381        pub fn set_variable_on_chain_schema (1382            origin,1383            collection_id: CollectionId,1384            schema: Vec<u8>1385        ) -> DispatchResult {1386            let sender = ensure_signed(origin)?;1387            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13881389            let mut target_collection = <Collection<T>>::get(collection_id);1390            target_collection.variable_on_chain_schema = schema;1391            <Collection<T>>::insert(collection_id, target_collection);13921393            Ok(())1394        }13951396        // Sudo permissions function1397        #[weight = 0]1398        pub fn set_chain_limits(1399            origin,1400            limits: ChainLimits1401        ) -> DispatchResult {1402            ensure_root(origin)?;1403            <ChainLimit>::put(limits);1404            Ok(())1405        }14061407        /// Enable smart contract self-sponsoring.1408        /// 1409        /// # Permissions1410        /// 1411        /// * Contract Owner1412        /// 1413        /// # Arguments1414        /// 1415        /// * contract address1416        /// * enable flag1417        /// 1418        #[weight = T::WeightInfo::enable_contract_sponsoring()]1419        pub fn enable_contract_sponsoring(1420            origin,1421            contract_address: T::AccountId,1422            enable: bool1423        ) -> DispatchResult {14241425            let sender = ensure_signed(origin)?;14261427            #[cfg(feature = "runtime-benchmarks")]1428            <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14291430            let mut is_owner = false;1431            if <ContractOwner<T>>::contains_key(contract_address.clone()) {1432                let owner = <ContractOwner<T>>::get(&contract_address);1433                is_owner = sender == owner;1434            }1435            ensure!(is_owner, Error::<T>::NoPermission);14361437            <ContractSelfSponsoring<T>>::insert(contract_address, enable);1438            Ok(())1439        }14401441        /// Set the rate limit for contract sponsoring to specified number of blocks.1442        /// 1443        /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1444        /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1445        /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1446        /// from contract endowment if there are at least B blocks between such transactions. 1447        /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1448        /// 1449        /// # Permissions1450        /// 1451        /// * Contract Owner1452        /// 1453        /// # Arguments1454        /// 1455        /// -`contract_address`: Address of the contract to sponsor1456        /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1457        /// 1458        #[weight = 0]1459        pub fn set_contract_sponsoring_rate_limit(1460            origin,1461            contract_address: T::AccountId,1462            rate_limit: T::BlockNumber1463        ) -> DispatchResult {1464            let sender = ensure_signed(origin)?;1465            let mut is_owner = false;1466            if <ContractOwner<T>>::contains_key(contract_address.clone()) {1467                let owner = <ContractOwner<T>>::get(&contract_address);1468                is_owner = sender == owner;1469            }1470            ensure!(is_owner, Error::<T>::NoPermission);14711472            <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1473            Ok(())1474        }14751476        #[weight = 0]1477        pub fn set_collection_limits(1478            origin,1479            collection_id: u32,1480            limits: CollectionLimits,1481        ) -> DispatchResult {1482            let sender = ensure_signed(origin)?;1483            Self::check_owner_permissions(collection_id, sender.clone())?;1484            let mut target_collection = <Collection<T>>::get(collection_id);1485            let chain_limits = ChainLimit::get();1486            let climits = target_collection.limits;14871488            // collection bounds1489            ensure!(limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1490                limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP,  1491                Error::<T>::CollectionLimitBoundsExceeded);14921493            // token_limit   check  prev1494            ensure!(climits.token_limit > limits.token_limit && 1495                limits.token_limit <= chain_limits.account_token_ownership_limit, 1496                Error::<T>::AccountTokenLimitExceeded);14971498            target_collection.limits = limits;1499            <Collection<T>>::insert(collection_id, target_collection);15001501            Ok(())1502        } 1503    }1504}15051506impl<T: Trait> Module<T> {15071508    fn is_correct_transfer(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, recipient: &T::AccountId) -> DispatchResult {15091510        // check token limit and account token limit1511        let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1512        ensure!(collection.limits.account_token_ownership_limit > account_items,  Error::<T>::AccountTokenLimitExceeded);1513        1514        Ok(())1515    }15161517    fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {15181519        // check token limit and account token limit1520        let total_items: u32 = ItemListIndex::get(collection_id);1521        let account_items: u32 = <AddressTokens<T>>::get(collection_id, owner).len() as u32;1522        ensure!(collection.limits.token_limit > total_items,  Error::<T>::CollectionTokenLimitExceeded);1523        ensure!(collection.limits.account_token_ownership_limit > account_items,  Error::<T>::AccountTokenLimitExceeded);15241525        if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1526            ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1527            Self::check_white_list(collection_id, owner)?;1528            Self::check_white_list(collection_id, sender)?;1529        }15301531        Ok(())1532    }15331534    fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1535        match target_collection.mode1536        {1537            CollectionMode::NFT => {1538                if let CreateItemData::NFT(data) = data {1539                    // check sizes1540                    ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1541                    ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1542                } else {1543                    fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1544                }1545            },1546            CollectionMode::Fungible(_) => {1547                if let CreateItemData::Fungible(_) = data {1548                } else {1549                    fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1550                }1551            },1552            CollectionMode::ReFungible(_) => {1553                if let CreateItemData::ReFungible(data) = data {15541555                    // check sizes1556                    ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1557                    ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1558                } else {1559                    fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1560                }1561            },1562            _ => { fail!(Error::<T>::UnexpectedCollectionType); }1563        };15641565        Ok(())1566    }15671568    fn create_item_no_validation(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1569        match data1570        {1571            CreateItemData::NFT(data) => {1572                let item = NftItemType {1573                    collection: collection_id,1574                    owner,1575                    const_data: data.const_data,1576                    variable_data: data.variable_data1577                };15781579                Self::add_nft_item(item)?;1580            },1581            CreateItemData::Fungible(_) => {1582                let item = FungibleItemType {1583                    collection: collection_id,1584                    owner,1585                    value: (10 as u128).pow(collection.decimal_points as u32)1586                };15871588                Self::add_fungible_item(item)?;1589            },1590            CreateItemData::ReFungible(data) => {1591                let mut owner_list = Vec::new();1592                let value = (10 as u128).pow(collection.decimal_points as u32);1593                owner_list.push(Ownership {owner: owner.clone(), fraction: value});15941595                let item = ReFungibleItemType {1596                    collection: collection_id,1597                    owner: owner_list,1598                    const_data: data.const_data,1599                    variable_data: data.variable_data1600                };16011602                Self::add_refungible_item(item)?;1603            }1604        };16051606        // call event1607        Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));16081609        Ok(())1610    }16111612    fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {1613        let current_index = <ItemListIndex>::get(item.collection)1614            .checked_add(1)1615            .ok_or(Error::<T>::NumOverflow)?;1616        let itemcopy = item.clone();1617        let owner = item.owner.clone();16181619        Self::add_token_index(item.collection, current_index, owner.clone())?;16201621        <ItemListIndex>::insert(item.collection, current_index);1622        <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);16231624        // Add current block1625        let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();1626        <FungibleTransferBasket<T>>::insert(item.collection, current_index, v);1627        1628        // Update balance1629        let new_balance = <Balance<T>>::get(item.collection, owner.clone())1630            .checked_add(item.value)1631            .ok_or(Error::<T>::NumOverflow)?;1632        <Balance<T>>::insert(item.collection, owner.clone(), new_balance);16331634        Ok(())1635    }16361637    fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1638        let current_index = <ItemListIndex>::get(item.collection)1639            .checked_add(1)1640            .ok_or(Error::<T>::NumOverflow)?;1641        let itemcopy = item.clone();16421643        let value = item.owner.first().unwrap().fraction;1644        let owner = item.owner.first().unwrap().owner.clone();16451646        Self::add_token_index(item.collection, current_index, owner.clone())?;16471648        <ItemListIndex>::insert(item.collection, current_index);1649        <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);16501651        // Add current block1652        let block_number: T::BlockNumber = 0.into();1653        <ReFungibleTransferBasket<T>>::insert(item.collection, current_index, block_number);16541655        // Update balance1656        let new_balance = <Balance<T>>::get(item.collection, owner.clone())1657            .checked_add(value)1658            .ok_or(Error::<T>::NumOverflow)?;1659        <Balance<T>>::insert(item.collection, owner.clone(), new_balance);16601661        Ok(())1662    }16631664    fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {1665        let current_index = <ItemListIndex>::get(item.collection)1666            .checked_add(1)1667            .ok_or(Error::<T>::NumOverflow)?;16681669        let item_owner = item.owner.clone();1670        let collection_id = item.collection.clone();1671        Self::add_token_index(collection_id, current_index, item.owner.clone())?;16721673        <ItemListIndex>::insert(collection_id, current_index);1674        <NftItemList<T>>::insert(collection_id, current_index, item);16751676        // Add current block1677        let block_number: T::BlockNumber = 0.into();1678        <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);16791680        // Update balance1681        let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1682            .checked_add(1)1683            .ok_or(Error::<T>::NumOverflow)?;1684        <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);16851686        Ok(())1687    }16881689    fn burn_refungible_item(1690        collection_id: CollectionId,1691        item_id: TokenId,1692        owner: T::AccountId,1693    ) -> DispatchResult {1694        ensure!(1695            <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1696            Error::<T>::TokenNotFound1697        );1698        let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1699        let item = collection1700            .owner1701            .iter()1702            .filter(|&i| i.owner == owner)1703            .next()1704            .unwrap();1705        Self::remove_token_index(collection_id, item_id, owner.clone())?;17061707        // remove approve list1708        <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));17091710        // update balance1711        let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1712            .checked_sub(item.fraction)1713            .ok_or(Error::<T>::NumOverflow)?;1714        <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);17151716        <ReFungibleItemList<T>>::remove(collection_id, item_id);17171718        Ok(())1719    }17201721    fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1722        ensure!(1723            <NftItemList<T>>::contains_key(collection_id, item_id),1724            Error::<T>::TokenNotFound1725        );1726        let item = <NftItemList<T>>::get(collection_id, item_id);1727        Self::remove_token_index(collection_id, item_id, item.owner.clone())?;17281729        // remove approve list1730        <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));17311732        // update balance1733        let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1734            .checked_sub(1)1735            .ok_or(Error::<T>::NumOverflow)?;1736        <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1737        <NftItemList<T>>::remove(collection_id, item_id);17381739        Ok(())1740    }17411742    fn burn_fungible_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1743        ensure!(1744            <FungibleItemList<T>>::contains_key(collection_id, item_id),1745            Error::<T>::TokenNotFound1746        );1747        let item = <FungibleItemList<T>>::get(collection_id, item_id);1748        Self::remove_token_index(collection_id, item_id, item.owner.clone())?;17491750        // remove approve list1751        <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));17521753        // update balance1754        let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1755            .checked_sub(item.value)1756            .ok_or(Error::<T>::NumOverflow)?;1757        <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);17581759        <FungibleItemList<T>>::remove(collection_id, item_id);17601761        Ok(())1762    }17631764    fn collection_exists(collection_id: CollectionId) -> DispatchResult {1765        ensure!(1766            <Collection<T>>::contains_key(collection_id),1767            Error::<T>::CollectionNotFound1768        );1769        Ok(())1770    }17711772    fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult {1773        Self::collection_exists(collection_id)?;17741775        let target_collection = <Collection<T>>::get(collection_id);1776        ensure!(1777            subject == target_collection.owner,1778            Error::<T>::NoPermission1779        );17801781        Ok(())1782    }17831784    fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool {1785        let target_collection = <Collection<T>>::get(collection_id);1786        let mut result: bool = subject == target_collection.owner;1787        let exists = <AdminList<T>>::contains_key(collection_id);17881789        if !result & exists {1790            if <AdminList<T>>::get(collection_id).contains(&subject) {1791                result = true1792            }1793        }17941795        result1796    }17971798    fn check_owner_or_admin_permissions(1799        collection_id: CollectionId,1800        subject: T::AccountId,1801    ) -> DispatchResult {1802        Self::collection_exists(collection_id)?;1803        let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());18041805        ensure!(1806            result,1807            Error::<T>::NoPermission1808        );1809        Ok(())1810    }18111812    fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool {1813        let target_collection = <Collection<T>>::get(collection_id);18141815        match target_collection.mode {1816            CollectionMode::NFT => {1817                <NftItemList<T>>::get(collection_id, item_id).owner == subject1818            }1819            CollectionMode::Fungible(_) => {1820                <FungibleItemList<T>>::get(collection_id, item_id).owner == subject1821            }1822            CollectionMode::ReFungible(_) => {1823                <ReFungibleItemList<T>>::get(collection_id, item_id)1824                    .owner1825                    .iter()1826                    .any(|i| i.owner == subject)1827            }1828            CollectionMode::Invalid => false,1829        }1830    }18311832    fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult {1833        let mes = Error::<T>::AddresNotInWhiteList;1834        ensure!(<WhiteList<T>>::contains_key(collection_id), mes);1835        let wl = <WhiteList<T>>::get(collection_id);1836        ensure!(wl.contains(address), mes);18371838        Ok(())1839    }18401841    fn transfer_fungible(1842        collection_id: CollectionId,1843        item_id: TokenId,1844        value: u128,1845        owner: T::AccountId,1846        new_owner: T::AccountId,1847    ) -> DispatchResult {1848        ensure!(1849            <FungibleItemList<T>>::contains_key(collection_id, item_id),1850            Error::<T>::TokenNotFound1851        );18521853        let full_item = <FungibleItemList<T>>::get(collection_id, item_id);1854        let amount = full_item.value;18551856        ensure!(amount >= value, Error::<T>::TokenValueTooLow);18571858        // update balance1859        let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())1860            .checked_sub(value)1861            .ok_or(Error::<T>::NumOverflow)?;1862        <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);18631864        let mut new_owner_account_id = 0;1865        let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());1866        if new_owner_items.len() > 0 {1867            new_owner_account_id = new_owner_items[0];1868        }18691870        // transfer1871        if amount == value && new_owner_account_id == 0 {1872            // change owner1873            // new owner do not have account1874            let mut new_full_item = full_item.clone();1875            new_full_item.owner = new_owner.clone();1876            <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);18771878            // update balance1879            let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1880                .checked_add(value)1881                .ok_or(Error::<T>::NumOverflow)?;1882            <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);18831884            // update index collection1885            Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;1886        } else {1887            let mut new_full_item = full_item.clone();1888            new_full_item.value -= value;18891890            // separate amount1891            if new_owner_account_id > 0 {1892                // new owner has account1893                let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);1894                item.value += value;18951896                // update balance1897                let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1898                    .checked_add(value)1899                    .ok_or(Error::<T>::NumOverflow)?;1900                <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);19011902                <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1903            } else {1904                // new owner do not have account1905                let item = FungibleItemType {1906                    collection: collection_id,1907                    owner: new_owner.clone(),1908                    value1909                };19101911                Self::add_fungible_item(item)?;1912            }19131914            if amount == value {1915                Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;19161917                // remove approve list1918                <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1919                <FungibleItemList<T>>::remove(collection_id, item_id);1920            }19211922            <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1923        }19241925        Ok(())1926    }19271928    fn transfer_refungible(1929        collection_id: CollectionId,1930        item_id: TokenId,1931        value: u128,1932        owner: T::AccountId,1933        new_owner: T::AccountId,1934    ) -> DispatchResult {1935        ensure!(1936            <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1937            Error::<T>::TokenNotFound1938        );19391940        let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1941        let item = full_item1942            .owner1943            .iter()1944            .filter(|i| i.owner == owner)1945            .next()1946            .ok_or(Error::<T>::NumOverflow)?;1947        let amount = item.fraction;19481949        ensure!(amount >= value, Error::<T>::TokenValueTooLow);19501951        // update balance1952        let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1953            .checked_sub(value)1954            .ok_or(Error::<T>::NumOverflow)?;1955        <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19561957        let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1958            .checked_add(value)1959            .ok_or(Error::<T>::NumOverflow)?;1960        <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);19611962        let old_owner = item.owner.clone();1963        let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);19641965        // transfer1966        if amount == value && !new_owner_has_account {1967            // change owner1968            // new owner do not have account1969            let mut new_full_item = full_item.clone();1970            new_full_item1971                .owner1972                .iter_mut()1973                .find(|i| i.owner == owner)1974                .unwrap()1975                .owner = new_owner.clone();1976            <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);19771978            // update index collection1979            Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1980        } else {1981            let mut new_full_item = full_item.clone();1982            new_full_item1983                .owner1984                .iter_mut()1985                .find(|i| i.owner == owner)1986                .unwrap()1987                .fraction -= value;19881989            // separate amount1990            if new_owner_has_account {1991                // new owner has account1992                new_full_item1993                    .owner1994                    .iter_mut()1995                    .find(|i| i.owner == new_owner)1996                    .unwrap()1997                    .fraction += value;1998            } else {1999                // new owner do not have account2000                new_full_item.owner.push(Ownership {2001                    owner: new_owner.clone(),2002                    fraction: value,2003                });2004                Self::add_token_index(collection_id, item_id, new_owner.clone())?;2005            }20062007            <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2008        }20092010        Ok(())2011    }20122013    fn transfer_nft(2014        collection_id: CollectionId,2015        item_id: TokenId,2016        sender: T::AccountId,2017        new_owner: T::AccountId,2018    ) -> DispatchResult {2019        ensure!(2020            <NftItemList<T>>::contains_key(collection_id, item_id),2021            Error::<T>::TokenNotFound2022        );20232024        let mut item = <NftItemList<T>>::get(collection_id, item_id);20252026        ensure!(2027            sender == item.owner,2028            Error::<T>::MustBeTokenOwner2029        );20302031        // update balance2032        let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())2033            .checked_sub(1)2034            .ok_or(Error::<T>::NumOverflow)?;2035        <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);20362037        let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())2038            .checked_add(1)2039            .ok_or(Error::<T>::NumOverflow)?;2040        <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);20412042        // change owner2043        let old_owner = item.owner.clone();2044        item.owner = new_owner.clone();2045        <NftItemList<T>>::insert(collection_id, item_id, item);20462047        // update index collection2048        Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;20492050        // reset approved list2051        <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));2052        Ok(())2053    }2054    2055    fn item_exists(2056        collection_id: CollectionId,2057        item_id: TokenId,2058        mode: &CollectionMode2059    ) -> DispatchResult {2060        match mode {2061            CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2062            CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2063            CollectionMode::Fungible(_) => ensure!(<FungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2064            _ => ()2065        };2066        2067        Ok(())2068    }20692070    fn set_re_fungible_variable_data(2071        collection_id: CollectionId,2072        item_id: TokenId,2073        data: Vec<u8>2074    ) -> DispatchResult {2075        let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);20762077        item.variable_data = data;20782079        <ReFungibleItemList<T>>::insert(collection_id, item_id, item);20802081        Ok(())2082    }20832084    fn set_nft_variable_data(2085        collection_id: CollectionId,2086        item_id: TokenId,2087        data: Vec<u8>2088    ) -> DispatchResult {2089        let mut item = <NftItemList<T>>::get(collection_id, item_id);2090        2091        item.variable_data = data;20922093        <NftItemList<T>>::insert(collection_id, item_id, item);2094        2095        Ok(())2096    }20972098    fn init_collection(item: &CollectionType<T::AccountId>) {2099        // check params2100        assert!(2101            item.decimal_points <= MAX_DECIMAL_POINTS,2102            "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2103        );2104        assert!(2105            item.name.len() <= 64,2106            "Collection name can not be longer than 63 char"2107        );2108        assert!(2109            item.name.len() <= 256,2110            "Collection description can not be longer than 255 char"2111        );2112        assert!(2113            item.token_prefix.len() <= 16,2114            "Token prefix can not be longer than 15 char"2115        );21162117        // Generate next collection ID2118        let next_id = CreatedCollectionCount::get()2119            .checked_add(1)2120            .unwrap();21212122        CreatedCollectionCount::put(next_id);2123    }21242125    fn init_nft_token(item: &NftItemType<T::AccountId>) {2126        let current_index = <ItemListIndex>::get(item.collection)2127            .checked_add(1)2128            .unwrap();21292130        let item_owner = item.owner.clone();2131        let collection_id = item.collection.clone();2132        Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();21332134        <ItemListIndex>::insert(collection_id, current_index);21352136        // Update balance2137        let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())2138            .checked_add(1)2139            .unwrap();2140        <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2141    }21422143    fn init_fungible_token(item: &FungibleItemType<T::AccountId>) {2144        let current_index = <ItemListIndex>::get(item.collection)2145            .checked_add(1)2146            .unwrap();2147        let owner = item.owner.clone();21482149        Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();21502151        <ItemListIndex>::insert(item.collection, current_index);21522153        // Update balance2154        let new_balance = <Balance<T>>::get(item.collection, owner.clone())2155            .checked_add(item.value)2156            .unwrap();2157        <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2158    }21592160    fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>) {2161        let current_index = <ItemListIndex>::get(item.collection)2162            .checked_add(1)2163            .unwrap();21642165        let value = item.owner.first().unwrap().fraction;2166        let owner = item.owner.first().unwrap().owner.clone();21672168        Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();21692170        <ItemListIndex>::insert(item.collection, current_index);21712172        // Update balance2173        let new_balance = <Balance<T>>::get(item.collection, owner.clone())2174            .checked_add(value)2175            .unwrap();2176        <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2177    }21782179    fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: T::AccountId) -> DispatchResult {21802181        // add to account limit2182        if <AccountItemCount<T>>::contains_key(owner.clone()) {21832184            // bound Owned tokens by a single address2185            let count = <AccountItemCount<T>>::get(owner.clone());2186            ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);21872188            <AccountItemCount<T>>::insert(owner.clone(), count2189                .checked_add(1)2190                .ok_or(Error::<T>::NumOverflow)?);2191        }2192        else {2193            <AccountItemCount<T>>::insert(owner.clone(), 1);2194        }21952196        let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2197        if list_exists {2198            let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2199            let item_contains = list.contains(&item_index.clone());22002201            if !item_contains {2202                list.push(item_index.clone());2203            }22042205            <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2206        } else {2207            let mut itm = Vec::new();2208            itm.push(item_index.clone());2209            <AddressTokens<T>>::insert(collection_id, owner, itm);2210            2211        }22122213        Ok(())2214    }22152216    fn remove_token_index(2217        collection_id: CollectionId,2218        item_index: TokenId,2219        owner: T::AccountId,2220    ) -> DispatchResult {22212222        // update counter2223        <AccountItemCount<T>>::insert(owner.clone(), 2224            <AccountItemCount<T>>::get(owner.clone())2225            .checked_sub(1)2226            .ok_or(Error::<T>::NumOverflow)?);222722282229        let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2230        if list_exists {2231            let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2232            let item_contains = list.contains(&item_index.clone());22332234            if item_contains {2235                list.retain(|&item| item != item_index);2236                <AddressTokens<T>>::insert(collection_id, owner, list);2237            }2238        }22392240        Ok(())2241    }22422243    fn move_token_index(2244        collection_id: CollectionId,2245        item_index: TokenId,2246        old_owner: T::AccountId,2247        new_owner: T::AccountId,2248    ) -> DispatchResult {2249        Self::remove_token_index(collection_id, item_index, old_owner)?;2250        Self::add_token_index(collection_id, item_index, new_owner)?;22512252        Ok(())2253    }2254}22552256////////////////////////////////////////////////////////////////////////////////////////////////////2257// Economic models2258// #region22592260/// Fee multiplier.2261pub type Multiplier = FixedU128;22622263type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2264    <T as system::Trait>::AccountId,2265>>::Balance;2266type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2267    <T as system::Trait>::AccountId,2268>>::NegativeImbalance;22692270/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2271/// in the queue.2272#[derive(Encode, Decode, Clone, Eq, PartialEq)]2273pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(2274    #[codec(compact)] BalanceOf<T>2275);22762277impl<T: Trait + Send + Sync> sp_std::fmt::Debug2278    for ChargeTransactionPayment<T>2279{2280    #[cfg(feature = "std")]2281    fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2282        write!(f, "ChargeTransactionPayment<{:?}>", self.0)2283    }2284    #[cfg(not(feature = "std"))]2285    fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2286        Ok(())2287    }2288}22892290impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2291where2292    T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2293    BalanceOf<T>: Send + Sync + FixedPointOperand,2294{2295    /// utility constructor. Used only in client/factory code.2296    pub fn from(fee: BalanceOf<T>) -> Self {2297        Self(fee)2298    }22992300    pub fn traditional_fee(2301        len: usize,2302        info: &DispatchInfoOf<T::Call>,2303        tip: BalanceOf<T>,2304    ) -> BalanceOf<T>2305    where2306        T::Call: Dispatchable<Info = DispatchInfo>,2307    {2308        <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2309    }23102311	fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2312		let weight_saturation = T::MaximumBlockWeight::get() / info.weight.max(1);2313		let len_saturation = T::MaximumBlockLength::get() as u64 / (len as u64).max(1);2314		let coefficient: BalanceOf<T> = weight_saturation.min(len_saturation).saturated_into::<BalanceOf<T>>();2315		final_fee.saturating_mul(coefficient).saturated_into::<TransactionPriority>()2316	}23172318    fn withdraw_fee(2319        &self,2320        who: &T::AccountId,2321        call: &T::Call,2322        info: &DispatchInfoOf<T::Call>,2323        len: usize,2324    ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2325        let tip = self.0;23262327        // Set fee based on call type. Creating collection costs 1 Unique.2328        // All other transactions have traditional fees so far2329        // let fee = match call.is_sub_type() {2330        //     Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2331        //     _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2332        //                                                 // _ => <BalanceOf<T>>::from(100)2333        // };2334        let fee = Self::traditional_fee(len, info, tip);23352336        // Determine who is paying transaction fee based on ecnomic model2337        // Parse call to extract collection ID and access collection sponsor2338        let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2339            Some(Call::create_item(collection_id, _owner, _properties)) => {23402341                // check free create limit2342                if (<Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)) &&2343                   (<Collection<T>>::get(collection_id).sponsor_confirmed)2344                {2345                    <Collection<T>>::get(collection_id).sponsor2346                } else {2347                    T::AccountId::default()2348                }2349            }2350            Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {2351                2352                let mut sponsor_transfer = false;2353                if <Collection<T>>::get(collection_id).sponsor_confirmed {2354                    let _collection_limits = <Collection<T>>::get(collection_id).limits;2355                    let _collection_mode = <Collection<T>>::get(collection_id).mode;2356    2357                    // sponsor timeout2358                    sponsor_transfer = match _collection_mode {2359                        CollectionMode::NFT => {2360    2361                            // get correct limit2362                            let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2363                                _collection_limits.sponsor_transfer_timeout2364                            } else {2365                                ChainLimit::get().nft_sponsor_transfer_timeout2366                            };2367    2368                            let basket = <NftTransferBasket<T>>::get(collection_id, _item_id);2369                            let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2370                            let limit_time = basket + limit.into();2371                            if block_number >= limit_time {2372                                <NftTransferBasket<T>>::insert(collection_id, _item_id, block_number);2373                                true2374                            }2375                            else {2376                                false2377                            }2378                        }2379                        CollectionMode::Fungible(_) => {2380    2381                            // get correct limit2382                            let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2383                                _collection_limits.sponsor_transfer_timeout2384                            } else {2385                                ChainLimit::get().fungible_sponsor_transfer_timeout2386                            };2387    2388                            let mut basket = <FungibleTransferBasket<T>>::get(collection_id, _item_id);2389                            let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2390                            if basket.iter().any(|i| i.address == _new_owner.clone())2391                            {2392                                let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone();2393                                let limit_time = item.start_block + limit.into();2394                                if block_number >= limit_time {2395                                    basket.retain(|x| x.address == item.address);2396                                    basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() });2397                                    <FungibleTransferBasket<T>>::insert(collection_id, _item_id, basket);2398                                    true2399                                }2400                                else {2401                                    false2402                                }2403                            }2404                            else {2405                                basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()});2406                                true2407                            }2408                        }2409                        CollectionMode::ReFungible(_) => {2410    2411                            // get correct limit2412                            let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2413                                _collection_limits.sponsor_transfer_timeout2414                            } else {2415                                ChainLimit::get().refungible_sponsor_transfer_timeout2416                            };2417    2418                            let basket = <ReFungibleTransferBasket<T>>::get(collection_id, _item_id);2419                            let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2420                            let limit_time = basket + limit.into();2421                            if block_number >= limit_time {2422                                <ReFungibleTransferBasket<T>>::insert(collection_id, _item_id, block_number);2423                                true2424                            } else {2425                                false2426                            }2427                        }2428                        _ => {2429                            false2430                        },2431                    };2432                }24332434                if !sponsor_transfer {2435                    T::AccountId::default()2436                } else {2437                    <Collection<T>>::get(collection_id).sponsor2438                }2439            }24402441            _ => T::AccountId::default(),2442        };24432444        // Sponsor smart contracts2445        sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {24462447            // On instantiation: set the contract owner2448            Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {24492450                let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2451                    code_hash,2452                    &data,2453                    &who,2454                );2455                <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());24562457                T::AccountId::default()2458            },24592460            // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2461            Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {24622463                let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());24642465                let mut sponsor_transfer = false;2466                if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2467                    let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2468                    let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2469                    let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2470                    let limit_time = last_tx_block + rate_limit;24712472                    if block_number >= limit_time {2473                        <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2474                        sponsor_transfer = true;2475                    }2476                } else {2477                    sponsor_transfer = false;2478                }2479               2480                2481                let mut sp = T::AccountId::default();2482                if sponsor_transfer {2483                    if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2484                        if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2485                            sp = called_contract;2486                        }2487                    }2488                }24892490                sp2491            },24922493            _ => sponsor,2494        };24952496        let mut who_pays_fee: T::AccountId = sponsor.clone();2497        if sponsor == T::AccountId::default() {2498            who_pays_fee = who.clone();2499        }25002501        // Only mess with balances if fee is not zero.2502        if fee.is_zero() {2503            return Ok((fee, None));2504        }25052506        match <T as transaction_payment::Trait>::Currency::withdraw(2507            &who_pays_fee,2508            fee,2509            if tip.is_zero() {2510                WithdrawReason::TransactionPayment.into()2511            } else {2512                WithdrawReason::TransactionPayment | WithdrawReason::Tip2513            },2514            ExistenceRequirement::KeepAlive,2515        ) {2516            Ok(imbalance) => Ok((fee, Some(imbalance))),2517            Err(_) => Err(InvalidTransaction::Payment.into()),2518        }2519    }2520}252125222523impl<T: Trait + Send + Sync> SignedExtension2524    for ChargeTransactionPayment<T>2525where2526    BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2527    T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2528{2529    const IDENTIFIER: &'static str = "ChargeTransactionPayment";2530    type AccountId = T::AccountId;2531    type Call = T::Call;2532    type AdditionalSigned = ();2533    type Pre = (2534        BalanceOf<T>,2535        Self::AccountId,2536        Option<NegativeImbalanceOf<T>>,2537        BalanceOf<T>,2538    );2539    fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2540        Ok(())2541    }25422543    fn validate(2544        &self,2545        who: &Self::AccountId,2546        call: &Self::Call,2547        info: &DispatchInfoOf<Self::Call>,2548        len: usize,2549    ) -> TransactionValidity {2550		let (fee, _) = self.withdraw_fee(who, call, info, len)?;2551		Ok(ValidTransaction {2552			priority: Self::get_priority(len, info, fee),2553			..Default::default()2554		})2555    }25562557    fn pre_dispatch(2558        self,2559        who: &Self::AccountId,2560        call: &Self::Call,2561        info: &DispatchInfoOf<Self::Call>,2562        len: usize,2563    ) -> Result<Self::Pre, TransactionValidityError> {2564        let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2565        Ok((self.0, who.clone(), imbalance, fee))2566    }25672568    fn post_dispatch(2569        pre: Self::Pre,2570        info: &DispatchInfoOf<Self::Call>,2571        post_info: &PostDispatchInfoOf<Self::Call>,2572        len: usize,2573        _result: &DispatchResult,2574    ) -> Result<(), TransactionValidityError> {2575        let (tip, who, imbalance, fee) = pre;2576        if let Some(payed) = imbalance {2577            let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2578                len as u32, info, post_info, tip,2579            );2580            let refund = fee.saturating_sub(actual_fee);2581            let actual_payment =2582                match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2583                    &who, refund,2584                ) {2585                    Ok(refund_imbalance) => {2586                        // The refund cannot be larger than the up front payed max weight.2587                        // `PostDispatchInfo::calc_unspent` guards against such a case.2588                        match payed.offset(refund_imbalance) {2589                            Ok(actual_payment) => actual_payment,2590                            Err(_) => return Err(InvalidTransaction::Payment.into()),2591                        }2592                    }2593                    // We do not recreate the account using the refund. The up front payment2594                    // is gone in that case.2595                    Err(_) => payed,2596                };2597            let imbalances = actual_payment.split(tip);2598            <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2599                Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2600            );2601        }2602        Ok(())2603    }2604}26052606// #endregion
modifiedruntime_types.jsondiffbeforeafterboth
--- a/runtime_types.json
+++ b/runtime_types.json
@@ -70,7 +70,7 @@
       "OffchainSchema": "Vec<u8>",
       "SchemaVersion": "SchemaVersion",
       "Sponsor": "AccountId",
-      "UnconfirmedSponsor": "AccountId",
+      "SponsorConfirmed": "bool",
       "Limits": "CollectionLimits",
       "VariableOnChainSchema": "Vec<u8>",
       "ConstOnChainSchema": "Vec<u8>"
modifiedtests/src/destroyCollection.test.tsdiffbeforeafterboth
--- a/tests/src/destroyCollection.test.ts
+++ b/tests/src/destroyCollection.test.ts
@@ -1,55 +1,12 @@
 import chai from 'chai';
 import chaiAsPromised from 'chai-as-promised';
 import { default as usingApi, submitTransactionAsync } from "./substrate/substrate-api";
-import { createCollectionExpectSuccess, createCollectionExpectFailure } from "./util/helpers";
+import { createCollectionExpectSuccess, createCollectionExpectFailure, destroyCollectionExpectSuccess, destroyCollectionExpectFailure } from "./util/helpers";
 import type { AccountId, EventRecord } from '@polkadot/types/interfaces';
 import privateKey from './substrate/privateKey';
-import { nullPublicKey } from './accounts'; 
 
 chai.use(chaiAsPromised);
 const expect = chai.expect;
-
-function getDestroyResult(events: EventRecord[]): boolean {
-  let success: boolean = false;
-  events.forEach(({ phase, event: { data, method, section } }) => {
-    // console.log(`    ${phase}: ${section}.${method}:: ${data}`);
-    if (method == 'ExtrinsicSuccess') {
-      success = true;
-    }
-  });
-  return success;
-}
-
-async function destroyCollectionExpectSuccess(collectionId: number, senderSeed: string = '//Alice') {
-  await usingApi(async (api) => {
-    // Run the DestroyCollection transaction
-    const alicePrivateKey = privateKey(senderSeed);
-    const tx = api.tx.nft.destroyCollection(collectionId);
-    const events = await submitTransactionAsync(alicePrivateKey, tx);
-    const result = getDestroyResult(events);
-
-    // Get the collection 
-    const collection: any = (await api.query.nft.collection(collectionId)).toJSON();
-
-    // What to expect
-    expect(result).to.be.true;
-    expect(collection).to.be.not.null;
-    expect(collection.Owner).to.be.equal(nullPublicKey);
-  });
-}
-
-async function destroyCollectionExpectFailure(collectionId: number, senderSeed: string = '//Alice') {
-  await usingApi(async (api) => {
-    // Run the DestroyCollection transaction
-    const alicePrivateKey = privateKey(senderSeed);
-    const tx = api.tx.nft.destroyCollection(collectionId);
-    const events = await submitTransactionAsync(alicePrivateKey, tx);
-    const result = getDestroyResult(events);
-
-    // What to expect
-    expect(result).to.be.false;
-  });
-}
 
 describe('integration test: ext. destroyCollection():', () => {
   it('NFT collection can be destroyed', async () => {
addedtests/src/setCollectionSponsor.test.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/setCollectionSponsor.test.ts
@@ -0,0 +1,78 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+import { default as usingApi } from "./substrate/substrate-api";
+import { createCollectionExpectSuccess, setCollectionSponsorExpectSuccess, destroyCollectionExpectSuccess, setCollectionSponsorExpectFailure } from "./util/helpers";
+import { Keyring } from "@polkadot/api";
+import { IKeyringPair } from "@polkadot/types/types";
+import type { AccountId } from '@polkadot/types/interfaces';
+
+chai.use(chaiAsPromised);
+const expect = chai.expect;
+
+let bob: IKeyringPair;
+
+describe('integration test: ext. setCollectionSponsor():', () => {
+
+  before(async () => {
+    await usingApi(async (api) => {
+      const keyring = new Keyring({ type: 'sr25519' });
+      bob = keyring.addFromUri(`//Bob`);
+    });
+  });
+
+  it('Set NFT collection sponsor', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+  });
+  it('Set Fungible collection sponsor', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'Fungible');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+  });
+  it('Set ReFungible collection sponsor', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'ReFungible');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+  });
+
+  it('Set the same sponsor repeatedly', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+  });
+  it('Replace collection sponsor', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+
+    const keyring = new Keyring({ type: 'sr25519' });
+    const charlie = keyring.addFromUri(`//Charlie`);
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await setCollectionSponsorExpectSuccess(collectionId, charlie.address);
+  });
+});
+
+describe('(!negative test!) integration test: ext. setCollectionSponsor():', () => {
+  before(async () => {
+    await usingApi(async (api) => {
+      const keyring = new Keyring({ type: 'sr25519' });
+      bob = keyring.addFromUri(`//Bob`);
+    });
+  });
+
+  it('(!negative test!) Add sponsor to a collection that never existed', async () => {
+    // Find the collection that never existed
+    const collectionId = 0;
+    await usingApi(async (api) => {
+      const collectionId = parseInt((await api.query.nft.createdCollectionCount()).toString()) + 1;
+    });
+
+    await setCollectionSponsorExpectFailure(collectionId, bob.address);
+  });
+  it('(!negative test!) Add sponsor to a collection that was destroyed', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+    await destroyCollectionExpectSuccess(collectionId);
+    await setCollectionSponsorExpectFailure(collectionId, bob.address);
+  });
+});
modifiedtests/src/util/helpers.tsdiffbeforeafterboth
--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -9,7 +9,7 @@
 import { ApiPromise, Keyring } from "@polkadot/api";
 import { default as usingApi, submitTransactionAsync } from "../substrate/substrate-api";
 import privateKey from '../substrate/privateKey';
-import { alicesPublicKey } from "../accounts";
+import { alicesPublicKey, nullPublicKey } from "../accounts";
 import { strToUTF16, utf16ToStr, hexToStr } from '../util/util';
 import { IKeyringPair } from "@polkadot/types/types";
 import { BigNumber } from 'bignumber.js';
@@ -121,4 +121,79 @@
     bal = new BigNumber((await api.query.system.account(unused.address)).data.free.toString());
   } while (bal.toFixed() != '0');
   return unused; 
-}
\ No newline at end of file
+}
+
+function getDestroyResult(events: EventRecord[]): boolean {
+  let success: boolean = false;
+  events.forEach(({ phase, event: { data, method, section } }) => {
+    // console.log(`    ${phase}: ${section}.${method}:: ${data}`);
+    if (method == 'ExtrinsicSuccess') {
+      success = true;
+    }
+  });
+  return success;
+}
+
+export async function setCollectionSponsorExpectSuccess(collectionId: number, sponsor: string) {
+  await usingApi(async (api) => {
+
+    // Run the transaction
+    const alicePrivateKey = privateKey('//Alice');
+    const tx = api.tx.nft.setCollectionSponsor(collectionId, sponsor);
+    const events = await submitTransactionAsync(alicePrivateKey, tx);
+    const result = getGenericResult(events);
+
+    // Get the collection 
+    const collection: any = (await api.query.nft.collection(collectionId)).toJSON();
+
+    // What to expect
+    expect(result.success).to.be.true;
+    expect(collection.Sponsor.toString()).to.be.equal(sponsor.toString());
+    expect(collection.SponsorConfirmed).to.be.false;
+  });
+}
+
+export async function destroyCollectionExpectFailure(collectionId: number, senderSeed: string = '//Alice') {
+  await usingApi(async (api) => {
+    // Run the DestroyCollection transaction
+    const alicePrivateKey = privateKey(senderSeed);
+    const tx = api.tx.nft.destroyCollection(collectionId);
+    const events = await submitTransactionAsync(alicePrivateKey, tx);
+    const result = getDestroyResult(events);
+
+    // What to expect
+    expect(result).to.be.false;
+  });
+}
+
+export async function destroyCollectionExpectSuccess(collectionId: number, senderSeed: string = '//Alice') {
+  await usingApi(async (api) => {
+    // Run the DestroyCollection transaction
+    const alicePrivateKey = privateKey(senderSeed);
+    const tx = api.tx.nft.destroyCollection(collectionId);
+    const events = await submitTransactionAsync(alicePrivateKey, tx);
+    const result = getDestroyResult(events);
+
+    // Get the collection 
+    const collection: any = (await api.query.nft.collection(collectionId)).toJSON();
+
+    // What to expect
+    expect(result).to.be.true;
+    expect(collection).to.be.not.null;
+    expect(collection.Owner).to.be.equal(nullPublicKey);
+  });
+}
+
+export async function setCollectionSponsorExpectFailure(collectionId: number, sponsor: string) {
+  await usingApi(async (api) => {
+
+    // Run the transaction
+    const alicePrivateKey = privateKey('//Alice');
+    const tx = api.tx.nft.setCollectionSponsor(collectionId, sponsor);
+    const events = await submitTransactionAsync(alicePrivateKey, tx);
+    const result = getGenericResult(events);
+
+    // What to expect
+    expect(result.success).to.be.false;
+  });
+}