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

difftreelog

Merge branch 'develop' into feature/NFTPAR-250

Greg Zaitsev2020-12-25parents: #f7b41a8 #d430fcf.patch.diff
in: master

4 files changed

modifiedpallets/nft/src/lib.rsdiffbeforeafterboth
before · pallets/nft/src/lib.rs
1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]78#![cfg_attr(not(feature = "std"), no_std)]910#[cfg(feature = "std")]11pub use std::*;1213#[cfg(feature = "std")]14pub use serde::*;1516use codec::{Decode, Encode};17pub use frame_support::{18    construct_runtime, decl_event, decl_module, decl_storage, decl_error,19    dispatch::DispatchResult,20    ensure, fail, parameter_types,21    traits::{22        Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,23        Randomness, 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 owner: AccountId,149    pub const_data: Vec<u8>,150    pub variable_data: Vec<u8>,151}152153#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]154#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]155pub struct FungibleItemType {156    pub value: u128,157}158159#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]160#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]161pub struct ReFungibleItemType<AccountId> {162    pub owner: Vec<Ownership<AccountId>>,163    pub const_data: Vec<u8>,164    pub variable_data: Vec<u8>,165}166167// #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]168// #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]169// pub struct VestingItem<AccountId, Moment> {170//     pub sender: AccountId,171//     pub recipient: AccountId,172//     pub collection_id: CollectionId,173//     pub item_id: TokenId,174//     pub amount: u64,175//     pub vesting_date: Moment,176// }177178#[derive(Encode, Decode, Debug, Clone, PartialEq)]179#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]180pub struct CollectionLimits {181    pub account_token_ownership_limit: u32,182    pub sponsored_data_size: u32,183    pub token_limit: u32,184185    // Timeouts for item types in passed blocks186    pub sponsor_transfer_timeout: u32,187}188189impl Default for CollectionLimits {190    fn default() -> CollectionLimits {191        CollectionLimits { 192            account_token_ownership_limit: 10_000_000, 193            token_limit: u32::max_value(),194            sponsored_data_size: u32::max_value(), 195            sponsor_transfer_timeout: 14400 }196    }197}198199#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]200#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]201pub struct ChainLimits {202    pub collection_numbers_limit: u32,203    pub account_token_ownership_limit: u32,204    pub collections_admins_limit: u64,205    pub custom_data_limit: u32,206207    // Timeouts for item types in passed blocks208    pub nft_sponsor_transfer_timeout: u32,209    pub fungible_sponsor_transfer_timeout: u32,210    pub refungible_sponsor_transfer_timeout: u32,211}212213pub trait WeightInfo {214	fn create_collection() -> Weight;215	fn destroy_collection() -> Weight;216	fn add_to_white_list() -> Weight;217	fn remove_from_white_list() -> Weight;218    fn set_public_access_mode() -> Weight;219    fn set_mint_permission() -> Weight;220    fn change_collection_owner() -> Weight;221    fn add_collection_admin() -> Weight;222    fn remove_collection_admin() -> Weight;223    fn set_collection_sponsor() -> Weight;224    fn confirm_sponsorship() -> Weight;225    fn remove_collection_sponsor() -> Weight;226    fn create_item(s: usize) -> Weight;227    fn burn_item() -> Weight;228    fn transfer() -> Weight;229    fn approve() -> Weight;230    fn transfer_from() -> Weight;231    fn set_offchain_schema() -> Weight;232    fn set_const_on_chain_schema() -> Weight;233    fn set_variable_on_chain_schema() -> Weight;234    fn set_variable_meta_data() -> Weight;235    fn enable_contract_sponsoring() -> Weight;236}237238#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]239#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]240pub struct CreateNftData {241    pub const_data: Vec<u8>,242    pub variable_data: Vec<u8>,243}244245#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]246#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]247pub struct CreateFungibleData {248    pub value: u128,249}250251#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]252#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]253pub struct CreateReFungibleData {254    pub const_data: Vec<u8>,255    pub variable_data: Vec<u8>,256}257258#[derive(Encode, Decode, Debug, Clone, PartialEq)]259#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]260pub enum CreateItemData {261    NFT(CreateNftData),262    Fungible(CreateFungibleData),263    ReFungible(CreateReFungibleData),264}265266impl CreateItemData {267    pub fn len(&self) -> usize {268        let len = match self {269            CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),270            CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),271            _ => 0272        };273        274        return len;275    }276}277278impl From<CreateNftData> for CreateItemData {279    fn from(item: CreateNftData) -> Self {280        CreateItemData::NFT(item)281    }282}283284impl From<CreateReFungibleData> for CreateItemData {285    fn from(item: CreateReFungibleData) -> Self {286        CreateItemData::ReFungible(item)287    }288}289290impl From<CreateFungibleData> for CreateItemData {291    fn from(item: CreateFungibleData) -> Self {292        CreateItemData::Fungible(item)293    }294}295296297decl_error! {298	/// Error for non-fungible-token module.299	pub enum Error for Module<T: Trait> {300        /// Total collections bound exceeded.301        TotalCollectionsLimitExceeded,302		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.303        CollectionDecimalPointLimitExceeded, 304        /// Collection name can not be longer than 63 char.305        CollectionNameLimitExceeded, 306        /// Collection description can not be longer than 255 char.307        CollectionDescriptionLimitExceeded, 308        /// Token prefix can not be longer than 15 char.309        CollectionTokenPrefixLimitExceeded,310        /// This collection does not exist.311        CollectionNotFound,312        /// Item not exists.313        TokenNotFound,314        /// Arithmetic calculation overflow.315        NumOverflow,       316        /// Account already has admin role.317        AlreadyAdmin,  318        /// You do not own this collection.319        NoPermission,320        /// This address is not set as sponsor, use setCollectionSponsor first.321        ConfirmUnsetSponsorFail,322        /// Collection is not in mint mode.323        PublicMintingNotAllowed,324        /// Sender parameter and item owner must be equal.325        MustBeTokenOwner,326        /// Item balance not enough.327        TokenValueTooLow,328        /// Size of item is too large.329        NftSizeLimitExceeded,330        /// No approve found331        ApproveNotFound,332        /// Requested value more than approved.333        TokenValueNotEnough,334        /// Only approved addresses can call this method.335        ApproveRequired,336        /// Address is not in white list.337        AddresNotInWhiteList,338        /// Number of collection admins bound exceeded.339        CollectionAdminsLimitExceeded,340        /// Owned tokens by a single address bound exceeded.341        AddressOwnershipLimitExceeded,342        /// Length of items properties must be greater than 0.343        EmptyArgument,344        /// const_data exceeded data limit.345        TokenConstDataLimitExceeded,346        /// variable_data exceeded data limit.347        TokenVariableDataLimitExceeded,348        /// Not NFT item data used to mint in NFT collection.349        NotNftDataUsedToMintNftCollectionToken,350        /// Not Fungible item data used to mint in Fungible collection.351        NotFungibleDataUsedToMintFungibleCollectionToken,352        /// Not Re Fungible item data used to mint in Re Fungible collection.353        NotReFungibleDataUsedToMintReFungibleCollectionToken,354        /// Unexpected collection type.355        UnexpectedCollectionType,356        /// Can't store metadata in fungible tokens.357        CantStoreMetadataInFungibleTokens,358        /// Collection token limit exceeded359        CollectionTokenLimitExceeded,360        /// Account token limit exceeded per collection361        AccountTokenLimitExceeded,362        /// Collection limit bounds per collection exceeded363        CollectionLimitBoundsExceeded364	}365}366367pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait {368    type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;369370    /// Weight information for extrinsics in this pallet.371	type WeightInfo: WeightInfo;372}373374#[cfg(feature = "runtime-benchmarks")]375mod benchmarking;376377// #endregion378379decl_storage! {380    trait Store for Module<T: Trait> as Nft {381382        // Private members383        NextCollectionID: CollectionId;384        CreatedCollectionCount: u32;385        ChainVersion: u64;386        ItemListIndex: map hasher(identity) CollectionId => TokenId;387388        // Chain limits struct389        pub ChainLimit get(fn chain_limit) config(): ChainLimits;390391        // Bound counters392        CollectionCount: u32;393        pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;394395        // Basic collections396        pub Collection get(fn collection) config(): map hasher(identity) CollectionId => CollectionType<T::AccountId>;397        pub AdminList get(fn admin_list_collection): map hasher(identity) CollectionId => Vec<T::AccountId>;398        pub WhiteList get(fn white_list): map hasher(identity) CollectionId => Vec<T::AccountId>;399400        /// Balance owner per collection map401        pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;402403        /// second parameter: item id + owner account id + spender account id404        pub Allowances get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId, T::AccountId) => u128;405406        /// Item collections407        pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;408        pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => FungibleItemType;409        pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;410411        /// Index list412        pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;413414        /// Tokens transfer baskets415        pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;416        pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;417        pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;418419        // Contract Sponsorship and Ownership420        pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId;421        pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;422        pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;423        pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;424    }425    add_extra_genesis {426        build(|config: &GenesisConfig<T>| {427            // Modification of storage428            for (_num, _c) in &config.collection {429                <Module<T>>::init_collection(_c);430            }431432            for (_num, _c, _i) in &config.nft_item_id {433                <Module<T>>::init_nft_token(*_c, _i);434            }435436            for (collection_id, account_id, fungible_item) in &config.fungible_item_id {437                <Module<T>>::init_fungible_token(*collection_id, account_id, fungible_item);438            }439440            for (_num, _c, _i) in &config.refungible_item_id {441                <Module<T>>::init_refungible_token(*_c, _i);442            }443        })444    }445}446447decl_event!(448    pub enum Event<T>449    where450        AccountId = <T as system::Trait>::AccountId,451    {452        /// New collection was created453        /// 454        /// # Arguments455        /// 456        /// * collection_id: Globally unique identifier of newly created collection.457        /// 458        /// * mode: [CollectionMode] converted into u8.459        /// 460        /// * account_id: Collection owner.461        Created(CollectionId, u8, AccountId),462463        /// New item was created.464        /// 465        /// # Arguments466        /// 467        /// * collection_id: Id of the collection where item was created.468        /// 469        /// * item_id: Id of an item. Unique within the collection.470        ItemCreated(CollectionId, TokenId),471472        /// Collection item was burned.473        /// 474        /// # Arguments475        /// 476        /// collection_id.477        /// 478        /// item_id: Identifier of burned NFT.479        ItemDestroyed(CollectionId, TokenId),480    }481);482483decl_module! {484    pub struct Module<T: Trait> for enum Call where origin: T::Origin {485486        fn deposit_event() = default;487        type Error = Error<T>;488489        fn on_initialize(now: T::BlockNumber) -> Weight {490491            if ChainVersion::get() < 2492            {493                let value = NextCollectionID::get();494                CreatedCollectionCount::put(value);495                ChainVersion::put(2);496            }497498            0499        }500501        /// 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.502        /// 503        /// # Permissions504        /// 505        /// * Anyone.506        /// 507        /// # Arguments508        /// 509        /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.510        /// 511        /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.512        /// 513        /// * token_prefix: UTF-8 string with token prefix.514        /// 515        /// * mode: [CollectionMode] collection type and type dependent data.516        // returns collection ID517        #[weight = T::WeightInfo::create_collection()]518        pub fn create_collection(origin,519                                 collection_name: Vec<u16>,520                                 collection_description: Vec<u16>,521                                 token_prefix: Vec<u8>,522                                 mode: CollectionMode) -> DispatchResult {523524            // Anyone can create a collection525            let who = ensure_signed(origin)?;526527            let decimal_points = match mode {528                CollectionMode::Fungible(points) => points,529                CollectionMode::ReFungible(points) => points,530                _ => 0531            };532533            // bound Total number of collections534            ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);535536            // check params537            ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);538539            let mut name = collection_name.to_vec();540            name.push(0);541            ensure!(name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);542543            let mut description = collection_description.to_vec();544            description.push(0);545            ensure!(name.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);546547            let mut prefix = token_prefix.to_vec();548            prefix.push(0);549            ensure!(prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);550551            // Generate next collection ID552            let next_id = CreatedCollectionCount::get()553                .checked_add(1)554                .ok_or(Error::<T>::NumOverflow)?;555556            // bound counter557            let total = CollectionCount::get()558                .checked_add(1)559                .ok_or(Error::<T>::NumOverflow)?;560561            CreatedCollectionCount::put(next_id);562            CollectionCount::put(total);563564            // Create new collection565            let new_collection = CollectionType {566                owner: who.clone(),567                name: name,568                mode: mode.clone(),569                mint_mode: false,570                access: AccessMode::Normal,571                description: description,572                decimal_points: decimal_points,573                token_prefix: prefix,574                offchain_schema: Vec::new(),575                schema_version: SchemaVersion::ImageURL,576                sponsor: T::AccountId::default(),577                sponsor_confirmed: false,578                variable_on_chain_schema: Vec::new(),579                const_on_chain_schema: Vec::new(),580                limits: CollectionLimits::default(),581            };582583            // Add new collection to map584            <Collection<T>>::insert(next_id, new_collection);585586            // call event587            Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));588589            Ok(())590        }591592        /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.593        /// 594        /// # Permissions595        /// 596        /// * Collection Owner.597        /// 598        /// # Arguments599        /// 600        /// * collection_id: collection to destroy.601        #[weight = T::WeightInfo::destroy_collection()]602        pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {603604            let sender = ensure_signed(origin)?;605            Self::check_owner_permissions(collection_id, sender)?;606607            <AddressTokens<T>>::remove_prefix(collection_id);608            <Allowances<T>>::remove_prefix(collection_id);609            <Balance<T>>::remove_prefix(collection_id);610            <ItemListIndex>::remove(collection_id);611            <AdminList<T>>::remove(collection_id);612            <Collection<T>>::remove(collection_id);613            <WhiteList<T>>::remove(collection_id);614615            <NftItemList<T>>::remove_prefix(collection_id);616            <FungibleItemList<T>>::remove_prefix(collection_id);617            <ReFungibleItemList<T>>::remove_prefix(collection_id);618619            <NftTransferBasket<T>>::remove_prefix(collection_id);620            <FungibleTransferBasket<T>>::remove_prefix(collection_id);621            <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);622623            if CollectionCount::get() > 0624            {625                // bound couter626                let total = CollectionCount::get()627                    .checked_sub(1)628                    .ok_or(Error::<T>::NumOverflow)?;629630                CollectionCount::put(total);631            }632633            Ok(())634        }635636        /// Add an address to white list.637        /// 638        /// # Permissions639        /// 640        /// * Collection Owner641        /// * Collection Admin642        /// 643        /// # Arguments644        /// 645        /// * collection_id.646        /// 647        /// * address.648        #[weight = T::WeightInfo::add_to_white_list()]649        pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{650651            let sender = ensure_signed(origin)?;652            Self::check_owner_or_admin_permissions(collection_id, sender)?;653654            let mut white_list_collection: Vec<T::AccountId>;655            if <WhiteList<T>>::contains_key(collection_id) {656                white_list_collection = <WhiteList<T>>::get(collection_id);657                if !white_list_collection.contains(&address.clone())658                {659                    white_list_collection.push(address.clone());660                }661            }662            else {663                white_list_collection = Vec::new();664                white_list_collection.push(address.clone());665            }666667            <WhiteList<T>>::insert(collection_id, white_list_collection);668            Ok(())669        }670671        /// Remove an address from white list.672        /// 673        /// # Permissions674        /// 675        /// * Collection Owner676        /// * Collection Admin677        /// 678        /// # Arguments679        /// 680        /// * collection_id.681        /// 682        /// * address.683        #[weight = T::WeightInfo::remove_from_white_list()]684        pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{685686            let sender = ensure_signed(origin)?;687            Self::check_owner_or_admin_permissions(collection_id, sender)?;688689            if <WhiteList<T>>::contains_key(collection_id) {690                let mut white_list_collection = <WhiteList<T>>::get(collection_id);691                if white_list_collection.contains(&address.clone())692                {693                    white_list_collection.retain(|i| *i != address.clone());694                    <WhiteList<T>>::insert(collection_id, white_list_collection);695                }696            }697698            Ok(())699        }700701        /// Toggle between normal and white list access for the methods with access for `Anyone`.702        /// 703        /// # Permissions704        /// 705        /// * Collection Owner.706        /// 707        /// # Arguments708        /// 709        /// * collection_id.710        /// 711        /// * mode: [AccessMode]712        #[weight = T::WeightInfo::set_public_access_mode()]713        pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult714        {715            let sender = ensure_signed(origin)?;716717            Self::check_owner_permissions(collection_id, sender)?;718            let mut target_collection = <Collection<T>>::get(collection_id);719            target_collection.access = mode;720            <Collection<T>>::insert(collection_id, target_collection);721722            Ok(())723        }724725        /// Allows Anyone to create tokens if:726        /// * White List is enabled, and727        /// * Address is added to white list, and728        /// * This method was called with True parameter729        /// 730        /// # Permissions731        /// * Collection Owner732        ///733        /// # Arguments734        /// 735        /// * collection_id.736        /// 737        /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.738        #[weight = T::WeightInfo::set_mint_permission()]739        pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult740        {741            let sender = ensure_signed(origin)?;742743            Self::check_owner_permissions(collection_id, sender)?;744            let mut target_collection = <Collection<T>>::get(collection_id);745            target_collection.mint_mode = mint_permission;746            <Collection<T>>::insert(collection_id, target_collection);747748            Ok(())749        }750751        /// Change the owner of the collection.752        /// 753        /// # Permissions754        /// 755        /// * Collection Owner.756        /// 757        /// # Arguments758        /// 759        /// * collection_id.760        /// 761        /// * new_owner.762        #[weight = T::WeightInfo::change_collection_owner()]763        pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {764765            let sender = ensure_signed(origin)?;766            Self::check_owner_permissions(collection_id, sender)?;767            let mut target_collection = <Collection<T>>::get(collection_id);768            target_collection.owner = new_owner;769            <Collection<T>>::insert(collection_id, target_collection);770771            Ok(())772        }773774        /// Adds an admin of the Collection.775        /// 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. 776        /// 777        /// # Permissions778        /// 779        /// * Collection Owner.780        /// * Collection Admin.781        /// 782        /// # Arguments783        /// 784        /// * collection_id: ID of the Collection to add admin for.785        /// 786        /// * new_admin_id: Address of new admin to add.787        #[weight = T::WeightInfo::add_collection_admin()]788        pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {789790            let sender = ensure_signed(origin)?;791            Self::check_owner_or_admin_permissions(collection_id, sender)?;792            let mut admin_arr: Vec<T::AccountId> = Vec::new();793794            if <AdminList<T>>::contains_key(collection_id)795            {796                admin_arr = <AdminList<T>>::get(collection_id);797                ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);798            }799800            // Number of collection admins801            ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);802803            admin_arr.push(new_admin_id);804            <AdminList<T>>::insert(collection_id, admin_arr);805806            Ok(())807        }808809        /// 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.810        ///811        /// # Permissions812        /// 813        /// * Collection Owner.814        /// * Collection Admin.815        /// 816        /// # Arguments817        /// 818        /// * collection_id: ID of the Collection to remove admin for.819        /// 820        /// * account_id: Address of admin to remove.821        #[weight = T::WeightInfo::remove_collection_admin()]822        pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {823824            let sender = ensure_signed(origin)?;825            Self::check_owner_or_admin_permissions(collection_id, sender)?;826827            if <AdminList<T>>::contains_key(collection_id)828            {829                let mut admin_arr = <AdminList<T>>::get(collection_id);830                admin_arr.retain(|i| *i != account_id);831                <AdminList<T>>::insert(collection_id, admin_arr);832            }833834            Ok(())835        }836837        /// # Permissions838        /// 839        /// * Collection Owner840        /// 841        /// # Arguments842        /// 843        /// * collection_id.844        /// 845        /// * new_sponsor.846        #[weight = T::WeightInfo::set_collection_sponsor()]847        pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {848849            let sender = ensure_signed(origin)?;850            ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);851852            let mut target_collection = <Collection<T>>::get(collection_id);853            ensure!(sender == target_collection.owner, Error::<T>::NoPermission);854855            target_collection.sponsor = new_sponsor;856            target_collection.sponsor_confirmed = false;857            <Collection<T>>::insert(collection_id, target_collection);858859            Ok(())860        }861862        /// # Permissions863        /// 864        /// * Sponsor.865        /// 866        /// # Arguments867        /// 868        /// * collection_id.869        #[weight = T::WeightInfo::confirm_sponsorship()]870        pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {871872            let sender = ensure_signed(origin)?;873            ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);874875            let mut target_collection = <Collection<T>>::get(collection_id);876            ensure!(sender == target_collection.sponsor, Error::<T>::ConfirmUnsetSponsorFail);877878            target_collection.sponsor_confirmed = true;879            <Collection<T>>::insert(collection_id, target_collection);880881            Ok(())882        }883884        /// Switch back to pay-per-own-transaction model.885        ///886        /// # Permissions887        ///888        /// * Collection owner.889        /// 890        /// # Arguments891        /// 892        /// * collection_id.893        #[weight = T::WeightInfo::remove_collection_sponsor()]894        pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {895896            let sender = ensure_signed(origin)?;897            ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);898899            let mut target_collection = <Collection<T>>::get(collection_id);900            ensure!(sender == target_collection.owner, Error::<T>::NoPermission);901902            target_collection.sponsor = T::AccountId::default();903            target_collection.sponsor_confirmed = false;904            <Collection<T>>::insert(collection_id, target_collection);905906            Ok(())907        }908909        /// This method creates a concrete instance of NFT Collection created with CreateCollection method.910        /// 911        /// # Permissions912        /// 913        /// * Collection Owner.914        /// * Collection Admin.915        /// * Anyone if916        ///     * White List is enabled, and917        ///     * Address is added to white list, and918        ///     * MintPermission is enabled (see SetMintPermission method)919        /// 920        /// # Arguments921        /// 922        /// * collection_id: ID of the collection.923        /// 924        /// * owner: Address, initial owner of the NFT.925        ///926        /// * data: Token data to store on chain.927        // #[weight =928        // (130_000_000 as Weight)929        // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))930        // .saturating_add(RocksDbWeight::get().reads(10 as Weight))931        // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]932933        #[weight = T::WeightInfo::create_item(data.len())]934        pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {935936            let sender = ensure_signed(origin)?;937938            Self::collection_exists(collection_id)?;939940            let target_collection = <Collection<T>>::get(collection_id);941942            Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;943            Self::validate_create_item_args(&target_collection, &data)?;944            Self::create_item_no_validation(collection_id, &target_collection, owner, data)?;945946            Ok(())947        }948949        /// This method creates multiple instances of NFT Collection created with CreateCollection method.950        /// 951        /// # Permissions952        /// 953        /// * Collection Owner.954        /// * Collection Admin.955        /// * Anyone if956        ///     * White List is enabled, and957        ///     * Address is added to white list, and958        ///     * MintPermission is enabled (see SetMintPermission method)959        /// 960        /// # Arguments961        /// 962        /// * collection_id: ID of the collection.963        /// 964        /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].965        /// 966        /// * owner: Address, initial owner of the NFT.967        #[weight = T::WeightInfo::create_item(items_data.into_iter()968                               .map(|data| { data.len() })969                               .sum())]970        pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {971972            ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);973            let sender = ensure_signed(origin)?;974975            Self::collection_exists(collection_id)?;976            let target_collection = <Collection<T>>::get(collection_id);977978            Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;979980            for data in &items_data {981                Self::validate_create_item_args(&target_collection, data)?;982            }983            for data in &items_data {984                Self::create_item_no_validation(collection_id, &target_collection, owner.clone(), data.clone())?;985            }986987            Ok(())988        }989990        /// Destroys a concrete instance of NFT.991        /// 992        /// # Permissions993        /// 994        /// * Collection Owner.995        /// * Collection Admin.996        /// * Current NFT Owner.997        /// 998        /// # Arguments999        /// 1000        /// * collection_id: ID of the collection.1001        /// 1002        /// * item_id: ID of NFT to burn.1003        #[weight = T::WeightInfo::burn_item()]1004        pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {10051006            let sender = ensure_signed(origin)?;1007            Self::collection_exists(collection_id)?;10081009            // Transfer permissions check1010            let target_collection = <Collection<T>>::get(collection_id);1011            ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1012                Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1013                Error::<T>::NoPermission);10141015            if target_collection.access == AccessMode::WhiteList {1016                Self::check_white_list(collection_id, &sender)?;1017            }10181019            match target_collection.mode1020            {1021                CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1022                CollectionMode::Fungible(_)  => Self::burn_fungible_item(&sender, collection_id, value)?,1023                CollectionMode::ReFungible(_)  => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,1024                _ => ()1025            };10261027            // call event1028            Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));10291030            Ok(())1031        }10321033        /// Change ownership of the token.1034        /// 1035        /// # Permissions1036        /// 1037        /// * Collection Owner1038        /// * Collection Admin1039        /// * Current NFT owner1040        ///1041        /// # Arguments1042        /// 1043        /// * recipient: Address of token recipient.1044        /// 1045        /// * collection_id.1046        /// 1047        /// * item_id: ID of the item1048        ///     * Non-Fungible Mode: Required.1049        ///     * Fungible Mode: Ignored.1050        ///     * Re-Fungible Mode: Required.1051        /// 1052        /// * value: Amount to transfer.1053        ///     * Non-Fungible Mode: Ignored1054        ///     * Fungible Mode: Must specify transferred amount1055        ///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1056        #[weight = T::WeightInfo::transfer()]1057        pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {10581059            let sender = ensure_signed(origin)?;1060            let target_collection = <Collection<T>>::get(collection_id);10611062            // Limits check1063            Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;10641065            // Transfer permissions check1066            ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1067                Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1068                Error::<T>::NoPermission);10691070            if target_collection.access == AccessMode::WhiteList {1071                Self::check_white_list(collection_id, &sender)?;1072                Self::check_white_list(collection_id, &recipient)?;1073            }10741075            match target_collection.mode1076            {1077                CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1078                CollectionMode::Fungible(_)  => Self::transfer_fungible(collection_id, value, &sender, &recipient)?,1079                CollectionMode::ReFungible(_)  => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1080                _ => ()1081            };10821083            Ok(())1084        }10851086        /// Set, change, or remove approved address to transfer the ownership of the NFT.1087        /// 1088        /// # Permissions1089        /// 1090        /// * Collection Owner1091        /// * Collection Admin1092        /// * Current NFT owner1093        /// 1094        /// # Arguments1095        /// 1096        /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1097        /// 1098        /// * collection_id.1099        /// 1100        /// * item_id: ID of the item.1101        #[weight = T::WeightInfo::approve()]1102        pub fn approve(origin, spender: T::AccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {11031104            let sender = ensure_signed(origin)?;11051106            // Transfer permissions check1107            let target_collection = <Collection<T>>::get(collection_id);1108            ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1109                Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1110                Error::<T>::NoPermission);11111112            if target_collection.access == AccessMode::WhiteList {1113                Self::check_white_list(collection_id, &sender)?;1114                Self::check_white_list(collection_id, &spender)?;1115            }11161117            let allowance_exists = <Allowances<T>>::contains_key(collection_id, (item_id, &sender, &spender));1118            let mut allowance: u128 = amount;1119            if allowance_exists {1120                allowance += <Allowances<T>>::get(collection_id, (item_id, &sender, &spender));1121            }1122            <Allowances<T>>::insert(collection_id, (item_id, sender.clone(), spender.clone()), allowance);11231124            Ok(())1125        }1126        1127        /// 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.1128        /// 1129        /// # Permissions1130        /// * Collection Owner1131        /// * Collection Admin1132        /// * Current NFT owner1133        /// * Address approved by current NFT owner1134        /// 1135        /// # Arguments1136        /// 1137        /// * from: Address that owns token.1138        /// 1139        /// * recipient: Address of token recipient.1140        /// 1141        /// * collection_id.1142        /// 1143        /// * item_id: ID of the item.1144        /// 1145        /// * value: Amount to transfer.1146        #[weight = T::WeightInfo::transfer_from()]1147        pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {11481149            let sender = ensure_signed(origin)?;1150            let mut appoved_transfer = false;11511152            // Check approval1153            let mut approval: u128 = 0;1154            if <Allowances<T>>::contains_key(collection_id, (item_id, &from, &recipient)) {1155                approval = <Allowances<T>>::get(collection_id, (item_id, &from, &recipient));1156                ensure!(approval >= value, Error::<T>::TokenValueNotEnough);1157                appoved_transfer = true;1158            }11591160            let target_collection = <Collection<T>>::get(collection_id);11611162            // Limits check1163            Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;11641165            // Transfer permissions check         1166            ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1167                Error::<T>::NoPermission);11681169            if target_collection.access == AccessMode::WhiteList {1170                Self::check_white_list(collection_id, &sender)?;1171                Self::check_white_list(collection_id, &recipient)?;1172            }11731174            // Reduce approval by transferred amount or remove if remaining approval drops to 01175            if approval - value > 0 {1176                <Allowances<T>>::insert(collection_id, (item_id, &from, &recipient), approval - value);1177            }1178            else {1179                <Allowances<T>>::remove(collection_id, (item_id, &from, &recipient));1180            }11811182            match target_collection.mode1183            {1184                CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1185                CollectionMode::Fungible(_)  => Self::transfer_fungible(collection_id, value, &from, &recipient)?,1186                CollectionMode::ReFungible(_)  => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1187                _ => ()1188            };11891190            Ok(())1191        }11921193        #[weight = 0]1194        pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {11951196            // let no_perm_mes = "You do not have permissions to modify this collection";1197            // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1198            // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1199            // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);12001201            // // on_nft_received  call12021203            // Self::transfer(origin, collection_id, item_id, new_owner)?;12041205            Ok(())1206        }12071208        /// Set off-chain data schema.1209        /// 1210        /// # Permissions1211        /// 1212        /// * Collection Owner1213        /// * Collection Admin1214        /// 1215        /// # Arguments1216        /// 1217        /// * collection_id.1218        /// 1219        /// * schema: String representing the offchain data schema.1220        #[weight = T::WeightInfo::set_variable_meta_data()]1221        pub fn set_variable_meta_data (1222            origin,1223            collection_id: CollectionId,1224            item_id: TokenId,1225            data: Vec<u8>1226        ) -> DispatchResult {1227            let sender = ensure_signed(origin)?;1228            1229            Self::collection_exists(collection_id)?;1230            1231            ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);12321233            // Modify permissions check1234            let target_collection = <Collection<T>>::get(collection_id);1235            ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1236                Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1237                Error::<T>::NoPermission);12381239            Self::item_exists(collection_id, item_id, &target_collection.mode)?;12401241            match target_collection.mode1242            {1243                CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1244                CollectionMode::ReFungible(_)  => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1245                CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1246                _ => fail!(Error::<T>::UnexpectedCollectionType)1247            };12481249            Ok(())1250        }1251 1252        /// Set schema standard1253        /// ImageURL1254        /// Unique1255        /// 1256        /// # Permissions1257        /// 1258        /// * Collection Owner1259        /// * Collection Admin1260        /// 1261        /// # Arguments1262        /// 1263        /// * collection_id.1264        /// 1265        /// * schema: SchemaVersion: enum1266        #[weight = 0]1267        pub fn set_schema_version(1268            origin,1269            collection_id: CollectionId,1270            version: SchemaVersion1271        ) -> DispatchResult {1272            let sender = ensure_signed(origin)?;1273            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;1274            let mut target_collection = <Collection<T>>::get(collection_id);1275            target_collection.schema_version = version;1276            <Collection<T>>::insert(collection_id, target_collection);12771278            Ok(())1279        }12801281        /// Set off-chain data schema.1282        /// 1283        /// # Permissions1284        /// 1285        /// * Collection Owner1286        /// * Collection Admin1287        /// 1288        /// # Arguments1289        /// 1290        /// * collection_id.1291        /// 1292        /// * schema: String representing the offchain data schema.1293        #[weight = T::WeightInfo::set_offchain_schema()]1294        pub fn set_offchain_schema(1295            origin,1296            collection_id: CollectionId,1297            schema: Vec<u8>1298        ) -> DispatchResult {1299            let sender = ensure_signed(origin)?;1300            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13011302            let mut target_collection = <Collection<T>>::get(collection_id);1303            target_collection.offchain_schema = schema;1304            <Collection<T>>::insert(collection_id, target_collection);13051306            Ok(())1307        }13081309        /// Set const on-chain data schema.1310        /// 1311        /// # Permissions1312        /// 1313        /// * Collection Owner1314        /// * Collection Admin1315        /// 1316        /// # Arguments1317        /// 1318        /// * collection_id.1319        /// 1320        /// * schema: String representing the const on-chain data schema.1321        #[weight = T::WeightInfo::set_const_on_chain_schema()]1322        pub fn set_const_on_chain_schema (1323            origin,1324            collection_id: CollectionId,1325            schema: Vec<u8>1326        ) -> DispatchResult {1327            let sender = ensure_signed(origin)?;1328            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13291330            let mut target_collection = <Collection<T>>::get(collection_id);1331            target_collection.const_on_chain_schema = schema;1332            <Collection<T>>::insert(collection_id, target_collection);13331334            Ok(())1335        }13361337        /// Set variable on-chain data schema.1338        /// 1339        /// # Permissions1340        /// 1341        /// * Collection Owner1342        /// * Collection Admin1343        /// 1344        /// # Arguments1345        /// 1346        /// * collection_id.1347        /// 1348        /// * schema: String representing the variable on-chain data schema.1349        #[weight = T::WeightInfo::set_const_on_chain_schema()]1350        pub fn set_variable_on_chain_schema (1351            origin,1352            collection_id: CollectionId,1353            schema: Vec<u8>1354        ) -> DispatchResult {1355            let sender = ensure_signed(origin)?;1356            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13571358            let mut target_collection = <Collection<T>>::get(collection_id);1359            target_collection.variable_on_chain_schema = schema;1360            <Collection<T>>::insert(collection_id, target_collection);13611362            Ok(())1363        }13641365        // Sudo permissions function1366        #[weight = 0]1367        pub fn set_chain_limits(1368            origin,1369            limits: ChainLimits1370        ) -> DispatchResult {1371            ensure_root(origin)?;1372            <ChainLimit>::put(limits);1373            Ok(())1374        }13751376        /// Enable smart contract self-sponsoring.1377        /// 1378        /// # Permissions1379        /// 1380        /// * Contract Owner1381        /// 1382        /// # Arguments1383        /// 1384        /// * contract address1385        /// * enable flag1386        /// 1387        #[weight = T::WeightInfo::enable_contract_sponsoring()]1388        pub fn enable_contract_sponsoring(1389            origin,1390            contract_address: T::AccountId,1391            enable: bool1392        ) -> DispatchResult {13931394            let sender = ensure_signed(origin)?;13951396            #[cfg(feature = "runtime-benchmarks")]1397            <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());13981399            let mut is_owner = false;1400            if <ContractOwner<T>>::contains_key(contract_address.clone()) {1401                let owner = <ContractOwner<T>>::get(&contract_address);1402                is_owner = sender == owner;1403            }1404            ensure!(is_owner, Error::<T>::NoPermission);14051406            <ContractSelfSponsoring<T>>::insert(contract_address, enable);1407            Ok(())1408        }14091410        /// Set the rate limit for contract sponsoring to specified number of blocks.1411        /// 1412        /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1413        /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1414        /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1415        /// from contract endowment if there are at least B blocks between such transactions. 1416        /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1417        /// 1418        /// # Permissions1419        /// 1420        /// * Contract Owner1421        /// 1422        /// # Arguments1423        /// 1424        /// -`contract_address`: Address of the contract to sponsor1425        /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1426        /// 1427        #[weight = 0]1428        pub fn set_contract_sponsoring_rate_limit(1429            origin,1430            contract_address: T::AccountId,1431            rate_limit: T::BlockNumber1432        ) -> DispatchResult {1433            let sender = ensure_signed(origin)?;1434            let mut is_owner = false;1435            if <ContractOwner<T>>::contains_key(contract_address.clone()) {1436                let owner = <ContractOwner<T>>::get(&contract_address);1437                is_owner = sender == owner;1438            }1439            ensure!(is_owner, Error::<T>::NoPermission);14401441            <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1442            Ok(())1443        }14441445        #[weight = 0]1446        pub fn set_collection_limits(1447            origin,1448            collection_id: u32,1449            limits: CollectionLimits,1450        ) -> DispatchResult {1451            let sender = ensure_signed(origin)?;1452            Self::check_owner_permissions(collection_id, sender.clone())?;1453            let mut target_collection = <Collection<T>>::get(collection_id);1454            let chain_limits = ChainLimit::get();1455            let climits = target_collection.limits;14561457            // collection bounds1458            ensure!(limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1459                limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP,  1460                Error::<T>::CollectionLimitBoundsExceeded);14611462            // token_limit   check  prev1463            ensure!(climits.token_limit > limits.token_limit && 1464                limits.token_limit <= chain_limits.account_token_ownership_limit, 1465                Error::<T>::AccountTokenLimitExceeded);14661467            target_collection.limits = limits;1468            <Collection<T>>::insert(collection_id, target_collection);14691470            Ok(())1471        } 1472    }1473}14741475impl<T: Trait> Module<T> {14761477    fn is_correct_transfer(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, recipient: &T::AccountId) -> DispatchResult {14781479        // check token limit and account token limit1480        let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1481        ensure!(collection.limits.account_token_ownership_limit > account_items,  Error::<T>::AccountTokenLimitExceeded);1482        1483        Ok(())1484    }14851486    fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {14871488        // check token limit and account token limit1489        let total_items: u32 = ItemListIndex::get(collection_id);1490        let account_items: u32 = <AddressTokens<T>>::get(collection_id, owner).len() as u32;1491        ensure!(collection.limits.token_limit > total_items,  Error::<T>::CollectionTokenLimitExceeded);1492        ensure!(collection.limits.account_token_ownership_limit > account_items,  Error::<T>::AccountTokenLimitExceeded);14931494        if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1495            ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1496            Self::check_white_list(collection_id, owner)?;1497            Self::check_white_list(collection_id, sender)?;1498        }14991500        Ok(())1501    }15021503    fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1504        match target_collection.mode1505        {1506            CollectionMode::NFT => {1507                if let CreateItemData::NFT(data) = data {1508                    // check sizes1509                    ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1510                    ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1511                } else {1512                    fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1513                }1514            },1515            CollectionMode::Fungible(_) => {1516                if let CreateItemData::Fungible(_) = data {1517                } else {1518                    fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1519                }1520            },1521            CollectionMode::ReFungible(_) => {1522                if let CreateItemData::ReFungible(data) = data {15231524                    // check sizes1525                    ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1526                    ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1527                } else {1528                    fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1529                }1530            },1531            _ => { fail!(Error::<T>::UnexpectedCollectionType); }1532        };15331534        Ok(())1535    }15361537    fn create_item_no_validation(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1538        match data1539        {1540            CreateItemData::NFT(data) => {1541                let item = NftItemType {1542                    owner,1543                    const_data: data.const_data,1544                    variable_data: data.variable_data1545                };15461547                Self::add_nft_item(collection_id, item)?;1548            },1549            CreateItemData::Fungible(data) => {1550                Self::add_fungible_item(collection_id, &owner, data.value)?;1551            },1552            CreateItemData::ReFungible(data) => {1553                let mut owner_list = Vec::new();1554                let value = (10 as u128).pow(collection.decimal_points as u32);1555                owner_list.push(Ownership {owner: owner.clone(), fraction: value});15561557                let item = ReFungibleItemType {1558                    owner: owner_list,1559                    const_data: data.const_data,1560                    variable_data: data.variable_data1561                };15621563                Self::add_refungible_item(collection_id, item)?;1564            }1565        };15661567        // call event1568        Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));15691570        Ok(())1571    }15721573    fn add_fungible_item(collection_id: CollectionId, owner: &T::AccountId, value: u128) -> DispatchResult {15741575        // Does new owner already have an account?1576        let mut balance: u128 = 0;1577        if <FungibleItemList<T>>::contains_key(collection_id, owner) {1578            balance = <FungibleItemList<T>>::get(collection_id, owner).value;1579        } 15801581        // Mint 1582        let item = FungibleItemType {1583            value: balance + value1584        };1585        <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), item);15861587        // Update balance1588        let new_balance = <Balance<T>>::get(collection_id, owner)1589            .checked_add(value)1590            .ok_or(Error::<T>::NumOverflow)?;1591        <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);15921593        Ok(())1594    }15951596    fn add_refungible_item(collection_id: CollectionId, item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1597        let current_index = <ItemListIndex>::get(collection_id)1598            .checked_add(1)1599            .ok_or(Error::<T>::NumOverflow)?;1600        let itemcopy = item.clone();16011602        let value = item.owner.first().unwrap().fraction;1603        let owner = item.owner.first().unwrap().owner.clone();16041605        Self::add_token_index(collection_id, current_index, owner.clone())?;16061607        <ItemListIndex>::insert(collection_id, current_index);1608        <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);16091610        // Update balance1611        let new_balance = <Balance<T>>::get(collection_id, owner.clone())1612            .checked_add(value)1613            .ok_or(Error::<T>::NumOverflow)?;1614        <Balance<T>>::insert(collection_id, owner.clone(), new_balance);16151616        Ok(())1617    }16181619    fn add_nft_item(collection_id: CollectionId, item: NftItemType<T::AccountId>) -> DispatchResult {1620        let current_index = <ItemListIndex>::get(collection_id)1621            .checked_add(1)1622            .ok_or(Error::<T>::NumOverflow)?;16231624        let item_owner = item.owner.clone();1625        Self::add_token_index(collection_id, current_index, item.owner.clone())?;16261627        <ItemListIndex>::insert(collection_id, current_index);1628        <NftItemList<T>>::insert(collection_id, current_index, item);16291630        // Update balance1631        let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1632            .checked_add(1)1633            .ok_or(Error::<T>::NumOverflow)?;1634        <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);16351636        Ok(())1637    }16381639    fn burn_refungible_item(1640        collection_id: CollectionId,1641        item_id: TokenId,1642        owner: T::AccountId,1643    ) -> DispatchResult {1644        ensure!(1645            <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1646            Error::<T>::TokenNotFound1647        );1648        let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1649        let item = collection1650            .owner1651            .iter()1652            .filter(|&i| i.owner == owner)1653            .next()1654            .unwrap();1655        Self::remove_token_index(collection_id, item_id, owner.clone())?;16561657        // update balance1658        let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1659            .checked_sub(item.fraction)1660            .ok_or(Error::<T>::NumOverflow)?;1661        <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);16621663        <ReFungibleItemList<T>>::remove(collection_id, item_id);16641665        Ok(())1666    }16671668    fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1669        ensure!(1670            <NftItemList<T>>::contains_key(collection_id, item_id),1671            Error::<T>::TokenNotFound1672        );1673        let item = <NftItemList<T>>::get(collection_id, item_id);1674        Self::remove_token_index(collection_id, item_id, item.owner.clone())?;16751676        // update balance1677        let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1678            .checked_sub(1)1679            .ok_or(Error::<T>::NumOverflow)?;1680        <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1681        <NftItemList<T>>::remove(collection_id, item_id);16821683        Ok(())1684    }16851686    fn burn_fungible_item(owner: &T::AccountId, collection_id: CollectionId, value: u128) -> DispatchResult {1687        ensure!(1688            <FungibleItemList<T>>::contains_key(collection_id, owner),1689            Error::<T>::TokenNotFound1690        );1691        let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1692        ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);16931694        // update balance1695        let new_balance = <Balance<T>>::get(collection_id, owner)1696            .checked_sub(value)1697            .ok_or(Error::<T>::NumOverflow)?;1698        <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);16991700        if balance.value - value > 0 {1701            balance.value -= value;1702            <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1703        }1704        else {1705            <FungibleItemList<T>>::remove(collection_id, owner);1706        }17071708        Ok(())1709    }17101711    fn collection_exists(collection_id: CollectionId) -> DispatchResult {1712        ensure!(1713            <Collection<T>>::contains_key(collection_id),1714            Error::<T>::CollectionNotFound1715        );1716        Ok(())1717    }17181719    fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult {1720        Self::collection_exists(collection_id)?;17211722        let target_collection = <Collection<T>>::get(collection_id);1723        ensure!(1724            subject == target_collection.owner,1725            Error::<T>::NoPermission1726        );17271728        Ok(())1729    }17301731    fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool {1732        let target_collection = <Collection<T>>::get(collection_id);1733        let mut result: bool = subject == target_collection.owner;1734        let exists = <AdminList<T>>::contains_key(collection_id);17351736        if !result & exists {1737            if <AdminList<T>>::get(collection_id).contains(&subject) {1738                result = true1739            }1740        }17411742        result1743    }17441745    fn check_owner_or_admin_permissions(1746        collection_id: CollectionId,1747        subject: T::AccountId,1748    ) -> DispatchResult {1749        Self::collection_exists(collection_id)?;1750        let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());17511752        ensure!(1753            result,1754            Error::<T>::NoPermission1755        );1756        Ok(())1757    }17581759    fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool {1760        let target_collection = <Collection<T>>::get(collection_id);17611762        match target_collection.mode {1763            CollectionMode::NFT => {1764                <NftItemList<T>>::get(collection_id, item_id).owner == subject1765            }1766            CollectionMode::Fungible(_) => {1767                <FungibleItemList<T>>::contains_key(collection_id, &subject)1768            }1769            CollectionMode::ReFungible(_) => {1770                <ReFungibleItemList<T>>::get(collection_id, item_id)1771                    .owner1772                    .iter()1773                    .any(|i| i.owner == subject)1774            }1775            CollectionMode::Invalid => false,1776        }1777    }17781779    fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult {1780        let mes = Error::<T>::AddresNotInWhiteList;1781        ensure!(<WhiteList<T>>::contains_key(collection_id), mes);1782        let wl = <WhiteList<T>>::get(collection_id);1783        ensure!(wl.contains(address), mes);17841785        Ok(())1786    }17871788    fn transfer_fungible(1789        collection_id: CollectionId,1790        value: u128,1791        owner: &T::AccountId,1792        recipient: &T::AccountId,1793    ) -> DispatchResult {1794        ensure!(1795            <FungibleItemList<T>>::contains_key(collection_id, owner),1796            Error::<T>::TokenNotFound1797        );17981799        let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1800        ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);18011802        // Send balance to recipient (updates balanceOf of recipient)1803        Self::add_fungible_item(collection_id, recipient, value)?;18041805        // update balanceOf of sender1806        <Balance<T>>::insert(collection_id, (*owner).clone(), balance.value - value);18071808        // Reduce or remove sender1809        if balance.value == value {1810            <FungibleItemList<T>>::remove(collection_id, owner);1811        }1812        else {1813            balance.value -= value;1814            <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1815        }18161817        Ok(())1818    }18191820    fn transfer_refungible(1821        collection_id: CollectionId,1822        item_id: TokenId,1823        value: u128,1824        owner: T::AccountId,1825        new_owner: T::AccountId,1826    ) -> DispatchResult {1827        ensure!(1828            <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1829            Error::<T>::TokenNotFound1830        );18311832        let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1833        let item = full_item1834            .owner1835            .iter()1836            .filter(|i| i.owner == owner)1837            .next()1838            .ok_or(Error::<T>::NumOverflow)?;1839        let amount = item.fraction;18401841        ensure!(amount >= value, Error::<T>::TokenValueTooLow);18421843        // update balance1844        let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1845            .checked_sub(value)1846            .ok_or(Error::<T>::NumOverflow)?;1847        <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);18481849        let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())1850            .checked_add(value)1851            .ok_or(Error::<T>::NumOverflow)?;1852        <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);18531854        let old_owner = item.owner.clone();1855        let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);18561857        // transfer1858        if amount == value && !new_owner_has_account {1859            // change owner1860            // new owner do not have account1861            let mut new_full_item = full_item.clone();1862            new_full_item1863                .owner1864                .iter_mut()1865                .find(|i| i.owner == owner)1866                .unwrap()1867                .owner = new_owner.clone();1868            <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);18691870            // update index collection1871            Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1872        } else {1873            let mut new_full_item = full_item.clone();1874            new_full_item1875                .owner1876                .iter_mut()1877                .find(|i| i.owner == owner)1878                .unwrap()1879                .fraction -= value;18801881            // separate amount1882            if new_owner_has_account {1883                // new owner has account1884                new_full_item1885                    .owner1886                    .iter_mut()1887                    .find(|i| i.owner == new_owner)1888                    .unwrap()1889                    .fraction += value;1890            } else {1891                // new owner do not have account1892                new_full_item.owner.push(Ownership {1893                    owner: new_owner.clone(),1894                    fraction: value,1895                });1896                Self::add_token_index(collection_id, item_id, new_owner.clone())?;1897            }18981899            <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1900        }19011902        Ok(())1903    }19041905    fn transfer_nft(1906        collection_id: CollectionId,1907        item_id: TokenId,1908        sender: T::AccountId,1909        new_owner: T::AccountId,1910    ) -> DispatchResult {1911        ensure!(1912            <NftItemList<T>>::contains_key(collection_id, item_id),1913            Error::<T>::TokenNotFound1914        );19151916        let mut item = <NftItemList<T>>::get(collection_id, item_id);19171918        ensure!(1919            sender == item.owner,1920            Error::<T>::MustBeTokenOwner1921        );19221923        // update balance1924        let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1925            .checked_sub(1)1926            .ok_or(Error::<T>::NumOverflow)?;1927        <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19281929        let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())1930            .checked_add(1)1931            .ok_or(Error::<T>::NumOverflow)?;1932        <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);19331934        // change owner1935        let old_owner = item.owner.clone();1936        item.owner = new_owner.clone();1937        <NftItemList<T>>::insert(collection_id, item_id, item);19381939        // update index collection1940        Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;19411942        Ok(())1943    }1944    1945    fn item_exists(1946        collection_id: CollectionId,1947        item_id: TokenId,1948        mode: &CollectionMode1949    ) -> DispatchResult {1950        match mode {1951            CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1952            CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1953            _ => ()1954        };1955        1956        Ok(())1957    }19581959    fn set_re_fungible_variable_data(1960        collection_id: CollectionId,1961        item_id: TokenId,1962        data: Vec<u8>1963    ) -> DispatchResult {1964        let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);19651966        item.variable_data = data;19671968        <ReFungibleItemList<T>>::insert(collection_id, item_id, item);19691970        Ok(())1971    }19721973    fn set_nft_variable_data(1974        collection_id: CollectionId,1975        item_id: TokenId,1976        data: Vec<u8>1977    ) -> DispatchResult {1978        let mut item = <NftItemList<T>>::get(collection_id, item_id);1979        1980        item.variable_data = data;19811982        <NftItemList<T>>::insert(collection_id, item_id, item);1983        1984        Ok(())1985    }19861987    fn init_collection(item: &CollectionType<T::AccountId>) {1988        // check params1989        assert!(1990            item.decimal_points <= MAX_DECIMAL_POINTS,1991            "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"1992        );1993        assert!(1994            item.name.len() <= 64,1995            "Collection name can not be longer than 63 char"1996        );1997        assert!(1998            item.name.len() <= 256,1999            "Collection description can not be longer than 255 char"2000        );2001        assert!(2002            item.token_prefix.len() <= 16,2003            "Token prefix can not be longer than 15 char"2004        );20052006        // Generate next collection ID2007        let next_id = CreatedCollectionCount::get()2008            .checked_add(1)2009            .unwrap();20102011        CreatedCollectionCount::put(next_id);2012    }20132014    fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::AccountId>) {2015        let current_index = <ItemListIndex>::get(collection_id)2016            .checked_add(1)2017            .unwrap();20182019        let item_owner = item.owner.clone();2020        Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();20212022        <ItemListIndex>::insert(collection_id, current_index);20232024        // Update balance2025        let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())2026            .checked_add(1)2027            .unwrap();2028        <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2029    }20302031    fn init_fungible_token(collection_id: CollectionId, owner: &T::AccountId, item: &FungibleItemType) {2032        let current_index = <ItemListIndex>::get(collection_id)2033            .checked_add(1)2034            .unwrap();20352036        Self::add_token_index(collection_id, current_index, (*owner).clone()).unwrap();20372038        <ItemListIndex>::insert(collection_id, current_index);20392040        // Update balance2041        let new_balance = <Balance<T>>::get(collection_id, owner)2042            .checked_add(item.value)2043            .unwrap();2044        <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);2045    }20462047    fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::AccountId>) {2048        let current_index = <ItemListIndex>::get(collection_id)2049            .checked_add(1)2050            .unwrap();20512052        let value = item.owner.first().unwrap().fraction;2053        let owner = item.owner.first().unwrap().owner.clone();20542055        Self::add_token_index(collection_id, current_index, owner.clone()).unwrap();20562057        <ItemListIndex>::insert(collection_id, current_index);20582059        // Update balance2060        let new_balance = <Balance<T>>::get(collection_id, owner.clone())2061            .checked_add(value)2062            .unwrap();2063        <Balance<T>>::insert(collection_id, owner.clone(), new_balance);2064    }20652066    fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: T::AccountId) -> DispatchResult {20672068        // add to account limit2069        if <AccountItemCount<T>>::contains_key(owner.clone()) {20702071            // bound Owned tokens by a single address2072            let count = <AccountItemCount<T>>::get(owner.clone());2073            ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);20742075            <AccountItemCount<T>>::insert(owner.clone(), count2076                .checked_add(1)2077                .ok_or(Error::<T>::NumOverflow)?);2078        }2079        else {2080            <AccountItemCount<T>>::insert(owner.clone(), 1);2081        }20822083        let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2084        if list_exists {2085            let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2086            let item_contains = list.contains(&item_index.clone());20872088            if !item_contains {2089                list.push(item_index.clone());2090            }20912092            <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2093        } else {2094            let mut itm = Vec::new();2095            itm.push(item_index.clone());2096            <AddressTokens<T>>::insert(collection_id, owner, itm);2097            2098        }20992100        Ok(())2101    }21022103    fn remove_token_index(2104        collection_id: CollectionId,2105        item_index: TokenId,2106        owner: T::AccountId,2107    ) -> DispatchResult {21082109        // update counter2110        <AccountItemCount<T>>::insert(owner.clone(), 2111            <AccountItemCount<T>>::get(owner.clone())2112            .checked_sub(1)2113            .ok_or(Error::<T>::NumOverflow)?);211421152116        let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2117        if list_exists {2118            let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2119            let item_contains = list.contains(&item_index.clone());21202121            if item_contains {2122                list.retain(|&item| item != item_index);2123                <AddressTokens<T>>::insert(collection_id, owner, list);2124            }2125        }21262127        Ok(())2128    }21292130    fn move_token_index(2131        collection_id: CollectionId,2132        item_index: TokenId,2133        old_owner: T::AccountId,2134        new_owner: T::AccountId,2135    ) -> DispatchResult {2136        Self::remove_token_index(collection_id, item_index, old_owner)?;2137        Self::add_token_index(collection_id, item_index, new_owner)?;21382139        Ok(())2140    }2141}21422143////////////////////////////////////////////////////////////////////////////////////////////////////2144// Economic models2145// #region21462147/// Fee multiplier.2148pub type Multiplier = FixedU128;21492150type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2151    <T as system::Trait>::AccountId,2152>>::Balance;2153type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2154    <T as system::Trait>::AccountId,2155>>::NegativeImbalance;21562157/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2158/// in the queue.2159#[derive(Encode, Decode, Clone, Eq, PartialEq)]2160pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(2161    #[codec(compact)] BalanceOf<T>2162);21632164impl<T: Trait + Send + Sync> sp_std::fmt::Debug2165    for ChargeTransactionPayment<T>2166{2167    #[cfg(feature = "std")]2168    fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2169        write!(f, "ChargeTransactionPayment<{:?}>", self.0)2170    }2171    #[cfg(not(feature = "std"))]2172    fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2173        Ok(())2174    }2175}21762177impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2178where2179    T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2180    BalanceOf<T>: Send + Sync + FixedPointOperand,2181{2182    /// utility constructor. Used only in client/factory code.2183    pub fn from(fee: BalanceOf<T>) -> Self {2184        Self(fee)2185    }21862187    pub fn traditional_fee(2188        len: usize,2189        info: &DispatchInfoOf<T::Call>,2190        tip: BalanceOf<T>,2191    ) -> BalanceOf<T>2192    where2193        T::Call: Dispatchable<Info = DispatchInfo>,2194    {2195        <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2196    }21972198	fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2199		let weight_saturation = T::MaximumBlockWeight::get() / info.weight.max(1);2200		let len_saturation = T::MaximumBlockLength::get() as u64 / (len as u64).max(1);2201		let coefficient: BalanceOf<T> = weight_saturation.min(len_saturation).saturated_into::<BalanceOf<T>>();2202		final_fee.saturating_mul(coefficient).saturated_into::<TransactionPriority>()2203	}22042205    fn withdraw_fee(2206        &self,2207        who: &T::AccountId,2208        call: &T::Call,2209        info: &DispatchInfoOf<T::Call>,2210        len: usize,2211    ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2212        let tip = self.0;22132214        // Set fee based on call type. Creating collection costs 1 Unique.2215        // All other transactions have traditional fees so far2216        // let fee = match call.is_sub_type() {2217        //     Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2218        //     _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2219        //                                                 // _ => <BalanceOf<T>>::from(100)2220        // };2221        let fee = Self::traditional_fee(len, info, tip);22222223        // Only mess with balances if fee is not zero.2224        if fee.is_zero() {2225            return Ok((fee, None));2226        }22272228        // Determine who is paying transaction fee based on ecnomic model2229        // Parse call to extract collection ID and access collection sponsor2230        let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2231            Some(Call::create_item(collection_id, _owner, _properties)) => {22322233                // check free create limit2234                if (<Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)) &&2235                   (<Collection<T>>::get(collection_id).sponsor_confirmed)2236                {2237                    <Collection<T>>::get(collection_id).sponsor2238                } else {2239                    T::AccountId::default()2240                }2241            }2242            Some(Call::transfer(_new_owner, collection_id, item_id, _value)) => {2243                2244                let mut sponsor_transfer = false;2245                if <Collection<T>>::get(collection_id).sponsor_confirmed {22462247                    let collection_limits = <Collection<T>>::get(collection_id).limits;2248                    let collection_mode = <Collection<T>>::get(collection_id).mode;2249    2250                    // sponsor timeout2251                    let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2252                    sponsor_transfer = match collection_mode {2253                        CollectionMode::NFT => {2254    2255                            // get correct limit2256                            let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2257                                collection_limits.sponsor_transfer_timeout2258                            } else {2259                                ChainLimit::get().nft_sponsor_transfer_timeout2260                            };2261    2262                            let mut sponsored = true;2263                            if <NftTransferBasket<T>>::contains_key(collection_id, item_id) {2264                                let last_tx_block = <NftTransferBasket<T>>::get(collection_id, item_id);2265                                let limit_time = last_tx_block + limit.into();2266                                if block_number <= limit_time {2267                                    sponsored = false;2268                                }2269                            }2270                            if sponsored {2271                                <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);2272                            }22732274                            sponsored2275                        }2276                        CollectionMode::Fungible(_) => {2277    2278                            // get correct limit2279                            let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2280                                collection_limits.sponsor_transfer_timeout2281                            } else {2282                                ChainLimit::get().fungible_sponsor_transfer_timeout2283                            };2284    2285                            let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2286                            let mut sponsored = true;2287                            if <FungibleTransferBasket<T>>::contains_key(collection_id, who) {2288                                let last_tx_block = <FungibleTransferBasket<T>>::get(collection_id, who);2289                                let limit_time = last_tx_block + limit.into();2290                                if block_number <= limit_time {2291                                    sponsored = false;2292                                }2293                            }2294                            if sponsored {2295                                <FungibleTransferBasket<T>>::insert(collection_id, who, block_number);2296                            }22972298                            sponsored2299                        }2300                        CollectionMode::ReFungible(_) => {2301    2302                            // get correct limit2303                            let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2304                                collection_limits.sponsor_transfer_timeout2305                            } else {2306                                ChainLimit::get().refungible_sponsor_transfer_timeout2307                            };2308    2309                            let mut sponsored = true;2310                            if <ReFungibleTransferBasket<T>>::contains_key(collection_id, item_id) {2311                                let last_tx_block = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);2312                                let limit_time = last_tx_block + limit.into();2313                                if block_number <= limit_time {2314                                    sponsored = false;2315                                }2316                            }2317                            if sponsored {2318                                <ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);2319                            }23202321                            sponsored2322                        }2323                        _ => {2324                            false2325                        },2326                    };2327                }23282329                if !sponsor_transfer {2330                    T::AccountId::default()2331                } else {2332                    <Collection<T>>::get(collection_id).sponsor2333                }2334            }23352336            _ => T::AccountId::default(),2337        };23382339        // Sponsor smart contracts2340        sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {23412342            // On instantiation: set the contract owner2343            Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {23442345                let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2346                    code_hash,2347                    &data,2348                    &who,2349                );2350                <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());23512352                T::AccountId::default()2353            },23542355            // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2356            Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {23572358                let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());23592360                let mut sponsor_transfer = false;2361                if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2362                    let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2363                    let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2364                    let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2365                    let limit_time = last_tx_block + rate_limit;23662367                    if block_number >= limit_time {2368                        <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2369                        sponsor_transfer = true;2370                    }2371                } else {2372                    sponsor_transfer = false;2373                }2374               2375                2376                let mut sp = T::AccountId::default();2377                if sponsor_transfer {2378                    if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2379                        if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2380                            sp = called_contract;2381                        }2382                    }2383                }23842385                sp2386            },23872388            _ => sponsor,2389        };23902391        let mut who_pays_fee: T::AccountId = sponsor.clone();2392        if sponsor == T::AccountId::default() {2393            who_pays_fee = who.clone();2394        }23952396        match <T as transaction_payment::Trait>::Currency::withdraw(2397            &who_pays_fee,2398            fee,2399            if tip.is_zero() {2400                WithdrawReason::TransactionPayment.into()2401            } else {2402                WithdrawReason::TransactionPayment | WithdrawReason::Tip2403            },2404            ExistenceRequirement::KeepAlive,2405        ) {2406            Ok(imbalance) => Ok((fee, Some(imbalance))),2407            Err(_) => Err(InvalidTransaction::Payment.into()),2408        }2409    }2410}241124122413impl<T: Trait + Send + Sync> SignedExtension2414    for ChargeTransactionPayment<T>2415where2416    BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2417    T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2418{2419    const IDENTIFIER: &'static str = "ChargeTransactionPayment";2420    type AccountId = T::AccountId;2421    type Call = T::Call;2422    type AdditionalSigned = ();2423    type Pre = (2424        BalanceOf<T>,2425        Self::AccountId,2426        Option<NegativeImbalanceOf<T>>,2427        BalanceOf<T>,2428    );2429    fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2430        Ok(())2431    }24322433    fn validate(2434        &self,2435        who: &Self::AccountId,2436        call: &Self::Call,2437        info: &DispatchInfoOf<Self::Call>,2438        len: usize,2439    ) -> TransactionValidity {2440		let (fee, _) = self.withdraw_fee(who, call, info, len)?;2441		Ok(ValidTransaction {2442			priority: Self::get_priority(len, info, fee),2443			..Default::default()2444		})2445    }24462447    fn pre_dispatch(2448        self,2449        who: &Self::AccountId,2450        call: &Self::Call,2451        info: &DispatchInfoOf<Self::Call>,2452        len: usize,2453    ) -> Result<Self::Pre, TransactionValidityError> {2454        let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2455        Ok((self.0, who.clone(), imbalance, fee))2456    }24572458    fn post_dispatch(2459        pre: Self::Pre,2460        info: &DispatchInfoOf<Self::Call>,2461        post_info: &PostDispatchInfoOf<Self::Call>,2462        len: usize,2463        _result: &DispatchResult,2464    ) -> Result<(), TransactionValidityError> {2465        let (tip, who, imbalance, fee) = pre;2466        if let Some(payed) = imbalance {2467            let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2468                len as u32, info, post_info, tip,2469            );2470            let refund = fee.saturating_sub(actual_fee);2471            let actual_payment =2472                match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2473                    &who, refund,2474                ) {2475                    Ok(refund_imbalance) => {2476                        // The refund cannot be larger than the up front payed max weight.2477                        // `PostDispatchInfo::calc_unspent` guards against such a case.2478                        match payed.offset(refund_imbalance) {2479                            Ok(actual_payment) => actual_payment,2480                            Err(_) => return Err(InvalidTransaction::Payment.into()),2481                        }2482                    }2483                    // We do not recreate the account using the refund. The up front payment2484                    // is gone in that case.2485                    Err(_) => payed,2486                };2487            let imbalances = actual_payment.split(tip);2488            <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2489                Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2490            );2491        }2492        Ok(())2493    }2494}24952496// #endregion
modifiedpallets/nft/src/tests.rsdiffbeforeafterboth
--- a/pallets/nft/src/tests.rs
+++ b/pallets/nft/src/tests.rs
@@ -960,7 +960,7 @@
 
         let origin1 = Origin::signed(1);
         assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), collection_id, 2));
-        assert_eq!(TemplateModule::white_list(collection_id)[0], 2);
+        assert_eq!(TemplateModule::white_list(collection_id, 2), true);
     });
 }
 
@@ -975,7 +975,7 @@
 
         assert_ok!(TemplateModule::add_collection_admin(origin1.clone(), collection_id, 2));
         assert_ok!(TemplateModule::add_to_white_list(origin2.clone(), collection_id, 3));
-        assert_eq!(TemplateModule::white_list(collection_id)[0], 3);
+        assert_eq!(TemplateModule::white_list(collection_id, 3), true);
     });
 }
 
@@ -1035,8 +1035,7 @@
         
         assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), collection_id, 2));
         assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), collection_id, 2));
-        assert_eq!(TemplateModule::white_list(collection_id)[0], 2);
-        assert_eq!(TemplateModule::white_list(collection_id).len(), 1);
+        assert_eq!(TemplateModule::white_list(collection_id, 2), true);
     });
 }
 
@@ -1054,7 +1053,7 @@
             collection_id,
             2
         ));
-        assert_eq!(TemplateModule::white_list(collection_id).len(), 0);
+        assert_eq!(TemplateModule::white_list(collection_id, 2), false);
     });
 }
 
@@ -1075,7 +1074,7 @@
             collection_id,
             3
         ));
-        assert_eq!(TemplateModule::white_list(collection_id).len(), 0);
+        assert_eq!(TemplateModule::white_list(collection_id, 3), false);
     });
 }
 
@@ -1093,7 +1092,7 @@
             TemplateModule::remove_from_white_list(origin2.clone(), collection_id, 2),
             Error::<Test>::NoPermission
         );
-        assert_eq!(TemplateModule::white_list(collection_id)[0], 2);
+        assert_eq!(TemplateModule::white_list(collection_id, 2), true);
     });
 }
 
@@ -1125,7 +1124,7 @@
             TemplateModule::remove_from_white_list(origin2.clone(), collection_id, 2),
             Error::<Test>::CollectionNotFound
         );
-        assert_eq!(TemplateModule::white_list(collection_id).len(), 0);
+        assert_eq!(TemplateModule::white_list(collection_id, 2), false);
     });
 }
 
@@ -1149,7 +1148,7 @@
             collection_id,
             2
         ));
-        assert_eq!(TemplateModule::white_list(collection_id).len(), 0);
+        assert_eq!(TemplateModule::white_list(collection_id, 2), false);
     });
 }
 
modifiedtests/src/contracts.test.tsdiffbeforeafterboth
--- a/tests/src/contracts.test.ts
+++ b/tests/src/contracts.test.ts
@@ -1,16 +1,15 @@
-//
-// This file is subject to the terms and conditions defined in
-// file 'LICENSE', which is part of this source code package.
-//
-
-import { ApiPromise } from "@polkadot/api";
-import { expect } from "chai";
-import { default as usingApi, submitTransactionAsync } from "./substrate/substrate-api";
+import chai from "chai";
+import chaiAsPromised from 'chai-as-promised';
+import usingApi, { submitTransactionAsync, submitTransactionExpectFailAsync } from "./substrate/substrate-api";
 import fs from "fs";
-import { Abi, BlueprintPromise, CodePromise } from "@polkadot/api-contract";
+import { Abi, BlueprintPromise as Blueprint, CodePromise, ContractPromise as Contract } from "@polkadot/api-contract";
 import { IKeyringPair } from "@polkadot/types/types";
-import { Keyring } from "@polkadot/api";
+import { ApiPromise, Keyring } from "@polkadot/api";
 import { ApiTypes, SubmittableExtrinsic } from "@polkadot/api/types";
+import privateKey from "./substrate/privateKey";
+
+chai.use(chaiAsPromised);
+const expect = chai.expect;
 import { BigNumber } from 'bignumber.js';
 import { findUnusedAddress } from './util/helpers'
 
@@ -18,8 +17,8 @@
 const gasLimit = 3000n * 1000000n;
 const endowment = `1000000000000000`;
 
-function deployBlueprint(alice: IKeyringPair, code: CodePromise): Promise<BlueprintPromise> {
-  return new Promise<BlueprintPromise>(async (resolve, reject) => {
+function deployBlueprint(alice: IKeyringPair, code: CodePromise): Promise<Blueprint> {
+  return new Promise<Blueprint>(async (resolve, reject) => {
     const unsub = await code
       .createBlueprint()
       .signAndSend(alice, (result) => {
@@ -32,7 +31,7 @@
   });
 }
 
-function deployContract(alice: IKeyringPair, blueprint: BlueprintPromise) : Promise<any> {
+function deployContract(alice: IKeyringPair, blueprint: Blueprint) : Promise<any> {
   return new Promise<any>(async (resolve, reject) => {
     const initValue = true;
 
@@ -62,39 +61,112 @@
   return deployer;
 }
 
-describe('Contracts smoke test', () => {
+async function deployFlipper(api: ApiPromise): Promise<[Contract, IKeyringPair]> {
+  const metadata = JSON.parse(fs.readFileSync('./src/flipper/metadata.json').toString('utf-8'));
+  const abi = new Abi(metadata);
+
+  const deployer = await prepareDeployer(api);
+
+  const wasm = fs.readFileSync('./src/flipper/flipper.wasm');
+
+  const code = new CodePromise(api, abi, wasm);
+
+  const blueprint = await deployBlueprint(deployer, code);
+  const contract = (await deployContract(deployer, blueprint))['contract'] as Contract;
+
+  const initialGetResponse = await getFlipValue(contract, deployer);
+  expect(initialGetResponse).to.be.true;
+
+  return [contract, deployer];
+}
+
+async function getFlipValue(contract: Contract, deployer: IKeyringPair) {
+  const result = await contract.query.get(deployer.address, value, gasLimit);
+
+  if(!result.result.isSuccess) {
+    throw `Failed to get flipper value`;
+  }
+  return (result.result.asSuccess.data[0] == 0x00) ? false : true;
+}
+
+describe('Contracts', () => {
   it(`Can deploy smart contract Flipper, instantiate it and call it's get and flip messages.`, async () => {
     await usingApi(async api => {
-      const deployer = await prepareDeployer(api);
+      const [contract, deployer] = await deployFlipper(api);
+      const initialGetResponse = await getFlipValue(contract, deployer);
+
+      const bob = privateKey("//Bob");
+      const flip = contract.exec('flip', value, gasLimit);
+      await submitTransactionAsync(bob, flip);
+
+      const afterFlipGetResponse = await getFlipValue(contract, deployer);
+      expect(afterFlipGetResponse).not.to.be.eq(initialGetResponse, 'Flipping should change value.');
+    });
+  });
+
+  it(`Whitelisted account can call contract.`, async () => {
+    await usingApi(async api => {
+      const bob = privateKey("//Bob");
       
-      const wasm = fs.readFileSync('./src/flipper/flipper.wasm');
-      
-      const metadata = JSON.parse(fs.readFileSync('./src/flipper/metadata.json').toString('utf-8'));
-      const abi = new Abi(metadata);
+      const [contract, deployer] = await deployFlipper(api);
+      const consoleError = console.error;
+      console.error = (...data: any[]) => {
+      };
 
-      const code = new CodePromise(api, abi, wasm);
+      let expectedFlipValue = await getFlipValue(contract, deployer);
 
-      const blueprint = await deployBlueprint(deployer, code);
-      const contract = (await deployContract(deployer, blueprint))['contract'];
+      const flip = contract.exec('flip', value, gasLimit);
+      await submitTransactionAsync(bob, flip);
+      expectedFlipValue = !expectedFlipValue;
+      const afterFlip = await getFlipValue(contract,deployer);
+      expect(afterFlip).to.be.eq(expectedFlipValue, `Anyone can call new contract.`);
 
-      const getFlipValue = async () => {
-        const result = await contract.query.get(deployer.address, value, gasLimit);
+      const deployerCanFlip = async () => {
+        expectedFlipValue = !expectedFlipValue;
+        const deployerFlip = contract.exec('flip', value, gasLimit);
+        await submitTransactionAsync(deployer, deployerFlip);
+        const aliceFlip1Response = await getFlipValue(contract, deployer);
+        expect(aliceFlip1Response).to.be.eq(expectedFlipValue, `Deployer always can flip.`);
+      };
+      await deployerCanFlip();
 
-        if(!result.result.isSuccess) {
-          throw `Failed to get flipper value`;
-        }
-        return (result.result.asSuccess.data[0] == 0x00) ? false : true;
-      }
+      const enableWhiteListTx = api.tx.nft.toggleContractWhiteList(contract.address, true);
+      const enableResult = await submitTransactionAsync(deployer, enableWhiteListTx);
+      const flipWithEnabledWhiteList = contract.exec('flip', value, gasLimit);
+      await expect(submitTransactionExpectFailAsync(bob, flipWithEnabledWhiteList)).to.be.rejected;
+      const flipValueAfterEnableWhiteList = await getFlipValue(contract, deployer);
+      expect(flipValueAfterEnableWhiteList).to.be.eq(expectedFlipValue, `Enabling whitelist doesn't make it possible to call contract for everyone.`);
 
-      const initialGetResponse = await getFlipValue();
-      expect(initialGetResponse).to.be.true;
+      await deployerCanFlip();
 
-      const flip = contract.exec('flip', value, gasLimit);
-      await submitTransactionAsync(deployer, flip);
+      const addBobToWhiteListTx = api.tx.nft.addToContractWhiteList(contract.address, bob.address);
+      const addBobResult = await submitTransactionAsync(deployer, addBobToWhiteListTx);
+      const flipWithWhitelistedBob = contract.exec('flip', value, gasLimit);
+      await submitTransactionAsync(bob, flipWithWhitelistedBob);
+      expectedFlipValue = !expectedFlipValue;
+      const flipAfterWhiteListed = await getFlipValue(contract,deployer);
+      expect(flipAfterWhiteListed).to.be.eq(expectedFlipValue, `Bob was whitelisted, now he can flip.`);
 
-      const afterFlipGetResponse = await getFlipValue();
+      await deployerCanFlip();
 
-      expect(afterFlipGetResponse).to.be.false;
+      const removeBobFromWhiteListTx = api.tx.nft.removeFromContractWhiteList(contract.address, bob.address);
+      const removeBobResult = await submitTransactionAsync(deployer, removeBobFromWhiteListTx);
+      const bobRemoved = contract.exec('flip', value, gasLimit);
+      await expect(submitTransactionExpectFailAsync(bob, bobRemoved)).to.be.rejected;
+      const afterBobRemoved = await getFlipValue(contract, deployer);
+      expect(afterBobRemoved).to.be.eq(expectedFlipValue, `Bob can't call contract, now when he is removeed from white list.`);
+
+      await deployerCanFlip();
+
+      const disableWhiteListTx = api.tx.nft.toggleContractWhiteList(contract.address, false);
+      const disableWhiteListResult = await submitTransactionAsync(deployer, disableWhiteListTx);
+      const whiteListDisabledFlip = contract.exec('flip', value, gasLimit);
+      await submitTransactionAsync(bob, whiteListDisabledFlip);
+      expectedFlipValue = !expectedFlipValue;
+      const afterWhiteListDisabled = await getFlipValue(contract,deployer);
+      expect(afterWhiteListDisabled).to.be.eq(expectedFlipValue, `Anyone can call contract with disabled whitelist.`);
+
+      console.error = consoleError;
     });
   });
 
modifiedtests/src/substrate/substrate-api.tsdiffbeforeafterboth
--- a/tests/src/substrate/substrate-api.ts
+++ b/tests/src/substrate/substrate-api.ts
@@ -56,3 +56,25 @@
     }
   });
 }
+
+export function submitTransactionExpectFailAsync(sender: IKeyringPair, transaction: SubmittableExtrinsic<ApiTypes>): Promise<EventRecord[]> {
+  return new Promise(async function(resolve, reject) {
+    try {
+      await transaction.signAndSend(sender, ({ events = [], status }) => {
+        if (status.isReady) {
+          // nothing to do
+          // console.log(`Current tx status is Ready`);
+        } else if (status.isBroadcast) {
+          // nothing to do
+          // console.log(`Current tx status is Broadcast`);
+        } else if (status.isInBlock || status.isFinalized) {
+          resolve(events);
+        } else {
+          reject("Transaction failed");
+        }
+      });
+    } catch (e) {
+      reject(e);
+    }
+  });
+}
\ No newline at end of file