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

difftreelog

source

pallets/nft/src/lib.rs82.2 KiBsourcehistory
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)]910extern crate alloc;1112pub use serde::{Serialize, Deserialize};1314pub use frame_support::{15    construct_runtime, decl_event, decl_module, decl_storage, decl_error,16    dispatch::DispatchResult,17    ensure, fail, parameter_types,18    traits::{19        Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,20        Randomness, IsSubType, WithdrawReasons,21    },22    weights::{23        constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},24        DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,25        WeightToFeePolynomial, DispatchClass,26    },27    StorageValue,28    transactional,29};3031use frame_system::{self as system, ensure_signed, ensure_root};32use sp_core::H160;33use sp_runtime::sp_std::prelude::Vec;34use core::ops::{Deref, DerefMut};35use core::cell::RefCell;36use nft_data_structs::{37    MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, MAX_REFUNGIBLE_PIECES,38	AccessMode, ChainLimits, Collection, CreateItemData, CollectionLimits,39    CollectionId, CollectionMode, TokenId, 40    SchemaVersion, SponsorshipState, Ownership,41    NftItemType, FungibleItemType, ReFungibleItemType42};43use pallet_ethereum::EthereumTransactionSender;4445#[cfg(test)]46mod mock;4748#[cfg(test)]49mod tests;5051mod default_weights;52mod eth;5354pub use eth::NftErcSupport;55pub use eth::account::*;56use eth::erc::{ERC20Events, ERC721Events};5758#[cfg(feature = "runtime-benchmarks")]59mod benchmarking;6061pub trait WeightInfo {62	fn create_collection() -> Weight;63	fn destroy_collection() -> Weight;64	fn add_to_white_list() -> Weight;65	fn remove_from_white_list() -> Weight;66    fn set_public_access_mode() -> Weight;67    fn set_mint_permission() -> Weight;68    fn change_collection_owner() -> Weight;69    fn add_collection_admin() -> Weight;70    fn remove_collection_admin() -> Weight;71    fn set_collection_sponsor() -> Weight;72    fn confirm_sponsorship() -> Weight;73    fn remove_collection_sponsor() -> Weight;74    fn create_item(s: usize) -> Weight;75    fn burn_item() -> Weight;76    fn transfer() -> Weight;77    fn approve() -> Weight;78    fn transfer_from() -> Weight;79    fn set_offchain_schema() -> Weight;80    fn set_const_on_chain_schema() -> Weight;81    fn set_variable_on_chain_schema() -> Weight;82    fn set_variable_meta_data() -> Weight;83    fn enable_contract_sponsoring() -> Weight;84    fn set_schema_version() -> Weight;85    fn set_chain_limits() -> Weight;86    fn set_contract_sponsoring_rate_limit() -> Weight;87    fn set_variable_meta_data_sponsoring_rate_limit() -> Weight;88    fn toggle_contract_white_list() -> Weight;89    fn add_to_contract_white_list() -> Weight;90    fn remove_from_contract_white_list() -> Weight;91    fn set_collection_limits() -> Weight;92}9394decl_error! {95	/// Error for non-fungible-token module.96	pub enum Error for Module<T: Config> {97        /// Total collections bound exceeded.98        TotalCollectionsLimitExceeded,99		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.100        CollectionDecimalPointLimitExceeded, 101        /// Collection name can not be longer than 63 char.102        CollectionNameLimitExceeded, 103        /// Collection description can not be longer than 255 char.104        CollectionDescriptionLimitExceeded, 105        /// Token prefix can not be longer than 15 char.106        CollectionTokenPrefixLimitExceeded,107        /// This collection does not exist.108        CollectionNotFound,109        /// Item not exists.110        TokenNotFound,111        /// Admin not found112        AdminNotFound,113        /// Arithmetic calculation overflow.114        NumOverflow,       115        /// Account already has admin role.116        AlreadyAdmin,  117        /// You do not own this collection.118        NoPermission,119        /// This address is not set as sponsor, use setCollectionSponsor first.120        ConfirmUnsetSponsorFail,121        /// Collection is not in mint mode.122        PublicMintingNotAllowed,123        /// Sender parameter and item owner must be equal.124        MustBeTokenOwner,125        /// Item balance not enough.126        TokenValueTooLow,127        /// Size of item is too large.128        NftSizeLimitExceeded,129        /// No approve found130        ApproveNotFound,131        /// Requested value more than approved.132        TokenValueNotEnough,133        /// Only approved addresses can call this method.134        ApproveRequired,135        /// Address is not in white list.136        AddresNotInWhiteList,137        /// Number of collection admins bound exceeded.138        CollectionAdminsLimitExceeded,139        /// Owned tokens by a single address bound exceeded.140        AddressOwnershipLimitExceeded,141        /// Length of items properties must be greater than 0.142        EmptyArgument,143        /// const_data exceeded data limit.144        TokenConstDataLimitExceeded,145        /// variable_data exceeded data limit.146        TokenVariableDataLimitExceeded,147        /// Not NFT item data used to mint in NFT collection.148        NotNftDataUsedToMintNftCollectionToken,149        /// Not Fungible item data used to mint in Fungible collection.150        NotFungibleDataUsedToMintFungibleCollectionToken,151        /// Not Re Fungible item data used to mint in Re Fungible collection.152        NotReFungibleDataUsedToMintReFungibleCollectionToken,153        /// Unexpected collection type.154        UnexpectedCollectionType,155        /// Can't store metadata in fungible tokens.156        CantStoreMetadataInFungibleTokens,157        /// Collection token limit exceeded158        CollectionTokenLimitExceeded,159        /// Account token limit exceeded per collection160        AccountTokenLimitExceeded,161        /// Collection limit bounds per collection exceeded162        CollectionLimitBoundsExceeded,163        /// Tried to enable permissions which are only permitted to be disabled164        OwnerPermissionsCantBeReverted,165        /// Schema data size limit bound exceeded166        SchemaDataLimitExceeded,167        /// Maximum refungibility exceeded168        WrongRefungiblePieces,169        /// createRefungible should be called with one owner170        BadCreateRefungibleCall,171        /// Gas limit exceeded172        OutOfGas,173	}174}175176pub struct CollectionHandle<T: Config> {177    pub id: CollectionId,178    collection: Collection<T>,179    logs: eth::log::LogRecorder,180    evm_address: H160,181    gas_limit: RefCell<u64>,182}183impl<T: Config> CollectionHandle<T> {184	pub fn get_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {185		<CollectionById<T>>::get(id)186			.map(|collection| Self {187				id,188				collection,189                logs: eth::log::LogRecorder::default(),190                evm_address: eth::collection_id_to_address(id),191                gas_limit: RefCell::new(gas_limit),192			})193	}194    pub fn get(id: CollectionId) -> Option<Self> {195        Self::get_with_gas_limit(id, u64::MAX)196    }197    pub fn gas_left(&self) -> u64 {198        *self.gas_limit.borrow()199    }200    pub fn consume_gas(&self, gas: u64) -> DispatchResult {201        let mut gas_limit = self.gas_limit.borrow_mut();202        if *gas_limit < gas {203            fail!(Error::<T>::OutOfGas);204        }205        *gas_limit -= gas;206        Ok(())207    }208    pub fn log(&self, log: impl evm_coder::ToLog) {209        self.logs.log(log.to_log(self.evm_address))210    }211    pub fn into_inner(self) -> Collection<T> {212        self.collection.clone()213    }214}215impl<T: Config> Deref for CollectionHandle<T> {216    type Target = Collection<T>;217218    fn deref(&self) -> &Self::Target {219        &self.collection220    }221}222223impl<T: Config> DerefMut for CollectionHandle<T> {224    fn deref_mut(&mut self) -> &mut Self::Target {225        &mut self.collection226    }227}228229pub trait Config: system::Config + Sized {230    type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;231232    /// Weight information for extrinsics in this pallet.233	type WeightInfo: WeightInfo;234235    type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;236    type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;237    type EvmWithdrawOrigin: pallet_evm::EnsureAddressOrigin<Self::Origin, Success = Self::AccountId>;238239	type CrossAccountId: CrossAccountId<Self::AccountId>;240    type Currency: Currency<Self::AccountId>;241    type CollectionCreationPrice: Get<<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance>;242    type TreasuryAccountId: Get<Self::AccountId>;243244    type EthereumChainId: Get<u64>;245    type EthereumTransactionSender: pallet_ethereum::EthereumTransactionSender;246}247248// # Used definitions249//250// ## User control levels251//252// chain-controlled - key is uncontrolled by user253//                    i.e autoincrementing index254//                    can use non-cryptographic hash255// real - key is controlled by user256//        but it is hard to generate enough colliding values, i.e owner of signed txs257//        can use non-cryptographic hash258// controlled - key is completly controlled by users259//              i.e maps with mutable keys260//              should use cryptographic hash261//262// ## User control level downgrade reasons263//264// ?1 - chain-controlled -> controlled265//      collections/tokens can be destroyed, resulting in massive holes266// ?2 - chain-controlled -> controlled267//      same as ?1, but can be only added, resulting in easier exploitation268// ?3 - real -> controlled269//      no confirmation required, so addresses can be easily generated270decl_storage! {271    trait Store for Module<T: Config> as Nft {272273        //#region Private members274        /// Id of next collection275        CreatedCollectionCount: u32;276        /// Used for migrations277        ChainVersion: u64;278        /// Id of last collection token279        /// Collection id (controlled?1)280        ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;281        //#endregion282283        //#region Chain limits struct284        pub ChainLimit get(fn chain_limit) config(): ChainLimits;285        //#endregion286287        //#region Bound counters288        /// Amount of collections destroyed, used for total amount tracking with289        /// CreatedCollectionCount290        DestroyedCollectionCount: u32;291        /// Total amount of account owned tokens (NFTs + RFTs + unique fungibles)292        /// Account id (real)293        pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;294        //#endregion295296        //#region Basic collections297        /// Collection info298        /// Collection id (controlled?1)299        pub CollectionById get(fn collection_id) config(): map hasher(blake2_128_concat) CollectionId => Option<Collection<T>> = None;300        /// List of collection admins301        /// Collection id (controlled?2)302        pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec<T::CrossAccountId>;303        /// Whitelisted collection users304        /// Collection id (controlled?2), user id (controlled?3)305        pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool;306        //#endregion307308        /// How many of collection items user have309        /// Collection id (controlled?2), account id (real)310        pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128;311312        /// Amount of items which spender can transfer out of owners account (via transferFrom)313        /// Collection id (controlled?2), (token id (controlled ?2) + owner account id (real) + spender account id (controlled?3))314        /// TODO: Off chain worker should remove from this map when token gets removed315        pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128;316317        //#region Item collections318        /// Collection id (controlled?2), token id (controlled?1)319        pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<NftItemType<T::CrossAccountId>>;320        /// Collection id (controlled?2), owner (controlled?2)321        pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType;322        /// Collection id (controlled?2), token id (controlled?1)323        pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<ReFungibleItemType<T::CrossAccountId>>;324        //#endregion325326        //#region Index list327        /// Collection id (controlled?2), tokens owner (controlled?2)328        pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;329        //#endregion330331        //#region Tokens transfer rate limit baskets332        /// (Collection id (controlled?2), who created (real))333        /// TODO: Off chain worker should remove from this map when collection gets removed334        pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;335        /// Collection id (controlled?2), token id (controlled?2)336        pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;337        /// Collection id (controlled?2), owning user (real)338        pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;339        /// Collection id (controlled?2), token id (controlled?2)340        pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;341        //#endregion342343        /// Variable metadata sponsoring344        /// Collection id (controlled?2), token id (controlled?2)345        pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;346    }347    add_extra_genesis {348        build(|config: &GenesisConfig<T>| {349            // Modification of storage350            for (_num, _c) in &config.collection_id {351                <Module<T>>::init_collection(_c);352            }353354            for (_num, _c, _i) in &config.nft_item_id {355                <Module<T>>::init_nft_token(*_c, _i);356            }357358            for (collection_id, account_id, fungible_item) in &config.fungible_item_id {359                <Module<T>>::init_fungible_token(*collection_id, &T::CrossAccountId::from_sub(account_id.clone()), fungible_item);360            }361362            for (_num, _c, _i) in &config.refungible_item_id {363                <Module<T>>::init_refungible_token(*_c, _i);364            }365        })366    }367}368369decl_event!(370    pub enum Event<T>371    where372        AccountId = <T as frame_system::Config>::AccountId,373        CrossAccountId = <T as Config>::CrossAccountId,374    {375        /// New collection was created376        /// 377        /// # Arguments378        /// 379        /// * collection_id: Globally unique identifier of newly created collection.380        /// 381        /// * mode: [CollectionMode] converted into u8.382        /// 383        /// * account_id: Collection owner.384        CollectionCreated(CollectionId, u8, AccountId),385386        /// New item was created.387        /// 388        /// # Arguments389        /// 390        /// * collection_id: Id of the collection where item was created.391        /// 392        /// * item_id: Id of an item. Unique within the collection.393        ///394        /// * recipient: Owner of newly created item 395        ItemCreated(CollectionId, TokenId, CrossAccountId),396397        /// Collection item was burned.398        /// 399        /// # Arguments400        /// 401        /// collection_id.402        /// 403        /// item_id: Identifier of burned NFT.404        ItemDestroyed(CollectionId, TokenId),405406        /// Item was transferred407        ///408        /// * collection_id: Id of collection to which item is belong409        ///410        /// * item_id: Id of an item411        ///412        /// * sender: Original owner of item413        ///414        /// * recipient: New owner of item415        ///416        /// * amount: Always 1 for NFT417        Transfer(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),418419        /// * collection_id420        ///421        /// * item_id422        ///423        /// * sender424        ///425        /// * spender426        ///427        /// * amount428        Approved(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),429    }430);431432decl_module! {433    pub struct Module<T: Config> for enum Call 434    where 435        origin: T::Origin436    {437        fn deposit_event() = default;438        type Error = Error<T>;439440        fn on_initialize(_now: T::BlockNumber) -> Weight {441            0442        }443444        /// 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.445        /// 446        /// # Permissions447        /// 448        /// * Anyone.449        /// 450        /// # Arguments451        /// 452        /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.453        /// 454        /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.455        /// 456        /// * token_prefix: UTF-8 string with token prefix.457        /// 458        /// * mode: [CollectionMode] collection type and type dependent data.459        // returns collection ID460        #[weight = <T as Config>::WeightInfo::create_collection()]461        #[transactional]462        pub fn create_collection(origin,463                                 collection_name: Vec<u16>,464                                 collection_description: Vec<u16>,465                                 token_prefix: Vec<u8>,466                                 mode: CollectionMode) -> DispatchResult {467468            // Anyone can create a collection469            let who = ensure_signed(origin)?;470471            // Take a (non-refundable) deposit of collection creation472            let mut imbalance = <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();473            imbalance.subsume(<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(474                &T::TreasuryAccountId::get(),475                T::CollectionCreationPrice::get(),476            ));477            <T as Config>::Currency::settle(478                &who,479                imbalance,480                WithdrawReasons::TRANSFER,481                ExistenceRequirement::KeepAlive,482            ).map_err(|_| Error::<T>::NoPermission)?;483484            let decimal_points = match mode {485                CollectionMode::Fungible(points) => points,486                _ => 0487            };488489            let chain_limit = ChainLimit::get();490491            let created_count = CreatedCollectionCount::get();492            let destroyed_count = DestroyedCollectionCount::get();493494            // bound Total number of collections495            ensure!(created_count - destroyed_count < chain_limit.collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);496497            // check params498            ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);499            ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);500            ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);501            ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);502503            // Generate next collection ID504            let next_id = created_count505                .checked_add(1)506                .ok_or(Error::<T>::NumOverflow)?;507508            CreatedCollectionCount::put(next_id);509510            let limits = CollectionLimits {511                sponsored_data_size: chain_limit.custom_data_limit,512                ..Default::default()513            };514515            // Create new collection516            let new_collection = Collection {517                owner: who.clone(),518                name: collection_name,519                mode: mode.clone(),520                mint_mode: false,521                access: AccessMode::Normal,522                description: collection_description,523                decimal_points: decimal_points,524                token_prefix: token_prefix,525                offchain_schema: Vec::new(),526                schema_version: SchemaVersion::ImageURL,527                sponsorship: SponsorshipState::Disabled,528                variable_on_chain_schema: Vec::new(),529                const_on_chain_schema: Vec::new(),530                limits,531            };532533            // Add new collection to map534            <CollectionById<T>>::insert(next_id, new_collection);535536            // call event537            Self::deposit_event(RawEvent::CollectionCreated(next_id, mode.into(), who));538539            Ok(())540        }541542        /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.543        /// 544        /// # Permissions545        /// 546        /// * Collection Owner.547        /// 548        /// # Arguments549        /// 550        /// * collection_id: collection to destroy.551        #[weight = <T as Config>::WeightInfo::destroy_collection()]552        #[transactional]553        pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {554555            let sender = ensure_signed(origin)?;556            let collection = Self::get_collection(collection_id)?;557            Self::check_owner_permissions(&collection, &sender)?;558            if !collection.limits.owner_can_destroy {559                fail!(Error::<T>::NoPermission);560            }561562            <AddressTokens<T>>::remove_prefix(collection_id);563            <Allowances<T>>::remove_prefix(collection_id);564            <Balance<T>>::remove_prefix(collection_id);565            <ItemListIndex>::remove(collection_id);566            <AdminList<T>>::remove(collection_id);567            <CollectionById<T>>::remove(collection_id);568            <WhiteList<T>>::remove_prefix(collection_id);569570            <NftItemList<T>>::remove_prefix(collection_id);571            <FungibleItemList<T>>::remove_prefix(collection_id);572            <ReFungibleItemList<T>>::remove_prefix(collection_id);573574            <NftTransferBasket<T>>::remove_prefix(collection_id);575            <FungibleTransferBasket<T>>::remove_prefix(collection_id);576            <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);577578            <VariableMetaDataBasket<T>>::remove_prefix(collection_id);579580            DestroyedCollectionCount::put(DestroyedCollectionCount::get()581                .checked_add(1)582                .ok_or(Error::<T>::NumOverflow)?);583584            Ok(())585        }586587        /// Add an address to white list.588        /// 589        /// # Permissions590        /// 591        /// * Collection Owner592        /// * Collection Admin593        /// 594        /// # Arguments595        /// 596        /// * collection_id.597        /// 598        /// * address.599        #[weight = <T as Config>::WeightInfo::add_to_white_list()]600        #[transactional]601        pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{602603            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);604            let collection = Self::get_collection(collection_id)?;605606            Self::toggle_white_list_internal(607                &sender,608                &collection,609                &address,610                true,611            )?;612613            Ok(())614        }615616        /// Remove an address from white list.617        /// 618        /// # Permissions619        /// 620        /// * Collection Owner621        /// * Collection Admin622        /// 623        /// # Arguments624        /// 625        /// * collection_id.626        /// 627        /// * address.628        #[weight = <T as Config>::WeightInfo::remove_from_white_list()]629        #[transactional]630        pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{631632            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);633            let collection = Self::get_collection(collection_id)?;634635            Self::toggle_white_list_internal(636                &sender,637                &collection,638                &address,639                false,640            )?;641642            Ok(())643        }644645        /// Toggle between normal and white list access for the methods with access for `Anyone`.646        /// 647        /// # Permissions648        /// 649        /// * Collection Owner.650        /// 651        /// # Arguments652        /// 653        /// * collection_id.654        /// 655        /// * mode: [AccessMode]656        #[weight = <T as Config>::WeightInfo::set_public_access_mode()]657        #[transactional]658        pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult659        {660            let sender = ensure_signed(origin)?;661662            let mut target_collection = Self::get_collection(collection_id)?;663            Self::check_owner_permissions(&target_collection, &sender)?;664            target_collection.access = mode;665            Self::save_collection(target_collection);666667            Ok(())668        }669670        /// Allows Anyone to create tokens if:671        /// * White List is enabled, and672        /// * Address is added to white list, and673        /// * This method was called with True parameter674        /// 675        /// # Permissions676        /// * Collection Owner677        ///678        /// # Arguments679        /// 680        /// * collection_id.681        /// 682        /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.683        #[weight = <T as Config>::WeightInfo::set_mint_permission()]684        #[transactional]685        pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult686        {687            let sender = ensure_signed(origin)?;688689            let mut target_collection = Self::get_collection(collection_id)?;690            Self::check_owner_permissions(&target_collection, &sender)?;691            target_collection.mint_mode = mint_permission;692            Self::save_collection(target_collection);693694            Ok(())695        }696697        /// Change the owner of the collection.698        /// 699        /// # Permissions700        /// 701        /// * Collection Owner.702        /// 703        /// # Arguments704        /// 705        /// * collection_id.706        /// 707        /// * new_owner.708        #[weight = <T as Config>::WeightInfo::change_collection_owner()]709        #[transactional]710        pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {711712            let sender = ensure_signed(origin)?;713            let mut target_collection = Self::get_collection(collection_id)?;714            Self::check_owner_permissions(&target_collection, &sender)?;715            target_collection.owner = new_owner;716            Self::save_collection(target_collection);717718            Ok(())719        }720721        /// Adds an admin of the Collection.722        /// 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. 723        /// 724        /// # Permissions725        /// 726        /// * Collection Owner.727        /// * Collection Admin.728        /// 729        /// # Arguments730        /// 731        /// * collection_id: ID of the Collection to add admin for.732        /// 733        /// * new_admin_id: Address of new admin to add.734        #[weight = <T as Config>::WeightInfo::add_collection_admin()]735        #[transactional]736        pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {737            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);738            let collection = Self::get_collection(collection_id)?;739            Self::check_owner_or_admin_permissions(&collection, &sender)?;740            let mut admin_arr = <AdminList<T>>::get(collection_id);741742            match admin_arr.binary_search(&new_admin_id) {743                Ok(_) => {},744                Err(idx) => {745                    let limits = ChainLimit::get();746                    ensure!(admin_arr.len() < limits.collections_admins_limit as usize, Error::<T>::CollectionAdminsLimitExceeded);747                    admin_arr.insert(idx, new_admin_id);748                    <AdminList<T>>::insert(collection_id, admin_arr);749                }750            }751            Ok(())752        }753754        /// 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.755        ///756        /// # Permissions757        /// 758        /// * Collection Owner.759        /// * Collection Admin.760        /// 761        /// # Arguments762        /// 763        /// * collection_id: ID of the Collection to remove admin for.764        /// 765        /// * account_id: Address of admin to remove.766        #[weight = <T as Config>::WeightInfo::remove_collection_admin()]767        #[transactional]768        pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {769            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);770            let collection = Self::get_collection(collection_id)?;771            Self::check_owner_or_admin_permissions(&collection, &sender)?;772            let mut admin_arr = <AdminList<T>>::get(collection_id);773774            match admin_arr.binary_search(&account_id) {775                Ok(idx) => {776                    admin_arr.remove(idx);777                    <AdminList<T>>::insert(collection_id, admin_arr);778                },779                Err(_) => {}780            }781            Ok(())782        }783784        /// # Permissions785        /// 786        /// * Collection Owner787        /// 788        /// # Arguments789        /// 790        /// * collection_id.791        /// 792        /// * new_sponsor.793        #[weight = <T as Config>::WeightInfo::set_collection_sponsor()]794        #[transactional]795        pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {796            let sender = ensure_signed(origin)?;797            let mut target_collection = Self::get_collection(collection_id)?;798            Self::check_owner_permissions(&target_collection, &sender)?;799800            target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);801            Self::save_collection(target_collection);802803            Ok(())804        }805806        /// # Permissions807        /// 808        /// * Sponsor.809        /// 810        /// # Arguments811        /// 812        /// * collection_id.813        #[weight = <T as Config>::WeightInfo::confirm_sponsorship()]814        #[transactional]815        pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {816            let sender = ensure_signed(origin)?;817818            let mut target_collection = Self::get_collection(collection_id)?;819            ensure!(820                target_collection.sponsorship.pending_sponsor() == Some(&sender),821                Error::<T>::ConfirmUnsetSponsorFail822            );823824            target_collection.sponsorship = SponsorshipState::Confirmed(sender);825            Self::save_collection(target_collection);826827            Ok(())828        }829830        /// Switch back to pay-per-own-transaction model.831        ///832        /// # Permissions833        ///834        /// * Collection owner.835        /// 836        /// # Arguments837        /// 838        /// * collection_id.839        #[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]840        #[transactional]841        pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {842            let sender = ensure_signed(origin)?;843844            let mut target_collection = Self::get_collection(collection_id)?;845            Self::check_owner_permissions(&target_collection, &sender)?;846847            target_collection.sponsorship = SponsorshipState::Disabled;848            Self::save_collection(target_collection);849850            Ok(())851        }852853        /// This method creates a concrete instance of NFT Collection created with CreateCollection method.854        /// 855        /// # Permissions856        /// 857        /// * Collection Owner.858        /// * Collection Admin.859        /// * Anyone if860        ///     * White List is enabled, and861        ///     * Address is added to white list, and862        ///     * MintPermission is enabled (see SetMintPermission method)863        /// 864        /// # Arguments865        /// 866        /// * collection_id: ID of the collection.867        /// 868        /// * owner: Address, initial owner of the NFT.869        ///870        /// * data: Token data to store on chain.871        // #[weight =872        // (130_000_000 as Weight)873        // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))874        // .saturating_add(RocksDbWeight::get().reads(10 as Weight))875        // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]876877        #[weight = <T as Config>::WeightInfo::create_item(data.len())]878        #[transactional]879        pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResult {880            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);881            let collection = Self::get_collection(collection_id)?;882883            Self::create_item_internal(&sender, &collection, &owner, data)?;884885            Self::submit_logs(collection)?;886            Ok(())887        }888889        /// This method creates multiple items in a collection created with CreateCollection method.890        /// 891        /// # Permissions892        /// 893        /// * Collection Owner.894        /// * Collection Admin.895        /// * Anyone if896        ///     * White List is enabled, and897        ///     * Address is added to white list, and898        ///     * MintPermission is enabled (see SetMintPermission method)899        /// 900        /// # Arguments901        /// 902        /// * collection_id: ID of the collection.903        /// 904        /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].905        /// 906        /// * owner: Address, initial owner of the NFT.907        #[weight = <T as Config>::WeightInfo::create_item(items_data.into_iter()908                               .map(|data| { data.len() })909                               .sum())]910        #[transactional]911        pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResult {912913            ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);914            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);915            let collection = Self::get_collection(collection_id)?;916917            Self::create_multiple_items_internal(&sender, &collection, &owner, items_data)?;918919            Self::submit_logs(collection)?;920            Ok(())921        }922923        /// Destroys a concrete instance of NFT.924        /// 925        /// # Permissions926        /// 927        /// * Collection Owner.928        /// * Collection Admin.929        /// * Current NFT Owner.930        /// 931        /// # Arguments932        /// 933        /// * collection_id: ID of the collection.934        /// 935        /// * item_id: ID of NFT to burn.936        #[weight = <T as Config>::WeightInfo::burn_item()]937        #[transactional]938        pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {939940            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);941            let target_collection = Self::get_collection(collection_id)?;942943            Self::burn_item_internal(&sender, &target_collection, item_id, value)?;944945            Self::submit_logs(target_collection)?;946            Ok(())947        }948949        /// Change ownership of the token.950        /// 951        /// # Permissions952        /// 953        /// * Collection Owner954        /// * Collection Admin955        /// * Current NFT owner956        ///957        /// # Arguments958        /// 959        /// * recipient: Address of token recipient.960        /// 961        /// * collection_id.962        /// 963        /// * item_id: ID of the item964        ///     * Non-Fungible Mode: Required.965        ///     * Fungible Mode: Ignored.966        ///     * Re-Fungible Mode: Required.967        /// 968        /// * value: Amount to transfer.969        ///     * Non-Fungible Mode: Ignored970        ///     * Fungible Mode: Must specify transferred amount971        ///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)972        #[weight = <T as Config>::WeightInfo::transfer()]973        #[transactional]974        pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {975            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);976            let collection = Self::get_collection(collection_id)?;977978            Self::transfer_internal(&sender, &recipient, &collection, item_id, value)?;979980            Self::submit_logs(collection)?;981            Ok(())982        }983984        /// Set, change, or remove approved address to transfer the ownership of the NFT.985        /// 986        /// # Permissions987        /// 988        /// * Collection Owner989        /// * Collection Admin990        /// * Current NFT owner991        /// 992        /// # Arguments993        /// 994        /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).995        /// 996        /// * collection_id.997        /// 998        /// * item_id: ID of the item.999        #[weight = <T as Config>::WeightInfo::approve()]1000        #[transactional]1001        pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {1002            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1003            let collection = Self::get_collection(collection_id)?;10041005            Self::approve_internal(&sender, &spender, &collection, item_id, amount)?;10061007            Self::submit_logs(collection)?;1008            Ok(())1009        }1010        1011        /// 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.1012        /// 1013        /// # Permissions1014        /// * Collection Owner1015        /// * Collection Admin1016        /// * Current NFT owner1017        /// * Address approved by current NFT owner1018        /// 1019        /// # Arguments1020        /// 1021        /// * from: Address that owns token.1022        /// 1023        /// * recipient: Address of token recipient.1024        /// 1025        /// * collection_id.1026        /// 1027        /// * item_id: ID of the item.1028        /// 1029        /// * value: Amount to transfer.1030        #[weight = <T as Config>::WeightInfo::transfer_from()]1031        #[transactional]1032        pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {1033            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1034            let collection = Self::get_collection(collection_id)?;10351036            Self::transfer_from_internal(&sender, &from, &recipient, &collection, item_id, value)?;10371038            Self::submit_logs(collection)?;1039            Ok(())1040        }1041        // #[weight = 0]1042        //     // let no_perm_mes = "You do not have permissions to modify this collection";1043        //     // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1044        //     // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1045        //     // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);10461047        //     // // on_nft_received  call10481049        //     // Self::transfer(origin, collection_id, item_id, new_owner)?;10501051        //     Ok(())1052        // }10531054        /// Set off-chain data schema.1055        /// 1056        /// # Permissions1057        /// 1058        /// * Collection Owner1059        /// * Collection Admin1060        /// 1061        /// # Arguments1062        /// 1063        /// * collection_id.1064        /// 1065        /// * schema: String representing the offchain data schema.1066        #[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1067        #[transactional]1068        pub fn set_variable_meta_data (1069            origin,1070            collection_id: CollectionId,1071            item_id: TokenId,1072            data: Vec<u8>1073        ) -> DispatchResult {1074            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1075            1076            let collection = Self::get_collection(collection_id)?;10771078            Self::set_variable_meta_data_internal(&sender, &collection, item_id, data)?;10791080            Ok(())1081        }1082 1083        /// Set schema standard1084        /// ImageURL1085        /// Unique1086        /// 1087        /// # Permissions1088        /// 1089        /// * Collection Owner1090        /// * Collection Admin1091        /// 1092        /// # Arguments1093        /// 1094        /// * collection_id.1095        /// 1096        /// * schema: SchemaVersion: enum1097        #[weight = <T as Config>::WeightInfo::set_schema_version()]1098        #[transactional]1099        pub fn set_schema_version(1100            origin,1101            collection_id: CollectionId,1102            version: SchemaVersion1103        ) -> DispatchResult {1104            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1105            let mut target_collection = Self::get_collection(collection_id)?;1106            Self::check_owner_or_admin_permissions(&target_collection, &sender)?;1107            target_collection.schema_version = version;1108            Self::save_collection(target_collection);11091110            Ok(())1111        }11121113        /// Set off-chain data schema.1114        /// 1115        /// # Permissions1116        /// 1117        /// * Collection Owner1118        /// * Collection Admin1119        /// 1120        /// # Arguments1121        /// 1122        /// * collection_id.1123        /// 1124        /// * schema: String representing the offchain data schema.1125        #[weight = <T as Config>::WeightInfo::set_offchain_schema()]1126        #[transactional]1127        pub fn set_offchain_schema(1128            origin,1129            collection_id: CollectionId,1130            schema: Vec<u8>1131        ) -> DispatchResult {1132            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1133            let mut target_collection = Self::get_collection(collection_id)?;1134            Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11351136            // check schema limit1137            ensure!(schema.len() as u32 <= ChainLimit::get().offchain_schema_limit, "");11381139            target_collection.offchain_schema = schema;1140            Self::save_collection(target_collection);11411142            Ok(())1143        }11441145        /// Set const on-chain data schema.1146        /// 1147        /// # Permissions1148        /// 1149        /// * Collection Owner1150        /// * Collection Admin1151        /// 1152        /// # Arguments1153        /// 1154        /// * collection_id.1155        /// 1156        /// * schema: String representing the const on-chain data schema.1157        #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1158        #[transactional]1159        pub fn set_const_on_chain_schema (1160            origin,1161            collection_id: CollectionId,1162            schema: Vec<u8>1163        ) -> DispatchResult {1164            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1165            let mut target_collection = Self::get_collection(collection_id)?;1166            Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11671168            // check schema limit1169            ensure!(schema.len() as u32 <= ChainLimit::get().const_on_chain_schema_limit, "");11701171            target_collection.const_on_chain_schema = schema;1172            Self::save_collection(target_collection);11731174            Ok(())1175        }11761177        /// Set variable on-chain data schema.1178        /// 1179        /// # Permissions1180        /// 1181        /// * Collection Owner1182        /// * Collection Admin1183        /// 1184        /// # Arguments1185        /// 1186        /// * collection_id.1187        /// 1188        /// * schema: String representing the variable on-chain data schema.1189        #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1190        #[transactional]1191        pub fn set_variable_on_chain_schema (1192            origin,1193            collection_id: CollectionId,1194            schema: Vec<u8>1195        ) -> DispatchResult {1196            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1197            let mut target_collection = Self::get_collection(collection_id)?;1198            Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11991200            // check schema limit1201            ensure!(schema.len() as u32 <= ChainLimit::get().variable_on_chain_schema_limit, "");12021203            target_collection.variable_on_chain_schema = schema;1204            Self::save_collection(target_collection);12051206            Ok(())1207        }12081209        // Sudo permissions function1210        #[weight = <T as Config>::WeightInfo::set_chain_limits()]1211        #[transactional]1212        pub fn set_chain_limits(1213            origin,1214            limits: ChainLimits1215        ) -> DispatchResult {12161217            #[cfg(not(feature = "runtime-benchmarks"))]1218            ensure_root(origin)?;12191220            <ChainLimit>::put(limits);1221            Ok(())1222        }12231224        #[weight = <T as Config>::WeightInfo::set_collection_limits()]1225        #[transactional]1226        pub fn set_collection_limits(1227            origin,1228            collection_id: u32,1229            new_limits: CollectionLimits<T::BlockNumber>,1230        ) -> DispatchResult {1231            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1232            let mut target_collection = Self::get_collection(collection_id)?;1233            Self::check_owner_permissions(&target_collection, &sender.as_sub())?;1234            let old_limits = &target_collection.limits;1235            let chain_limits = ChainLimit::get();12361237            // collection bounds1238            ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1239                new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP && 1240                new_limits.sponsored_data_size <= chain_limits.custom_data_limit,1241                Error::<T>::CollectionLimitBoundsExceeded);12421243            // token_limit   check  prev1244            ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1245            ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);12461247            ensure!(1248                (old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1249                (old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1250                Error::<T>::OwnerPermissionsCantBeReverted,1251            );12521253            target_collection.limits = new_limits;1254            Self::save_collection(target_collection);12551256            Ok(())1257        } 1258    }1259}12601261impl<T: Config> Module<T> {1262    pub fn create_item_internal(sender: &T::CrossAccountId, collection: &CollectionHandle<T>, owner: &T::CrossAccountId, data: CreateItemData) -> DispatchResult {1263        Self::can_create_items_in_collection(&collection, &sender, &owner, 1)?;1264        Self::validate_create_item_args(&collection, &data)?;1265        Self::create_item_no_validation(&collection, owner, data)?;12661267        Ok(())1268    }12691270    pub fn transfer_internal(sender: &T::CrossAccountId, recipient: &T::CrossAccountId, target_collection: &CollectionHandle<T>, item_id: TokenId, value: u128) -> DispatchResult {1271        target_collection.consume_gas(2000000)?;1272        // Limits check1273        Self::is_correct_transfer(target_collection, &recipient)?;12741275        // Transfer permissions check1276        ensure!(Self::is_item_owner(&sender, target_collection, item_id) ||1277            Self::is_owner_or_admin_permissions(target_collection, &sender),1278            Error::<T>::NoPermission);12791280        if target_collection.access == AccessMode::WhiteList {1281            Self::check_white_list(target_collection, &sender)?;1282            Self::check_white_list(target_collection, &recipient)?;1283        }12841285        match target_collection.mode1286        {1287            CollectionMode::NFT => Self::transfer_nft(target_collection, item_id, sender.clone(), recipient.clone())?,1288            CollectionMode::Fungible(_)  => Self::transfer_fungible(target_collection, value, &sender, &recipient)?,1289            CollectionMode::ReFungible  => Self::transfer_refungible(target_collection, item_id, value, sender.clone(), recipient.clone())?,1290            _ => ()1291        };12921293        Self::deposit_event(RawEvent::Transfer(target_collection.id, item_id, sender.clone(), recipient.clone(), value));12941295        Ok(())1296    }12971298	pub fn approve_internal(1299		sender: &T::CrossAccountId,1300		spender: &T::CrossAccountId,1301		collection: &CollectionHandle<T>,1302		item_id: TokenId,1303		amount: u1281304	) -> DispatchResult {1305        collection.consume_gas(2000000)?;1306		Self::token_exists(&collection, item_id)?;13071308		// Transfer permissions check1309		let bypasses_limits = collection.limits.owner_can_transfer &&1310			Self::is_owner_or_admin_permissions(1311				&collection,1312				&sender,1313			);13141315		let allowance_limit = if bypasses_limits {1316			None1317		} else if let Some(amount) = Self::owned_amount(1318			&sender,1319			&collection,1320			item_id,1321		) {1322			Some(amount)1323		} else {1324			fail!(Error::<T>::NoPermission);1325		};13261327		if collection.access == AccessMode::WhiteList {1328			Self::check_white_list(&collection, &sender)?;1329			Self::check_white_list(&collection, &spender)?;1330		}13311332		let allowance: u128 = amount1333			.checked_add(<Allowances<T>>::get(collection.id, (item_id, sender.as_sub(), spender.as_sub())))1334			.ok_or(Error::<T>::NumOverflow)?;1335		if let Some(limit) = allowance_limit {1336			ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1337		}1338		<Allowances<T>>::insert(collection.id, (item_id, sender.as_sub(), spender.as_sub()), allowance);13391340		if matches!(collection.mode, CollectionMode::NFT) {1341			// TODO: NFT: only one owner may exist for token in ERC7211342			collection.log(ERC721Events::Approval {1343                owner: *sender.as_eth(),1344                approved: *spender.as_eth(),1345                token_id: item_id.into(),1346            });1347		}13481349		if matches!(collection.mode, CollectionMode::Fungible(_)) {1350			// TODO: NFT: only one owner may exist for token in ERC201351			collection.log(ERC20Events::Approval {1352                owner: *sender.as_eth(),1353                spender: *spender.as_eth(),1354                value: allowance.into()1355            });1356		}13571358		Self::deposit_event(RawEvent::Approved(collection.id, item_id, sender.clone(), spender.clone(), allowance));1359		Ok(())1360	}13611362	pub fn transfer_from_internal(1363		sender: &T::CrossAccountId,1364		from: &T::CrossAccountId,1365		recipient: &T::CrossAccountId,1366		collection: &CollectionHandle<T>,1367		item_id: TokenId,1368		amount: u128,1369	) -> DispatchResult {1370        collection.consume_gas(2000000)?;1371		// Check approval1372		let approval: u128 = <Allowances<T>>::get(collection.id, (item_id, from.as_sub(), sender.as_sub()));13731374		// Limits check1375		Self::is_correct_transfer(&collection, &recipient)?;13761377		// Transfer permissions check1378		ensure!(1379			approval >= amount || 1380			(1381				collection.limits.owner_can_transfer &&1382				Self::is_owner_or_admin_permissions(&collection, &sender)1383			),1384			Error::<T>::NoPermission1385		);13861387		if collection.access == AccessMode::WhiteList {1388			Self::check_white_list(&collection, &sender)?;1389			Self::check_white_list(&collection, &recipient)?;1390		}13911392		// Reduce approval by transferred amount or remove if remaining approval drops to 01393		let allowance = approval.saturating_sub(amount);1394		if allowance > 0 {1395			<Allowances<T>>::insert(collection.id, (item_id, from.as_sub(), sender.as_sub()), allowance);1396		} else {1397			<Allowances<T>>::remove(collection.id, (item_id, from.as_sub(), sender.as_sub()));1398		}13991400		match collection.mode {1401			CollectionMode::NFT => {1402				Self::transfer_nft(&collection, item_id, from.clone(), recipient.clone())?1403			}1404			CollectionMode::Fungible(_) => {1405				Self::transfer_fungible(&collection, amount, &from, &recipient)?1406			}1407			CollectionMode::ReFungible => {1408				Self::transfer_refungible(&collection, item_id, amount, from.clone(), recipient.clone())?1409			}1410			_ => ()1411		};14121413		if matches!(collection.mode, CollectionMode::Fungible(_)) {1414			collection.log(ERC20Events::Approval {1415                owner: *from.as_eth(),1416                spender: *sender.as_eth(),1417                value: allowance.into()1418            });1419		}14201421		Ok(())1422	}14231424    pub fn set_variable_meta_data_internal(1425        sender: &T::CrossAccountId,1426        collection: &CollectionHandle<T>, 1427        item_id: TokenId,1428        data: Vec<u8>,1429    ) -> DispatchResult {1430        Self::token_exists(&collection, item_id)?;14311432        ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);14331434        // Modify permissions check1435        ensure!(Self::is_item_owner(&sender, &collection, item_id) ||1436            Self::is_owner_or_admin_permissions(&collection, &sender),1437            Error::<T>::NoPermission);14381439        match collection.mode1440        {1441            CollectionMode::NFT => Self::set_nft_variable_data(&collection, item_id, data)?,1442            CollectionMode::ReFungible  => Self::set_re_fungible_variable_data(&collection, item_id, data)?,1443            CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1444            _ => fail!(Error::<T>::UnexpectedCollectionType)1445        };14461447        Ok(())1448    }14491450    pub fn create_multiple_items_internal(1451        sender: &T::CrossAccountId,1452        collection: &CollectionHandle<T>,1453        owner: &T::CrossAccountId,1454        items_data: Vec<CreateItemData>,1455    ) -> DispatchResult {1456        Self::can_create_items_in_collection(&collection, &sender, &owner, items_data.len() as u32)?;14571458        for data in &items_data {1459            Self::validate_create_item_args(&collection, data)?;1460        }1461        for data in &items_data {1462            Self::create_item_no_validation(&collection, owner, data.clone())?;1463        }14641465        Ok(())1466    }14671468    pub fn burn_item_internal(1469        sender: &T::CrossAccountId,1470        collection: &CollectionHandle<T>,1471        item_id: TokenId,1472        value: u128,1473    ) -> DispatchResult {1474        ensure!(1475            Self::is_item_owner(&sender, &collection, item_id) ||1476            (1477                collection.limits.owner_can_transfer &&1478                Self::is_owner_or_admin_permissions(&collection, &sender)1479            ),1480            Error::<T>::NoPermission1481        );14821483        if collection.access == AccessMode::WhiteList {1484            Self::check_white_list(&collection, &sender)?;1485        }14861487        match collection.mode1488        {1489            CollectionMode::NFT => Self::burn_nft_item(&collection, item_id)?,1490            CollectionMode::Fungible(_)  => Self::burn_fungible_item(&sender, &collection, value)?,1491            CollectionMode::ReFungible  => Self::burn_refungible_item(&collection, item_id, &sender)?,1492            _ => ()1493        };14941495        Ok(())1496    }14971498    pub fn toggle_white_list_internal(1499        sender: &T::CrossAccountId,1500        collection: &CollectionHandle<T>,1501        address: &T::CrossAccountId,1502        whitelisted: bool,1503    ) -> DispatchResult {1504        Self::check_owner_or_admin_permissions(&collection, &sender)?;15051506        if whitelisted {1507            <WhiteList<T>>::insert(collection.id, address.as_sub(), true);1508        } else {1509            <WhiteList<T>>::remove(collection.id, address.as_sub());1510        }15111512        Ok(())1513    }15141515    fn is_correct_transfer(collection: &CollectionHandle<T>, recipient: &T::CrossAccountId) -> DispatchResult {1516        let collection_id = collection.id;15171518        // check token limit and account token limit1519        let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient.as_sub()).len() as u32;1520        ensure!(collection.limits.account_token_ownership_limit > account_items,  Error::<T>::AccountTokenLimitExceeded);1521        1522        Ok(())1523    }15241525    fn can_create_items_in_collection(collection: &CollectionHandle<T>, sender: &T::CrossAccountId, owner: &T::CrossAccountId, amount: u32) -> DispatchResult {1526        let collection_id = collection.id;15271528        // check token limit and account token limit1529        let total_items: u32 = ItemListIndex::get(collection_id)1530            .checked_add(amount)1531            .ok_or(Error::<T>::CollectionTokenLimitExceeded)?;1532        let account_items: u32 = (<AddressTokens<T>>::get(collection_id, owner.as_sub()).len() as u32)1533            .checked_add(amount)1534            .ok_or(Error::<T>::AccountTokenLimitExceeded)?;1535        ensure!(collection.limits.token_limit >= total_items,  Error::<T>::CollectionTokenLimitExceeded);1536        ensure!(collection.limits.account_token_ownership_limit >= account_items,  Error::<T>::AccountTokenLimitExceeded);15371538        if !Self::is_owner_or_admin_permissions(collection, &sender) {1539            ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1540            Self::check_white_list(collection, owner)?;1541            Self::check_white_list(collection, sender)?;1542        }15431544        Ok(())1545    }15461547    fn validate_create_item_args(target_collection: &CollectionHandle<T>, data: &CreateItemData) -> DispatchResult {1548        match target_collection.mode1549        {1550            CollectionMode::NFT => {1551                if let CreateItemData::NFT(data) = data {1552                    // check sizes1553                    ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1554                    ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1555                } else {1556                    fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1557                }1558            },1559            CollectionMode::Fungible(_) => {1560                if let CreateItemData::Fungible(_) = data {1561                } else {1562                    fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1563                }1564            },1565            CollectionMode::ReFungible => {1566                if let CreateItemData::ReFungible(data) = data {15671568                    // check sizes1569                    ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1570                    ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);15711572                    // Check refungibility limits1573                    ensure!(data.pieces <= MAX_REFUNGIBLE_PIECES, Error::<T>::WrongRefungiblePieces);1574                    ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1575                } else {1576                    fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1577                }1578            },1579            _ => { fail!(Error::<T>::UnexpectedCollectionType); }1580        };15811582        Ok(())1583    }15841585    fn create_item_no_validation(collection: &CollectionHandle<T>, owner: &T::CrossAccountId, data: CreateItemData) -> DispatchResult {1586        match data1587        {1588            CreateItemData::NFT(data) => {1589                let item = NftItemType {1590                    owner: owner.clone(),1591                    const_data: data.const_data,1592                    variable_data: data.variable_data1593                };15941595                Self::add_nft_item(collection, item)?;1596            },1597            CreateItemData::Fungible(data) => {1598                Self::add_fungible_item(collection, &owner, data.value)?;1599            },1600            CreateItemData::ReFungible(data) => {1601                let mut owner_list = Vec::new();1602                owner_list.push(Ownership {owner: owner.clone(), fraction: data.pieces});16031604                let item = ReFungibleItemType {1605                    owner: owner_list,1606                    const_data: data.const_data,1607                    variable_data: data.variable_data1608                };16091610                Self::add_refungible_item(collection, item)?;1611            }1612        };16131614        Ok(())1615    }16161617    fn add_fungible_item(collection: &CollectionHandle<T>, owner: &T::CrossAccountId, value: u128) -> DispatchResult {1618        let collection_id = collection.id;16191620        // Does new owner already have an account?1621        let balance: u128 = <FungibleItemList<T>>::get(collection_id, owner.as_sub()).value;16221623        // Mint 1624        let item = FungibleItemType {1625            value: balance.checked_add(value).ok_or(Error::<T>::NumOverflow)?,1626        };1627        <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), item);16281629        // Update balance1630        let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1631            .checked_add(value)1632            .ok_or(Error::<T>::NumOverflow)?;1633        <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);16341635        Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));1636        Ok(())1637    }16381639    fn add_refungible_item(collection: &CollectionHandle<T>, item: ReFungibleItemType<T::CrossAccountId>) -> DispatchResult {1640        let collection_id = collection.id;16411642        let current_index = <ItemListIndex>::get(collection_id)1643            .checked_add(1)1644            .ok_or(Error::<T>::NumOverflow)?;1645        let itemcopy = item.clone();16461647        ensure!(1648            item.owner.len() == 1,1649            Error::<T>::BadCreateRefungibleCall,1650        );1651        let item_owner = item.owner.first().expect("only one owner is defined");16521653        let value = item_owner.fraction;1654        let owner = item_owner.owner.clone();16551656        Self::add_token_index(collection_id, current_index, &owner)?;16571658        <ItemListIndex>::insert(collection_id, current_index);1659        <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);16601661        // Update balance1662        let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1663            .checked_add(value)1664            .ok_or(Error::<T>::NumOverflow)?;1665        <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);16661667        Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));1668        Ok(())1669    }16701671    fn add_nft_item(collection: &CollectionHandle<T>, item: NftItemType<T::CrossAccountId>) -> DispatchResult {1672        let collection_id = collection.id;16731674        let current_index = <ItemListIndex>::get(collection_id)1675            .checked_add(1)1676            .ok_or(Error::<T>::NumOverflow)?;16771678        let item_owner = item.owner.clone();1679        Self::add_token_index(collection_id, current_index, &item.owner)?;16801681        <ItemListIndex>::insert(collection_id, current_index);1682        <NftItemList<T>>::insert(collection_id, current_index, item);16831684        // Update balance1685        let new_balance = <Balance<T>>::get(collection_id, item_owner.as_sub())1686            .checked_add(1)1687            .ok_or(Error::<T>::NumOverflow)?;1688        <Balance<T>>::insert(collection_id, item_owner.as_sub(), new_balance);16891690        collection.log(ERC721Events::Transfer {1691            from: H160::default(),1692            to: *item_owner.as_eth(),1693            token_id: current_index.into(),1694        });1695        Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, item_owner));1696        Ok(())1697    }16981699    fn burn_refungible_item(1700        collection: &CollectionHandle<T>,1701        item_id: TokenId,1702        owner: &T::CrossAccountId,1703    ) -> DispatchResult {1704        let collection_id = collection.id;17051706        let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id)1707            .ok_or(Error::<T>::TokenNotFound)?;1708        let rft_balance = token1709            .owner1710            .iter()1711            .find(|&i| i.owner == *owner)1712            .ok_or(Error::<T>::TokenNotFound)?;1713        Self::remove_token_index(collection_id, item_id, owner)?;17141715        // update balance1716        let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.as_sub())1717            .checked_sub(rft_balance.fraction)1718            .ok_or(Error::<T>::NumOverflow)?;1719        <Balance<T>>::insert(collection_id, rft_balance.owner.as_sub(), new_balance);17201721        // Re-create owners list with sender removed1722        let index = token1723            .owner1724            .iter()1725            .position(|i| i.owner == *owner)1726            .expect("owned item is exists");1727        token.owner.remove(index);1728        let owner_count = token.owner.len();17291730        // Burn the token completely if this was the last (only) owner1731        if owner_count == 0 {1732            <ReFungibleItemList<T>>::remove(collection_id, item_id);1733            <VariableMetaDataBasket<T>>::remove(collection_id, item_id);1734        }1735        else {1736            <ReFungibleItemList<T>>::insert(collection_id, item_id, token);1737        }17381739        Ok(())1740    }17411742    fn burn_nft_item(collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1743        let collection_id = collection.id;17441745        let item = <NftItemList<T>>::get(collection_id, item_id)1746            .ok_or(Error::<T>::TokenNotFound)?;1747        Self::remove_token_index(collection_id, item_id, &item.owner)?;17481749        // update balance1750        let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())1751            .checked_sub(1)1752            .ok_or(Error::<T>::NumOverflow)?;1753        <Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);1754        <NftItemList<T>>::remove(collection_id, item_id);1755        <VariableMetaDataBasket<T>>::remove(collection_id, item_id);17561757        Self::deposit_event(RawEvent::ItemDestroyed(collection.id, item_id));1758        Ok(())1759    }17601761    fn burn_fungible_item(owner: &T::CrossAccountId, collection: &CollectionHandle<T>, value: u128) -> DispatchResult {1762        let collection_id = collection.id;17631764        let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());1765        ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);17661767        // update balance1768        let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1769            .checked_sub(value)1770            .ok_or(Error::<T>::NumOverflow)?;1771        <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);17721773        if balance.value - value > 0 {1774            balance.value -= value;1775            <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);1776        }1777        else {1778            <FungibleItemList<T>>::remove(collection_id, owner.as_sub());1779        }17801781        collection.log(ERC20Events::Transfer {1782            from: *owner.as_eth(),1783            to: H160::default(),1784            value: value.into(),1785        });1786        Ok(())1787    }17881789    pub fn get_collection(collection_id: CollectionId) -> Result<CollectionHandle<T>, sp_runtime::DispatchError> {1790        Ok(<CollectionHandle<T>>::get(collection_id)1791            .ok_or(Error::<T>::CollectionNotFound)?)1792    }17931794    fn save_collection(collection: CollectionHandle<T>) {1795        <CollectionById<T>>::insert(collection.id, collection.into_inner());1796    }17971798    pub fn submit_logs(collection: CollectionHandle<T>) -> DispatchResult {1799        if collection.logs.is_empty() {1800            return Ok(())1801        }1802        T::EthereumTransactionSender::submit_logs_transaction(1803            eth::generate_transaction(collection.id, T::EthereumChainId::get()),1804            collection.logs.retrieve_logs(),1805        )1806    }18071808    fn check_owner_permissions(target_collection: &CollectionHandle<T>, subject: &T::AccountId) -> DispatchResult {1809        ensure!(1810            *subject == target_collection.owner,1811            Error::<T>::NoPermission1812        );18131814        Ok(())1815    }18161817    fn is_owner_or_admin_permissions(collection: &CollectionHandle<T>, subject: &T::CrossAccountId) -> bool {1818        *subject.as_sub() == collection.owner || <AdminList<T>>::get(collection.id).contains(&subject)1819    }18201821    fn check_owner_or_admin_permissions(1822        collection: &CollectionHandle<T>,1823        subject: &T::CrossAccountId,1824    ) -> DispatchResult {1825        ensure!(Self::is_owner_or_admin_permissions(collection, subject), Error::<T>::NoPermission);18261827        Ok(())1828    }18291830    fn owned_amount(1831        subject: &T::CrossAccountId,1832        target_collection: &CollectionHandle<T>,1833        item_id: TokenId,1834    ) -> Option<u128> {1835        let collection_id = target_collection.id;18361837        match target_collection.mode {1838            CollectionMode::NFT => (<NftItemList<T>>::get(collection_id, item_id)?.owner == *subject)1839                .then(|| 1),1840            CollectionMode::Fungible(_) => Some(<FungibleItemList<T>>::get(collection_id, &subject.as_sub())1841                .value),1842            CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)?1843                .owner1844                .iter()1845                .find(|i| i.owner == *subject)1846                .map(|i| i.fraction),1847            CollectionMode::Invalid => None,1848        }1849    }18501851    fn is_item_owner(subject: &T::CrossAccountId, target_collection: &CollectionHandle<T>, item_id: TokenId) -> bool {1852        match target_collection.mode {1853            CollectionMode::Fungible(_) => true,1854            _ => Self::owned_amount(&subject, target_collection, item_id).is_some(),1855        }1856    }18571858    fn check_white_list(collection: &CollectionHandle<T>, address: &T::CrossAccountId) -> DispatchResult {1859        let collection_id = collection.id;18601861        let mes = Error::<T>::AddresNotInWhiteList;1862        ensure!(<WhiteList<T>>::contains_key(collection_id, address.as_sub()), mes);18631864        Ok(())1865    }18661867    /// Check if token exists. In case of Fungible, check if there is an entry for 1868    /// the owner in fungible balances double map1869    fn token_exists(1870        target_collection: &CollectionHandle<T>,1871        item_id: TokenId,1872    ) -> DispatchResult {1873        let collection_id = target_collection.id;1874        let exists = match target_collection.mode1875        {1876            CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),1877            CollectionMode::Fungible(_)  => true,1878            CollectionMode::ReFungible  => <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1879            _ => false1880        };18811882        ensure!(exists == true, Error::<T>::TokenNotFound);1883        Ok(())1884    }18851886    fn transfer_fungible(1887        collection: &CollectionHandle<T>,1888        value: u128,1889        owner: &T::CrossAccountId,1890        recipient: &T::CrossAccountId,1891    ) -> DispatchResult {1892        let collection_id = collection.id;18931894        let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());1895        ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);18961897        // Send balance to recipient (updates balanceOf of recipient)1898        Self::add_fungible_item(collection, recipient, value)?;18991900        // update balanceOf of sender1901        <Balance<T>>::insert(collection_id, owner.as_sub(), balance.value - value);19021903        // Reduce or remove sender1904        if balance.value == value {1905            <FungibleItemList<T>>::remove(collection_id, owner.as_sub());1906        }1907        else {1908            balance.value -= value;1909            <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);1910        }19111912        collection.log(ERC20Events::Transfer {1913            from: *owner.as_eth(),1914            to: *recipient.as_eth(),1915            value: value.into(),1916        });1917        Self::deposit_event(RawEvent::Transfer(collection.id, 1, owner.clone(), recipient.clone(), value));19181919        Ok(())1920    }19211922    fn transfer_refungible(1923        collection: &CollectionHandle<T>,1924        item_id: TokenId,1925        value: u128,1926        owner: T::CrossAccountId,1927        new_owner: T::CrossAccountId,1928    ) -> DispatchResult {1929        let collection_id = collection.id;1930        let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id)1931            .ok_or(Error::<T>::TokenNotFound)?;19321933        let item = full_item1934            .owner1935            .iter()1936            .filter(|i| i.owner == owner)1937            .next()1938            .ok_or(Error::<T>::TokenNotFound)?;1939        let amount = item.fraction;19401941        ensure!(amount >= value, Error::<T>::TokenValueTooLow);19421943        // update balance1944        let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())1945            .checked_sub(value)1946            .ok_or(Error::<T>::NumOverflow)?;1947        <Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);19481949        let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())1950            .checked_add(value)1951            .ok_or(Error::<T>::NumOverflow)?;1952        <Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);19531954        let old_owner = item.owner.clone();1955        let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);19561957        // transfer1958        if amount == value && !new_owner_has_account {1959            // change owner1960            // new owner do not have account1961            let mut new_full_item = full_item.clone();1962            new_full_item1963                .owner1964                .iter_mut()1965                .find(|i| i.owner == owner)1966                .expect("old owner does present in refungible")1967                .owner = new_owner.clone();1968            <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);19691970            // update index collection1971            Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;1972        } else {1973            let mut new_full_item = full_item.clone();1974            new_full_item1975                .owner1976                .iter_mut()1977                .find(|i| i.owner == owner)1978                .expect("old owner does present in refungible")1979                .fraction -= value;19801981            // separate amount1982            if new_owner_has_account {1983                // new owner has account1984                new_full_item1985                    .owner1986                    .iter_mut()1987                    .find(|i| i.owner == new_owner)1988                    .expect("new owner has account")1989                    .fraction += value;1990            } else {1991                // new owner do not have account1992                new_full_item.owner.push(Ownership {1993                    owner: new_owner.clone(),1994                    fraction: value,1995                });1996                Self::add_token_index(collection_id, item_id, &new_owner)?;1997            }19981999            <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2000        }20012002        Self::deposit_event(RawEvent::Transfer(collection.id, item_id, owner, new_owner, amount));20032004        Ok(())2005    }20062007    fn transfer_nft(2008        collection: &CollectionHandle<T>,2009        item_id: TokenId,2010        sender: T::CrossAccountId,2011        new_owner: T::CrossAccountId,2012    ) -> DispatchResult {2013        let collection_id = collection.id;2014        let mut item = <NftItemList<T>>::get(collection_id, item_id)2015            .ok_or(Error::<T>::TokenNotFound)?;20162017        ensure!(2018            sender == item.owner,2019            Error::<T>::MustBeTokenOwner2020        );20212022        // update balance2023        let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2024            .checked_sub(1)2025            .ok_or(Error::<T>::NumOverflow)?;2026        <Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);20272028        let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2029            .checked_add(1)2030            .ok_or(Error::<T>::NumOverflow)?;2031        <Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);20322033        // change owner2034        let old_owner = item.owner.clone();2035        item.owner = new_owner.clone();2036        <NftItemList<T>>::insert(collection_id, item_id, item);20372038        // update index collection2039        Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;20402041        collection.log(ERC721Events::Transfer {2042            from: *sender.as_eth(),2043            to: *new_owner.as_eth(),2044            token_id: item_id.into(),2045        });2046        Self::deposit_event(RawEvent::Transfer(collection.id, item_id, sender, new_owner, 1));20472048        Ok(())2049    }2050    2051    fn set_re_fungible_variable_data(2052        collection: &CollectionHandle<T>,2053        item_id: TokenId,2054        data: Vec<u8>2055    ) -> DispatchResult {2056        let collection_id = collection.id;2057        let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id)2058            .ok_or(Error::<T>::TokenNotFound)?;20592060        item.variable_data = data;20612062        <ReFungibleItemList<T>>::insert(collection_id, item_id, item);20632064        Ok(())2065    }20662067    fn set_nft_variable_data(2068        collection: &CollectionHandle<T>,2069        item_id: TokenId,2070        data: Vec<u8>2071    ) -> DispatchResult {2072        let collection_id = collection.id;2073        let mut item = <NftItemList<T>>::get(collection_id, item_id)2074            .ok_or(Error::<T>::TokenNotFound)?;2075        2076        item.variable_data = data;20772078        <NftItemList<T>>::insert(collection_id, item_id, item);2079        2080        Ok(())2081    }20822083    #[allow(dead_code)]2084    fn init_collection(item: &Collection<T>) {2085        // check params2086        assert!(2087            item.decimal_points <= MAX_DECIMAL_POINTS,2088            "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2089        );2090        assert!(2091            item.name.len() <= 64,2092            "Collection name can not be longer than 63 char"2093        );2094        assert!(2095            item.name.len() <= 256,2096            "Collection description can not be longer than 255 char"2097        );2098        assert!(2099            item.token_prefix.len() <= 16,2100            "Token prefix can not be longer than 15 char"2101        );21022103        // Generate next collection ID2104        let next_id = CreatedCollectionCount::get()2105            .checked_add(1)2106            .unwrap();21072108        CreatedCollectionCount::put(next_id);2109    }21102111    #[allow(dead_code)]2112    fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::CrossAccountId>) {2113        let current_index = <ItemListIndex>::get(collection_id)2114            .checked_add(1)2115            .unwrap();21162117        Self::add_token_index(collection_id, current_index, &item.owner).unwrap();21182119        <ItemListIndex>::insert(collection_id, current_index);21202121        // Update balance2122        let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2123            .checked_add(1)2124            .unwrap();2125        <Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2126    }21272128    #[allow(dead_code)]2129    fn init_fungible_token(collection_id: CollectionId, owner: &T::CrossAccountId, item: &FungibleItemType) {2130        let current_index = <ItemListIndex>::get(collection_id)2131            .checked_add(1)2132            .unwrap();21332134        Self::add_token_index(collection_id, current_index, owner).unwrap();21352136        <ItemListIndex>::insert(collection_id, current_index);21372138        // Update balance2139        let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2140            .checked_add(item.value)2141            .unwrap();2142        <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2143    }21442145    #[allow(dead_code)]2146    fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::CrossAccountId>) {2147        let current_index = <ItemListIndex>::get(collection_id)2148            .checked_add(1)2149            .unwrap();21502151        let value = item.owner.first().unwrap().fraction;2152        let owner = item.owner.first().unwrap().owner.clone();21532154        Self::add_token_index(collection_id, current_index, &owner).unwrap();21552156        <ItemListIndex>::insert(collection_id, current_index);21572158        // Update balance2159        let new_balance = <Balance<T>>::get(collection_id, &owner.as_sub())2160            .checked_add(value)2161            .unwrap();2162        <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2163    }21642165    fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: &T::CrossAccountId) -> DispatchResult {2166        // add to account limit2167        if <AccountItemCount<T>>::contains_key(owner.as_sub()) {21682169            // bound Owned tokens by a single address2170            let count = <AccountItemCount<T>>::get(owner.as_sub());2171            ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);21722173            <AccountItemCount<T>>::insert(owner.as_sub(), count2174                .checked_add(1)2175                .ok_or(Error::<T>::NumOverflow)?);2176        }2177        else {2178            <AccountItemCount<T>>::insert(owner.as_sub(), 1);2179        }21802181        let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2182        if list_exists {2183            let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2184            let item_contains = list.contains(&item_index.clone());21852186            if !item_contains {2187                list.push(item_index.clone());2188            }21892190            <AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2191        } else {2192            let mut itm = Vec::new();2193            itm.push(item_index.clone());2194            <AddressTokens<T>>::insert(collection_id, owner.as_sub(), itm);2195        }21962197        Ok(())2198    }21992200    fn remove_token_index(2201        collection_id: CollectionId,2202        item_index: TokenId,2203        owner: &T::CrossAccountId,2204    ) -> DispatchResult {22052206        // update counter2207        <AccountItemCount<T>>::insert(owner.as_sub(), 2208            <AccountItemCount<T>>::get(owner.as_sub())2209            .checked_sub(1)2210            .ok_or(Error::<T>::NumOverflow)?);221122122213        let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2214        if list_exists {2215            let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2216            let item_contains = list.contains(&item_index.clone());22172218            if item_contains {2219                list.retain(|&item| item != item_index);2220                <AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2221            }2222        }22232224        Ok(())2225    }22262227    fn move_token_index(2228        collection_id: CollectionId,2229        item_index: TokenId,2230        old_owner: &T::CrossAccountId,2231        new_owner: &T::CrossAccountId,2232    ) -> DispatchResult {2233        Self::remove_token_index(collection_id, item_index, old_owner)?;2234        Self::add_token_index(collection_id, item_index, new_owner)?;22352236        Ok(())2237    }2238    2239    fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult {2240        ensure!(<ContractOwner<T>>::get(contract) == Some(account), Error::<T>::NoPermission);22412242        Ok(())2243    }2244}22452246sp_api::decl_runtime_apis! {2247    pub trait NftApi {2248        /// Used for ethereum integration2249        fn eth_contract_code(account: H160) -> Option<Vec<u8>>;2250    }2251}