git.delta.rocks / unique-network / refs/commits / 28500d18ba54

difftreelog

source

pallets/nft/src/lib.rs72.6 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"]7#![cfg_attr(not(feature = "std"), no_std)]8#![allow(9	clippy::too_many_arguments,10	clippy::unnecessary_mut_passed,11	clippy::unused_unit12)]1314extern crate alloc;1516pub use serde::{Serialize, Deserialize};1718pub use frame_support::{19	construct_runtime, decl_event, decl_module, decl_storage, decl_error,20	dispatch::DispatchResult,21	ensure, fail, parameter_types,22	traits::{23		Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,24		Randomness, IsSubType, WithdrawReasons,25	},26	weights::{27		constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},28		DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,29		WeightToFeePolynomial, DispatchClass,30	},31	StorageValue, transactional,32};3334use frame_system::{self as system, ensure_signed, ensure_root};35use sp_core::H160;36use sp_std::vec;37use sp_runtime::{DispatchError, sp_std::prelude::Vec};38use core::ops::{Deref, DerefMut};39use nft_data_structs::{40	MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, MAX_REFUNGIBLE_PIECES,41	AccessMode, ChainLimits, Collection, CreateItemData, CollectionLimits, CollectionId,42	CollectionMode, TokenId, SchemaVersion, SponsorshipState, Ownership, NftItemType,43	FungibleItemType, ReFungibleItemType,44};4546#[cfg(test)]47mod mock;4849#[cfg(test)]50mod tests;5152mod default_weights;53mod eth;54mod sponsorship;55pub use sponsorship::NftSponsorshipHandler;56pub use eth::sponsoring::NftEthSponsorshipHandler;5758pub use eth::NftErcSupport;59pub use eth::account::*;60use eth::erc::{ERC20Events, ERC721Events};6162#[cfg(feature = "runtime-benchmarks")]63mod benchmarking;6465pub trait WeightInfo {66	fn create_collection() -> Weight;67	fn destroy_collection() -> Weight;68	fn add_to_white_list() -> Weight;69	fn remove_from_white_list() -> Weight;70	fn set_public_access_mode() -> Weight;71	fn set_mint_permission() -> Weight;72	fn change_collection_owner() -> Weight;73	fn add_collection_admin() -> Weight;74	fn remove_collection_admin() -> Weight;75	fn set_collection_sponsor() -> Weight;76	fn confirm_sponsorship() -> Weight;77	fn remove_collection_sponsor() -> Weight;78	fn create_item(s: usize) -> Weight;79	fn burn_item() -> Weight;80	fn transfer() -> Weight;81	fn approve() -> Weight;82	fn transfer_from() -> Weight;83	fn set_offchain_schema() -> Weight;84	fn set_const_on_chain_schema() -> Weight;85	fn set_variable_on_chain_schema() -> Weight;86	fn set_variable_meta_data() -> Weight;87	fn enable_contract_sponsoring() -> Weight;88	fn set_schema_version() -> Weight;89	fn set_chain_limits() -> Weight;90	fn set_contract_sponsoring_rate_limit() -> Weight;91	fn set_variable_meta_data_sponsoring_rate_limit() -> Weight;92	fn toggle_contract_white_list() -> Weight;93	fn add_to_contract_white_list() -> Weight;94	fn remove_from_contract_white_list() -> Weight;95	fn set_collection_limits() -> Weight;96}9798decl_error! {99	/// Error for non-fungible-token module.100	pub enum Error for Module<T: Config> {101		/// Total collections bound exceeded.102		TotalCollectionsLimitExceeded,103		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.104		CollectionDecimalPointLimitExceeded,105		/// Collection name can not be longer than 63 char.106		CollectionNameLimitExceeded,107		/// Collection description can not be longer than 255 char.108		CollectionDescriptionLimitExceeded,109		/// Token prefix can not be longer than 15 char.110		CollectionTokenPrefixLimitExceeded,111		/// This collection does not exist.112		CollectionNotFound,113		/// Item not exists.114		TokenNotFound,115		/// Admin not found116		AdminNotFound,117		/// Arithmetic calculation overflow.118		NumOverflow,119		/// Account already has admin role.120		AlreadyAdmin,121		/// You do not own this collection.122		NoPermission,123		/// This address is not set as sponsor, use setCollectionSponsor first.124		ConfirmUnsetSponsorFail,125		/// Collection is not in mint mode.126		PublicMintingNotAllowed,127		/// Sender parameter and item owner must be equal.128		MustBeTokenOwner,129		/// Item balance not enough.130		TokenValueTooLow,131		/// Size of item is too large.132		NftSizeLimitExceeded,133		/// No approve found134		ApproveNotFound,135		/// Requested value more than approved.136		TokenValueNotEnough,137		/// Only approved addresses can call this method.138		ApproveRequired,139		/// Address is not in white list.140		AddresNotInWhiteList,141		/// Number of collection admins bound exceeded.142		CollectionAdminsLimitExceeded,143		/// Owned tokens by a single address bound exceeded.144		AddressOwnershipLimitExceeded,145		/// Length of items properties must be greater than 0.146		EmptyArgument,147		/// const_data exceeded data limit.148		TokenConstDataLimitExceeded,149		/// variable_data exceeded data limit.150		TokenVariableDataLimitExceeded,151		/// Not NFT item data used to mint in NFT collection.152		NotNftDataUsedToMintNftCollectionToken,153		/// Not Fungible item data used to mint in Fungible collection.154		NotFungibleDataUsedToMintFungibleCollectionToken,155		/// Not Re Fungible item data used to mint in Re Fungible collection.156		NotReFungibleDataUsedToMintReFungibleCollectionToken,157		/// Unexpected collection type.158		UnexpectedCollectionType,159		/// Can't store metadata in fungible tokens.160		CantStoreMetadataInFungibleTokens,161		/// Collection token limit exceeded162		CollectionTokenLimitExceeded,163		/// Account token limit exceeded per collection164		AccountTokenLimitExceeded,165		/// Collection limit bounds per collection exceeded166		CollectionLimitBoundsExceeded,167		/// Tried to enable permissions which are only permitted to be disabled168		OwnerPermissionsCantBeReverted,169		/// Schema data size limit bound exceeded170		SchemaDataLimitExceeded,171		/// Maximum refungibility exceeded172		WrongRefungiblePieces,173		/// createRefungible should be called with one owner174		BadCreateRefungibleCall,175		/// Gas limit exceeded176		OutOfGas,177		/// Collection settings not allowing items transferring178		TransferNotAllowed,179	}180}181182#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]183pub struct CollectionHandle<T: Config> {184	pub id: CollectionId,185	collection: Collection<T>,186	recorder: pallet_evm_coder_substrate::SubstrateRecorder<T>,187}188impl<T: Config> CollectionHandle<T> {189	pub fn get_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {190		<CollectionById<T>>::get(id).map(|collection| Self {191			id,192			collection,193			recorder: pallet_evm_coder_substrate::SubstrateRecorder::new(194				eth::collection_id_to_address(id),195				gas_limit,196			),197		})198	}199	pub fn get(id: CollectionId) -> Option<Self> {200		Self::get_with_gas_limit(id, u64::MAX)201	}202	pub fn log(&self, log: impl evm_coder::ToLog) -> DispatchResult {203		self.recorder.log_sub(log)204	}205	#[allow(dead_code)]206	fn consume_gas(&self, gas: u64) -> DispatchResult {207		self.recorder.consume_gas_sub(gas)208	}209	fn consume_sload(&self) -> DispatchResult {210		self.recorder.consume_sload_sub()211	}212	fn consume_sstore(&self) -> DispatchResult {213		self.recorder.consume_sstore_sub()214	}215	pub fn submit_logs(self) -> DispatchResult {216		self.recorder.submit_logs()217	}218	pub fn save(self) -> DispatchResult {219		self.recorder.submit_logs()?;220		<CollectionById<T>>::insert(self.id, self.collection);221		Ok(())222	}223}224impl<T: Config> Deref for CollectionHandle<T> {225	type Target = Collection<T>;226227	fn deref(&self) -> &Self::Target {228		&self.collection229	}230}231232impl<T: Config> DerefMut for CollectionHandle<T> {233	fn deref_mut(&mut self) -> &mut Self::Target {234		&mut self.collection235	}236}237238pub trait Config: system::Config + pallet_evm_coder_substrate::Config + Sized {239	type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;240241	/// Weight information for extrinsics in this pallet.242	type WeightInfo: WeightInfo;243244	type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;245	type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;246247	type CrossAccountId: CrossAccountId<Self::AccountId>;248	type Currency: Currency<Self::AccountId>;249	type CollectionCreationPrice: Get<250		<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,251	>;252	type TreasuryAccountId: Get<Self::AccountId>;253}254255// # Used definitions256//257// ## User control levels258//259// chain-controlled - key is uncontrolled by user260//                    i.e autoincrementing index261//                    can use non-cryptographic hash262// real - key is controlled by user263//        but it is hard to generate enough colliding values, i.e owner of signed txs264//        can use non-cryptographic hash265// controlled - key is completly controlled by users266//              i.e maps with mutable keys267//              should use cryptographic hash268//269// ## User control level downgrade reasons270//271// ?1 - chain-controlled -> controlled272//      collections/tokens can be destroyed, resulting in massive holes273// ?2 - chain-controlled -> controlled274//      same as ?1, but can be only added, resulting in easier exploitation275// ?3 - real -> controlled276//      no confirmation required, so addresses can be easily generated277decl_storage! {278	trait Store for Module<T: Config> as Nft {279280		//#region Private members281		/// Id of next collection282		CreatedCollectionCount: u32;283		/// Used for migrations284		ChainVersion: u64;285		/// Id of last collection token286		/// Collection id (controlled?1)287		ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;288		//#endregion289290		//#region Chain limits struct291		pub ChainLimit get(fn chain_limit) config(): ChainLimits;292		//#endregion293294		//#region Bound counters295		/// Amount of collections destroyed, used for total amount tracking with296		/// CreatedCollectionCount297		DestroyedCollectionCount: u32;298		/// Total amount of account owned tokens (NFTs + RFTs + unique fungibles)299		/// Account id (real)300		pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;301		//#endregion302303		//#region Basic collections304		/// Collection info305		/// Collection id (controlled?1)306		pub CollectionById get(fn collection_id) config(): map hasher(blake2_128_concat) CollectionId => Option<Collection<T>> = None;307		/// List of collection admins308		/// Collection id (controlled?2)309		pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec<T::CrossAccountId>;310		/// Whitelisted collection users311		/// Collection id (controlled?2), user id (controlled?3)312		pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool;313		//#endregion314315		/// How many of collection items user have316		/// Collection id (controlled?2), account id (real)317		pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128;318319		/// Amount of items which spender can transfer out of owners account (via transferFrom)320		/// Collection id (controlled?2), (token id (controlled ?2) + owner account id (real) + spender account id (controlled?3))321		/// TODO: Off chain worker should remove from this map when token gets removed322		pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128;323324		//#region Item collections325		/// Collection id (controlled?2), token id (controlled?1)326		pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<NftItemType<T::CrossAccountId>>;327		/// Collection id (controlled?2), owner (controlled?2)328		pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType;329		/// Collection id (controlled?2), token id (controlled?1)330		pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<ReFungibleItemType<T::CrossAccountId>>;331		//#endregion332333		//#region Index list334		/// Collection id (controlled?2), tokens owner (controlled?2)335		pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;336		//#endregion337338		//#region Tokens transfer rate limit baskets339		/// (Collection id (controlled?2), who created (real))340		/// TODO: Off chain worker should remove from this map when collection gets removed341		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;342		/// Collection id (controlled?2), token id (controlled?2)343		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;344		/// Collection id (controlled?2), owning user (real)345		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;346		/// Collection id (controlled?2), token id (controlled?2)347		pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;348		//#endregion349350		/// Variable metadata sponsoring351		/// Collection id (controlled?2), token id (controlled?2)352		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;353	}354	add_extra_genesis {355		build(|config: &GenesisConfig<T>| {356			// Modification of storage357			for (_num, _c) in &config.collection_id {358				<Module<T>>::init_collection(_c);359			}360361			for (_num, _c, _i) in &config.nft_item_id {362				<Module<T>>::init_nft_token(*_c, _i);363			}364365			for (collection_id, account_id, fungible_item) in &config.fungible_item_id {366				<Module<T>>::init_fungible_token(*collection_id, &T::CrossAccountId::from_sub(account_id.clone()), fungible_item);367			}368369			for (_num, _c, _i) in &config.refungible_item_id {370				<Module<T>>::init_refungible_token(*_c, _i);371			}372		})373	}374}375376decl_event!(377	pub enum Event<T>378	where379		AccountId = <T as frame_system::Config>::AccountId,380		CrossAccountId = <T as Config>::CrossAccountId,381	{382		/// New collection was created383		///384		/// # Arguments385		///386		/// * collection_id: Globally unique identifier of newly created collection.387		///388		/// * mode: [CollectionMode] converted into u8.389		///390		/// * account_id: Collection owner.391		CollectionCreated(CollectionId, u8, AccountId),392393		/// New item was created.394		///395		/// # Arguments396		///397		/// * collection_id: Id of the collection where item was created.398		///399		/// * item_id: Id of an item. Unique within the collection.400		///401		/// * recipient: Owner of newly created item402		ItemCreated(CollectionId, TokenId, CrossAccountId),403404		/// Collection item was burned.405		///406		/// # Arguments407		///408		/// collection_id.409		///410		/// item_id: Identifier of burned NFT.411		ItemDestroyed(CollectionId, TokenId),412413		/// Item was transferred414		///415		/// * collection_id: Id of collection to which item is belong416		///417		/// * item_id: Id of an item418		///419		/// * sender: Original owner of item420		///421		/// * recipient: New owner of item422		///423		/// * amount: Always 1 for NFT424		Transfer(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),425426		/// * collection_id427		///428		/// * item_id429		///430		/// * sender431		///432		/// * spender433		///434		/// * amount435		Approved(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),436	}437);438439decl_module! {440	pub struct Module<T: Config> for enum Call441	where442		origin: T::Origin443	{444		fn deposit_event() = default;445		type Error = Error<T>;446447		fn on_initialize(_now: T::BlockNumber) -> Weight {448			0449		}450451		/// 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.452		///453		/// # Permissions454		///455		/// * Anyone.456		///457		/// # Arguments458		///459		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.460		///461		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.462		///463		/// * token_prefix: UTF-8 string with token prefix.464		///465		/// * mode: [CollectionMode] collection type and type dependent data.466		// returns collection ID467		#[weight = <T as Config>::WeightInfo::create_collection()]468		#[transactional]469		pub fn create_collection(origin,470								 collection_name: Vec<u16>,471								 collection_description: Vec<u16>,472								 token_prefix: Vec<u8>,473								 mode: CollectionMode) -> DispatchResult {474475			// Anyone can create a collection476			let who = ensure_signed(origin)?;477478			// Take a (non-refundable) deposit of collection creation479			let mut imbalance = <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();480			imbalance.subsume(<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(481				&T::TreasuryAccountId::get(),482				T::CollectionCreationPrice::get(),483			));484			<T as Config>::Currency::settle(485				&who,486				imbalance,487				WithdrawReasons::TRANSFER,488				ExistenceRequirement::KeepAlive,489			).map_err(|_| Error::<T>::NoPermission)?;490491			let decimal_points = match mode {492				CollectionMode::Fungible(points) => points,493				_ => 0494			};495496			let chain_limit = ChainLimit::get();497498			let created_count = CreatedCollectionCount::get();499			let destroyed_count = DestroyedCollectionCount::get();500501			// bound Total number of collections502			ensure!(created_count - destroyed_count < chain_limit.collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);503504			// check params505			ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);506			ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);507			ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);508			ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);509510			// Generate next collection ID511			let next_id = created_count512				.checked_add(1)513				.ok_or(Error::<T>::NumOverflow)?;514515			CreatedCollectionCount::put(next_id);516517			let limits = CollectionLimits {518				sponsored_data_size: chain_limit.custom_data_limit,519				..Default::default()520			};521522			// Create new collection523			let new_collection = Collection {524				owner: who.clone(),525				name: collection_name,526				mode: mode.clone(),527				mint_mode: false,528				access: AccessMode::Normal,529				description: collection_description,530				decimal_points,531				token_prefix,532				offchain_schema: Vec::new(),533				schema_version: SchemaVersion::ImageURL,534				sponsorship: SponsorshipState::Disabled,535				variable_on_chain_schema: Vec::new(),536				const_on_chain_schema: Vec::new(),537				limits,538				transfers_enabled: true,539			};540541			// Add new collection to map542			<CollectionById<T>>::insert(next_id, new_collection);543544			// call event545			Self::deposit_event(RawEvent::CollectionCreated(next_id, mode.id(), who));546547			Ok(())548		}549550		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.551		///552		/// # Permissions553		///554		/// * Collection Owner.555		///556		/// # Arguments557		///558		/// * collection_id: collection to destroy.559		#[weight = <T as Config>::WeightInfo::destroy_collection()]560		#[transactional]561		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {562563			let sender = ensure_signed(origin)?;564			let collection = Self::get_collection(collection_id)?;565			Self::check_owner_permissions(&collection, &sender)?;566			if !collection.limits.owner_can_destroy {567				fail!(Error::<T>::NoPermission);568			}569570			<AddressTokens<T>>::remove_prefix(collection_id, None);571			<Allowances<T>>::remove_prefix(collection_id, None);572			<Balance<T>>::remove_prefix(collection_id, None);573			<ItemListIndex>::remove(collection_id);574			<AdminList<T>>::remove(collection_id);575			<CollectionById<T>>::remove(collection_id);576			<WhiteList<T>>::remove_prefix(collection_id, None);577578			<NftItemList<T>>::remove_prefix(collection_id, None);579			<FungibleItemList<T>>::remove_prefix(collection_id, None);580			<ReFungibleItemList<T>>::remove_prefix(collection_id, None);581582			<NftTransferBasket<T>>::remove_prefix(collection_id, None);583			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);584			<ReFungibleTransferBasket<T>>::remove_prefix(collection_id, None);585586			<VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);587588			DestroyedCollectionCount::put(DestroyedCollectionCount::get()589				.checked_add(1)590				.ok_or(Error::<T>::NumOverflow)?);591592			Ok(())593		}594595		/// Add an address to white list.596		///597		/// # Permissions598		///599		/// * Collection Owner600		/// * Collection Admin601		///602		/// # Arguments603		///604		/// * collection_id.605		///606		/// * address.607		#[weight = <T as Config>::WeightInfo::add_to_white_list()]608		#[transactional]609		pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{610611			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);612			let collection = Self::get_collection(collection_id)?;613614			Self::toggle_white_list_internal(615				&sender,616				&collection,617				&address,618				true,619			)?;620621			Ok(())622		}623624		/// Remove an address from white list.625		///626		/// # Permissions627		///628		/// * Collection Owner629		/// * Collection Admin630		///631		/// # Arguments632		///633		/// * collection_id.634		///635		/// * address.636		#[weight = <T as Config>::WeightInfo::remove_from_white_list()]637		#[transactional]638		pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{639640			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);641			let collection = Self::get_collection(collection_id)?;642643			Self::toggle_white_list_internal(644				&sender,645				&collection,646				&address,647				false,648			)?;649650			Ok(())651		}652653		/// Toggle between normal and white list access for the methods with access for `Anyone`.654		///655		/// # Permissions656		///657		/// * Collection Owner.658		///659		/// # Arguments660		///661		/// * collection_id.662		///663		/// * mode: [AccessMode]664		#[weight = <T as Config>::WeightInfo::set_public_access_mode()]665		#[transactional]666		pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult667		{668			let sender = ensure_signed(origin)?;669670			let mut target_collection = Self::get_collection(collection_id)?;671			Self::check_owner_permissions(&target_collection, &sender)?;672			target_collection.access = mode;673			target_collection.save()674		}675676		/// Allows Anyone to create tokens if:677		/// * White List is enabled, and678		/// * Address is added to white list, and679		/// * This method was called with True parameter680		///681		/// # Permissions682		/// * Collection Owner683		///684		/// # Arguments685		///686		/// * collection_id.687		///688		/// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.689		#[weight = <T as Config>::WeightInfo::set_mint_permission()]690		#[transactional]691		pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult692		{693			let sender = ensure_signed(origin)?;694695			let mut target_collection = Self::get_collection(collection_id)?;696			Self::check_owner_permissions(&target_collection, &sender)?;697			target_collection.mint_mode = mint_permission;698			target_collection.save()699		}700701		/// Change the owner of the collection.702		///703		/// # Permissions704		///705		/// * Collection Owner.706		///707		/// # Arguments708		///709		/// * collection_id.710		///711		/// * new_owner.712		#[weight = <T as Config>::WeightInfo::change_collection_owner()]713		#[transactional]714		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {715716			let sender = ensure_signed(origin)?;717			let mut target_collection = Self::get_collection(collection_id)?;718			Self::check_owner_permissions(&target_collection, &sender)?;719			target_collection.owner = new_owner;720			target_collection.save()721		}722723		/// Adds an admin of the Collection.724		/// 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.725		///726		/// # Permissions727		///728		/// * Collection Owner.729		/// * Collection Admin.730		///731		/// # Arguments732		///733		/// * collection_id: ID of the Collection to add admin for.734		///735		/// * new_admin_id: Address of new admin to add.736		#[weight = <T as Config>::WeightInfo::add_collection_admin()]737		#[transactional]738		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {739			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);740			let collection = Self::get_collection(collection_id)?;741			Self::check_owner_or_admin_permissions(&collection, &sender)?;742			let mut admin_arr = <AdminList<T>>::get(collection_id);743744			match admin_arr.binary_search(&new_admin_id) {745				Ok(_) => {},746				Err(idx) => {747					let limits = ChainLimit::get();748					ensure!(admin_arr.len() < limits.collections_admins_limit as usize, Error::<T>::CollectionAdminsLimitExceeded);749					admin_arr.insert(idx, new_admin_id);750					<AdminList<T>>::insert(collection_id, admin_arr);751				}752			}753			Ok(())754		}755756		/// 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.757		///758		/// # Permissions759		///760		/// * Collection Owner.761		/// * Collection Admin.762		///763		/// # Arguments764		///765		/// * collection_id: ID of the Collection to remove admin for.766		///767		/// * account_id: Address of admin to remove.768		#[weight = <T as Config>::WeightInfo::remove_collection_admin()]769		#[transactional]770		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {771			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);772			let collection = Self::get_collection(collection_id)?;773			Self::check_owner_or_admin_permissions(&collection, &sender)?;774			let mut admin_arr = <AdminList<T>>::get(collection_id);775776			if let Ok(idx) = admin_arr.binary_search(&account_id) {777				admin_arr.remove(idx);778				<AdminList<T>>::insert(collection_id, admin_arr);779			}780			Ok(())781		}782783		/// # Permissions784		///785		/// * Collection Owner786		///787		/// # Arguments788		///789		/// * collection_id.790		///791		/// * new_sponsor.792		#[weight = <T as Config>::WeightInfo::set_collection_sponsor()]793		#[transactional]794		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {795			let sender = ensure_signed(origin)?;796			let mut target_collection = Self::get_collection(collection_id)?;797			Self::check_owner_permissions(&target_collection, &sender)?;798799			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);800			target_collection.save()801		}802803		/// # Permissions804		///805		/// * Sponsor.806		///807		/// # Arguments808		///809		/// * collection_id.810		#[weight = <T as Config>::WeightInfo::confirm_sponsorship()]811		#[transactional]812		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {813			let sender = ensure_signed(origin)?;814815			let mut target_collection = Self::get_collection(collection_id)?;816			ensure!(817				target_collection.sponsorship.pending_sponsor() == Some(&sender),818				Error::<T>::ConfirmUnsetSponsorFail819			);820821			target_collection.sponsorship = SponsorshipState::Confirmed(sender);822			target_collection.save()823		}824825		/// Switch back to pay-per-own-transaction model.826		///827		/// # Permissions828		///829		/// * Collection owner.830		///831		/// # Arguments832		///833		/// * collection_id.834		#[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]835		#[transactional]836		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {837			let sender = ensure_signed(origin)?;838839			let mut target_collection = Self::get_collection(collection_id)?;840			Self::check_owner_permissions(&target_collection, &sender)?;841842			target_collection.sponsorship = SponsorshipState::Disabled;843			target_collection.save()844		}845846		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.847		///848		/// # Permissions849		///850		/// * Collection Owner.851		/// * Collection Admin.852		/// * Anyone if853		///     * White List is enabled, and854		///     * Address is added to white list, and855		///     * MintPermission is enabled (see SetMintPermission method)856		///857		/// # Arguments858		///859		/// * collection_id: ID of the collection.860		///861		/// * owner: Address, initial owner of the NFT.862		///863		/// * data: Token data to store on chain.864		// #[weight =865		// (130_000_000 as Weight)866		// .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))867		// .saturating_add(RocksDbWeight::get().reads(10 as Weight))868		// .saturating_add(RocksDbWeight::get().writes(8 as Weight))]869870		#[weight = <T as Config>::WeightInfo::create_item(data.data_size())]871		#[transactional]872		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResult {873			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);874			let collection = Self::get_collection(collection_id)?;875876			Self::create_item_internal(&sender, &collection, &owner, data)?;877878			collection.submit_logs()879		}880881		/// This method creates multiple items in a collection created with CreateCollection method.882		///883		/// # Permissions884		///885		/// * Collection Owner.886		/// * Collection Admin.887		/// * Anyone if888		///     * White List is enabled, and889		///     * Address is added to white list, and890		///     * MintPermission is enabled (see SetMintPermission method)891		///892		/// # Arguments893		///894		/// * collection_id: ID of the collection.895		///896		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].897		///898		/// * owner: Address, initial owner of the NFT.899		#[weight = <T as Config>::WeightInfo::create_item(items_data.iter()900							   .map(|data| { data.data_size() })901							   .sum())]902		#[transactional]903		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResult {904905			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);906			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);907			let collection = Self::get_collection(collection_id)?;908909			Self::create_multiple_items_internal(&sender, &collection, &owner, items_data)?;910911			collection.submit_logs()912		}913914		// TODO! transaction weight915916		/// Set transfers_enabled value for particular collection917		///918		/// # Permissions919		///920		/// * Collection Owner.921		///922		/// # Arguments923		///924		/// * collection_id: ID of the collection.925		///926		/// * value: New flag value.927		#[weight = <T as Config>::WeightInfo::burn_item()]928		#[transactional]929		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {930931			let sender = ensure_signed(origin)?;932			let mut target_collection = Self::get_collection(collection_id)?;933934			Self::check_owner_permissions(&target_collection, &sender)?;935936			target_collection.transfers_enabled = value;937			target_collection.save()938		}939940		/// Destroys a concrete instance of NFT.941		///942		/// # Permissions943		///944		/// * Collection Owner.945		/// * Collection Admin.946		/// * Current NFT Owner.947		///948		/// # Arguments949		///950		/// * collection_id: ID of the collection.951		///952		/// * item_id: ID of NFT to burn.953		#[weight = <T as Config>::WeightInfo::burn_item()]954		#[transactional]955		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {956957			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);958			let target_collection = Self::get_collection(collection_id)?;959960			Self::burn_item_internal(&sender, &target_collection, item_id, value)?;961962			target_collection.submit_logs()963		}964965		/// Change ownership of the token.966		///967		/// # Permissions968		///969		/// * Collection Owner970		/// * Collection Admin971		/// * Current NFT owner972		///973		/// # Arguments974		///975		/// * recipient: Address of token recipient.976		///977		/// * collection_id.978		///979		/// * item_id: ID of the item980		///     * Non-Fungible Mode: Required.981		///     * Fungible Mode: Ignored.982		///     * Re-Fungible Mode: Required.983		///984		/// * value: Amount to transfer.985		///     * Non-Fungible Mode: Ignored986		///     * Fungible Mode: Must specify transferred amount987		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)988		#[weight = <T as Config>::WeightInfo::transfer()]989		#[transactional]990		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {991			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);992			let collection = Self::get_collection(collection_id)?;993994			Self::transfer_internal(&sender, &recipient, &collection, item_id, value)?;995996			collection.submit_logs()997		}998999		/// Set, change, or remove approved address to transfer the ownership of the NFT.1000		///1001		/// # Permissions1002		///1003		/// * Collection Owner1004		/// * Collection Admin1005		/// * Current NFT owner1006		///1007		/// # Arguments1008		///1009		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1010		///1011		/// * collection_id.1012		///1013		/// * item_id: ID of the item.1014		#[weight = <T as Config>::WeightInfo::approve()]1015		#[transactional]1016		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {1017			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1018			let collection = Self::get_collection(collection_id)?;10191020			Self::approve_internal(&sender, &spender, &collection, item_id, amount)?;10211022			collection.submit_logs()1023		}10241025		/// 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.1026		///1027		/// # Permissions1028		/// * Collection Owner1029		/// * Collection Admin1030		/// * Current NFT owner1031		/// * Address approved by current NFT owner1032		///1033		/// # Arguments1034		///1035		/// * from: Address that owns token.1036		///1037		/// * recipient: Address of token recipient.1038		///1039		/// * collection_id.1040		///1041		/// * item_id: ID of the item.1042		///1043		/// * value: Amount to transfer.1044		#[weight = <T as Config>::WeightInfo::transfer_from()]1045		#[transactional]1046		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {1047			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1048			let collection = Self::get_collection(collection_id)?;10491050			Self::transfer_from_internal(&sender, &from, &recipient, &collection, item_id, value)?;10511052			collection.submit_logs()1053		}1054		// #[weight = 0]1055		//     // let no_perm_mes = "You do not have permissions to modify this collection";1056		//     // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1057		//     // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1058		//     // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);10591060		//     // // on_nft_received  call10611062		//     // Self::transfer(origin, collection_id, item_id, new_owner)?;10631064		//     Ok(())1065		// }10661067		/// Set off-chain data schema.1068		///1069		/// # Permissions1070		///1071		/// * Collection Owner1072		/// * Collection Admin1073		///1074		/// # Arguments1075		///1076		/// * collection_id.1077		///1078		/// * schema: String representing the offchain data schema.1079		#[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1080		#[transactional]1081		pub fn set_variable_meta_data (1082			origin,1083			collection_id: CollectionId,1084			item_id: TokenId,1085			data: Vec<u8>1086		) -> DispatchResult {1087			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);10881089			let collection = Self::get_collection(collection_id)?;10901091			Self::set_variable_meta_data_internal(&sender, &collection, item_id, data)?;10921093			Ok(())1094		}10951096		/// Set schema standard1097		/// ImageURL1098		/// Unique1099		///1100		/// # Permissions1101		///1102		/// * Collection Owner1103		/// * Collection Admin1104		///1105		/// # Arguments1106		///1107		/// * collection_id.1108		///1109		/// * schema: SchemaVersion: enum1110		#[weight = <T as Config>::WeightInfo::set_schema_version()]1111		#[transactional]1112		pub fn set_schema_version(1113			origin,1114			collection_id: CollectionId,1115			version: SchemaVersion1116		) -> DispatchResult {1117			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1118			let mut target_collection = Self::get_collection(collection_id)?;1119			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;1120			target_collection.schema_version = version;1121			target_collection.save()1122		}11231124		/// Set off-chain data schema.1125		///1126		/// # Permissions1127		///1128		/// * Collection Owner1129		/// * Collection Admin1130		///1131		/// # Arguments1132		///1133		/// * collection_id.1134		///1135		/// * schema: String representing the offchain data schema.1136		#[weight = <T as Config>::WeightInfo::set_offchain_schema()]1137		#[transactional]1138		pub fn set_offchain_schema(1139			origin,1140			collection_id: CollectionId,1141			schema: Vec<u8>1142		) -> DispatchResult {1143			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1144			let mut target_collection = Self::get_collection(collection_id)?;1145			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11461147			// check schema limit1148			ensure!(schema.len() as u32 <= ChainLimit::get().offchain_schema_limit, "");11491150			target_collection.offchain_schema = schema;1151			target_collection.save()1152		}11531154		/// Set const on-chain data schema.1155		///1156		/// # Permissions1157		///1158		/// * Collection Owner1159		/// * Collection Admin1160		///1161		/// # Arguments1162		///1163		/// * collection_id.1164		///1165		/// * schema: String representing the const on-chain data schema.1166		#[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1167		#[transactional]1168		pub fn set_const_on_chain_schema (1169			origin,1170			collection_id: CollectionId,1171			schema: Vec<u8>1172		) -> DispatchResult {1173			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1174			let mut target_collection = Self::get_collection(collection_id)?;1175			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11761177			// check schema limit1178			ensure!(schema.len() as u32 <= ChainLimit::get().const_on_chain_schema_limit, "");11791180			target_collection.const_on_chain_schema = schema;1181			target_collection.save()1182		}11831184		/// Set variable on-chain data schema.1185		///1186		/// # Permissions1187		///1188		/// * Collection Owner1189		/// * Collection Admin1190		///1191		/// # Arguments1192		///1193		/// * collection_id.1194		///1195		/// * schema: String representing the variable on-chain data schema.1196		#[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1197		#[transactional]1198		pub fn set_variable_on_chain_schema (1199			origin,1200			collection_id: CollectionId,1201			schema: Vec<u8>1202		) -> DispatchResult {1203			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1204			let mut target_collection = Self::get_collection(collection_id)?;1205			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;12061207			// check schema limit1208			ensure!(schema.len() as u32 <= ChainLimit::get().variable_on_chain_schema_limit, "");12091210			target_collection.variable_on_chain_schema = schema;1211			target_collection.save()1212		}12131214		// Sudo permissions function1215		#[weight = <T as Config>::WeightInfo::set_chain_limits()]1216		#[transactional]1217		pub fn set_chain_limits(1218			origin,1219			limits: ChainLimits1220		) -> DispatchResult {12211222			#[cfg(not(feature = "runtime-benchmarks"))]1223			ensure_root(origin)?;12241225			<ChainLimit>::put(limits);1226			Ok(())1227		}12281229		#[weight = <T as Config>::WeightInfo::set_collection_limits()]1230		#[transactional]1231		pub fn set_collection_limits(1232			origin,1233			collection_id: u32,1234			new_limits: CollectionLimits<T::BlockNumber>,1235		) -> DispatchResult {1236			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1237			let mut target_collection = Self::get_collection(collection_id)?;1238			Self::check_owner_permissions(&target_collection, sender.as_sub())?;1239			let old_limits = &target_collection.limits;1240			let chain_limits = ChainLimit::get();12411242			// collection bounds1243			ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1244				new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP &&1245				new_limits.sponsored_data_size <= chain_limits.custom_data_limit,1246				Error::<T>::CollectionLimitBoundsExceeded);12471248			// token_limit   check  prev1249			ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1250			ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);12511252			ensure!(1253				(old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1254				(old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1255				Error::<T>::OwnerPermissionsCantBeReverted,1256			);12571258			target_collection.limits = new_limits;12591260			target_collection.save()1261		}1262	}1263}12641265impl<T: Config> Module<T> {1266	pub fn create_item_internal(1267		sender: &T::CrossAccountId,1268		collection: &CollectionHandle<T>,1269		owner: &T::CrossAccountId,1270		data: CreateItemData,1271	) -> DispatchResult {1272		Self::can_create_items_in_collection(collection, sender, owner, 1)?;1273		Self::validate_create_item_args(collection, &data)?;1274		Self::create_item_no_validation(collection, owner, data)?;12751276		Ok(())1277	}12781279	pub fn transfer_internal(1280		sender: &T::CrossAccountId,1281		recipient: &T::CrossAccountId,1282		target_collection: &CollectionHandle<T>,1283		item_id: TokenId,1284		value: u128,1285	) -> DispatchResult {1286		// Limits check1287		Self::is_correct_transfer(target_collection, recipient)?;12881289		// Transfer permissions check1290		ensure!(1291			Self::is_item_owner(sender, target_collection, item_id)?1292				|| Self::is_owner_or_admin_permissions(target_collection, sender)?,1293			Error::<T>::NoPermission1294		);12951296		if target_collection.access == AccessMode::WhiteList {1297			Self::check_white_list(target_collection, sender)?;1298			Self::check_white_list(target_collection, recipient)?;1299		}13001301		match target_collection.mode {1302			CollectionMode::NFT => Self::transfer_nft(1303				target_collection,1304				item_id,1305				sender.clone(),1306				recipient.clone(),1307			)?,1308			CollectionMode::Fungible(_) => {1309				Self::transfer_fungible(target_collection, value, sender, recipient)?1310			}1311			CollectionMode::ReFungible => Self::transfer_refungible(1312				target_collection,1313				item_id,1314				value,1315				sender.clone(),1316				recipient.clone(),1317			)?,1318			_ => (),1319		};13201321		Self::deposit_event(RawEvent::Transfer(1322			target_collection.id,1323			item_id,1324			sender.clone(),1325			recipient.clone(),1326			value,1327		));13281329		Ok(())1330	}13311332	pub fn approve_internal(1333		sender: &T::CrossAccountId,1334		spender: &T::CrossAccountId,1335		collection: &CollectionHandle<T>,1336		item_id: TokenId,1337		amount: u128,1338	) -> DispatchResult {1339		Self::token_exists(collection, item_id)?;13401341		// Transfer permissions check1342		let bypasses_limits = collection.limits.owner_can_transfer1343			&& Self::is_owner_or_admin_permissions(collection, sender)?;13441345		let allowance_limit = if bypasses_limits {1346			None1347		} else if let Some(amount) = Self::owned_amount(sender, collection, item_id)? {1348			Some(amount)1349		} else {1350			fail!(Error::<T>::NoPermission);1351		};13521353		if collection.access == AccessMode::WhiteList {1354			Self::check_white_list(collection, sender)?;1355			Self::check_white_list(collection, spender)?;1356		}13571358		collection.consume_sload()?;1359		let allowance: u128 = amount1360			.checked_add(<Allowances<T>>::get(1361				collection.id,1362				(item_id, sender.as_sub(), spender.as_sub()),1363			))1364			.ok_or(Error::<T>::NumOverflow)?;1365		if let Some(limit) = allowance_limit {1366			ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1367		}1368		collection.consume_sstore()?;1369		<Allowances<T>>::insert(1370			collection.id,1371			(item_id, sender.as_sub(), spender.as_sub()),1372			allowance,1373		);13741375		if matches!(collection.mode, CollectionMode::NFT) {1376			// TODO: NFT: only one owner may exist for token in ERC7211377			collection.log(ERC721Events::Approval {1378				owner: *sender.as_eth(),1379				approved: *spender.as_eth(),1380				token_id: item_id.into(),1381			})?;1382		}13831384		if matches!(collection.mode, CollectionMode::Fungible(_)) {1385			// TODO: NFT: only one owner may exist for token in ERC201386			collection.log(ERC20Events::Approval {1387				owner: *sender.as_eth(),1388				spender: *spender.as_eth(),1389				value: allowance.into(),1390			})?;1391		}13921393		Self::deposit_event(RawEvent::Approved(1394			collection.id,1395			item_id,1396			sender.clone(),1397			spender.clone(),1398			allowance,1399		));1400		Ok(())1401	}14021403	pub fn transfer_from_internal(1404		sender: &T::CrossAccountId,1405		from: &T::CrossAccountId,1406		recipient: &T::CrossAccountId,1407		collection: &CollectionHandle<T>,1408		item_id: TokenId,1409		amount: u128,1410	) -> DispatchResult {1411		// Check approval1412		collection.consume_sload()?;1413		let approval: u128 =1414			<Allowances<T>>::get(collection.id, (item_id, from.as_sub(), sender.as_sub()));14151416		// Limits check1417		Self::is_correct_transfer(collection, recipient)?;14181419		// Transfer permissions check1420		ensure!(1421			approval >= amount1422				|| (collection.limits.owner_can_transfer1423					&& Self::is_owner_or_admin_permissions(collection, sender)?),1424			Error::<T>::NoPermission1425		);14261427		if collection.access == AccessMode::WhiteList {1428			Self::check_white_list(collection, sender)?;1429			Self::check_white_list(collection, recipient)?;1430		}14311432		// Reduce approval by transferred amount or remove if remaining approval drops to 01433		let allowance = approval.saturating_sub(amount);1434		collection.consume_sstore()?;1435		if allowance > 0 {1436			<Allowances<T>>::insert(1437				collection.id,1438				(item_id, from.as_sub(), sender.as_sub()),1439				allowance,1440			);1441		} else {1442			<Allowances<T>>::remove(collection.id, (item_id, from.as_sub(), sender.as_sub()));1443		}14441445		match collection.mode {1446			CollectionMode::NFT => {1447				Self::transfer_nft(collection, item_id, from.clone(), recipient.clone())?1448			}1449			CollectionMode::Fungible(_) => {1450				Self::transfer_fungible(collection, amount, from, recipient)?1451			}1452			CollectionMode::ReFungible => Self::transfer_refungible(1453				collection,1454				item_id,1455				amount,1456				from.clone(),1457				recipient.clone(),1458			)?,1459			_ => (),1460		};14611462		if matches!(collection.mode, CollectionMode::Fungible(_)) {1463			collection.log(ERC20Events::Approval {1464				owner: *from.as_eth(),1465				spender: *sender.as_eth(),1466				value: allowance.into(),1467			})?;1468		}14691470		Ok(())1471	}14721473	pub fn set_variable_meta_data_internal(1474		sender: &T::CrossAccountId,1475		collection: &CollectionHandle<T>,1476		item_id: TokenId,1477		data: Vec<u8>,1478	) -> DispatchResult {1479		Self::token_exists(collection, item_id)?;14801481		ensure!(1482			ChainLimit::get().custom_data_limit >= data.len() as u32,1483			Error::<T>::TokenVariableDataLimitExceeded1484		);14851486		// Modify permissions check1487		ensure!(1488			Self::is_item_owner(sender, collection, item_id)?1489				|| Self::is_owner_or_admin_permissions(collection, sender)?,1490			Error::<T>::NoPermission1491		);14921493		match collection.mode {1494			CollectionMode::NFT => Self::set_nft_variable_data(collection, item_id, data)?,1495			CollectionMode::ReFungible => {1496				Self::set_re_fungible_variable_data(collection, item_id, data)?1497			}1498			CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1499			_ => fail!(Error::<T>::UnexpectedCollectionType),1500		};15011502		Ok(())1503	}15041505	pub fn create_multiple_items_internal(1506		sender: &T::CrossAccountId,1507		collection: &CollectionHandle<T>,1508		owner: &T::CrossAccountId,1509		items_data: Vec<CreateItemData>,1510	) -> DispatchResult {1511		Self::can_create_items_in_collection(collection, sender, owner, items_data.len() as u32)?;15121513		for data in &items_data {1514			Self::validate_create_item_args(collection, data)?;1515		}1516		for data in &items_data {1517			Self::create_item_no_validation(collection, owner, data.clone())?;1518		}15191520		Ok(())1521	}15221523	pub fn burn_item_internal(1524		sender: &T::CrossAccountId,1525		collection: &CollectionHandle<T>,1526		item_id: TokenId,1527		value: u128,1528	) -> DispatchResult {1529		ensure!(1530			Self::is_item_owner(sender, collection, item_id)?1531				|| (collection.limits.owner_can_transfer1532					&& Self::is_owner_or_admin_permissions(collection, sender)?),1533			Error::<T>::NoPermission1534		);15351536		if collection.access == AccessMode::WhiteList {1537			Self::check_white_list(collection, sender)?;1538		}15391540		match collection.mode {1541			CollectionMode::NFT => Self::burn_nft_item(collection, item_id)?,1542			CollectionMode::Fungible(_) => Self::burn_fungible_item(sender, collection, value)?,1543			CollectionMode::ReFungible => Self::burn_refungible_item(collection, item_id, sender)?,1544			_ => (),1545		};15461547		Ok(())1548	}15491550	pub fn toggle_white_list_internal(1551		sender: &T::CrossAccountId,1552		collection: &CollectionHandle<T>,1553		address: &T::CrossAccountId,1554		whitelisted: bool,1555	) -> DispatchResult {1556		Self::check_owner_or_admin_permissions(collection, sender)?;15571558		if whitelisted {1559			<WhiteList<T>>::insert(collection.id, address.as_sub(), true);1560		} else {1561			<WhiteList<T>>::remove(collection.id, address.as_sub());1562		}15631564		Ok(())1565	}15661567	fn is_correct_transfer(1568		collection: &CollectionHandle<T>,1569		recipient: &T::CrossAccountId,1570	) -> DispatchResult {1571		let collection_id = collection.id;15721573		// check token limit and account token limit1574		collection.consume_sload()?;1575		let account_items: u32 =1576			<AddressTokens<T>>::get(collection_id, recipient.as_sub()).len() as u32;1577		ensure!(1578			collection.limits.account_token_ownership_limit > account_items,1579			Error::<T>::AccountTokenLimitExceeded1580		);15811582		// preliminary transfer check1583		ensure!(collection.transfers_enabled, Error::<T>::TransferNotAllowed);15841585		Ok(())1586	}15871588	fn can_create_items_in_collection(1589		collection: &CollectionHandle<T>,1590		sender: &T::CrossAccountId,1591		owner: &T::CrossAccountId,1592		amount: u32,1593	) -> DispatchResult {1594		let collection_id = collection.id;15951596		// check token limit and account token limit1597		let total_items: u32 = ItemListIndex::get(collection_id)1598			.checked_add(amount)1599			.ok_or(Error::<T>::CollectionTokenLimitExceeded)?;1600		let account_items: u32 = (<AddressTokens<T>>::get(collection_id, owner.as_sub()).len()1601			as u32)1602			.checked_add(amount)1603			.ok_or(Error::<T>::AccountTokenLimitExceeded)?;1604		ensure!(1605			collection.limits.token_limit >= total_items,1606			Error::<T>::CollectionTokenLimitExceeded1607		);1608		ensure!(1609			collection.limits.account_token_ownership_limit >= account_items,1610			Error::<T>::AccountTokenLimitExceeded1611		);16121613		if !Self::is_owner_or_admin_permissions(collection, sender)? {1614			ensure!(collection.mint_mode, Error::<T>::PublicMintingNotAllowed);1615			Self::check_white_list(collection, owner)?;1616			Self::check_white_list(collection, sender)?;1617		}16181619		Ok(())1620	}16211622	fn validate_create_item_args(1623		target_collection: &CollectionHandle<T>,1624		data: &CreateItemData,1625	) -> DispatchResult {1626		match target_collection.mode {1627			CollectionMode::NFT => {1628				if let CreateItemData::NFT(data) = data {1629					// check sizes1630					ensure!(1631						ChainLimit::get().custom_data_limit >= data.const_data.len() as u32,1632						Error::<T>::TokenConstDataLimitExceeded1633					);1634					ensure!(1635						ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32,1636						Error::<T>::TokenVariableDataLimitExceeded1637					);1638				} else {1639					fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1640				}1641			}1642			CollectionMode::Fungible(_) => {1643				if let CreateItemData::Fungible(_) = data {1644				} else {1645					fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1646				}1647			}1648			CollectionMode::ReFungible => {1649				if let CreateItemData::ReFungible(data) = data {1650					// check sizes1651					ensure!(1652						ChainLimit::get().custom_data_limit >= data.const_data.len() as u32,1653						Error::<T>::TokenConstDataLimitExceeded1654					);1655					ensure!(1656						ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32,1657						Error::<T>::TokenVariableDataLimitExceeded1658					);16591660					// Check refungibility limits1661					ensure!(1662						data.pieces <= MAX_REFUNGIBLE_PIECES,1663						Error::<T>::WrongRefungiblePieces1664					);1665					ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1666				} else {1667					fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1668				}1669			}1670			_ => {1671				fail!(Error::<T>::UnexpectedCollectionType);1672			}1673		};16741675		Ok(())1676	}16771678	fn create_item_no_validation(1679		collection: &CollectionHandle<T>,1680		owner: &T::CrossAccountId,1681		data: CreateItemData,1682	) -> DispatchResult {1683		match data {1684			CreateItemData::NFT(data) => {1685				let item = NftItemType {1686					owner: owner.clone(),1687					const_data: data.const_data,1688					variable_data: data.variable_data,1689				};16901691				Self::add_nft_item(collection, item)?;1692			}1693			CreateItemData::Fungible(data) => {1694				Self::add_fungible_item(collection, owner, data.value)?;1695			}1696			CreateItemData::ReFungible(data) => {1697				let owner_list = vec![Ownership {1698					owner: owner.clone(),1699					fraction: data.pieces,1700				}];17011702				let item = ReFungibleItemType {1703					owner: owner_list,1704					const_data: data.const_data,1705					variable_data: data.variable_data,1706				};17071708				Self::add_refungible_item(collection, item)?;1709			}1710		};17111712		Ok(())1713	}17141715	fn add_fungible_item(1716		collection: &CollectionHandle<T>,1717		owner: &T::CrossAccountId,1718		value: u128,1719	) -> DispatchResult {1720		let collection_id = collection.id;17211722		// Does new owner already have an account?1723		collection.consume_sload()?;1724		let balance: u128 = <FungibleItemList<T>>::get(collection_id, owner.as_sub()).value;17251726		// Mint1727		let item = FungibleItemType {1728			value: balance.checked_add(value).ok_or(Error::<T>::NumOverflow)?,1729		};1730		collection.consume_sstore()?;1731		<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), item);17321733		// Update balance1734		collection.consume_sload()?;1735		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1736			.checked_add(value)1737			.ok_or(Error::<T>::NumOverflow)?;1738		collection.consume_sstore()?;1739		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);17401741		collection.log(ERC20Events::Transfer {1742			from: H160::default(),1743			to: *owner.as_eth(),1744			value: value.into(),1745		})?;1746		Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));1747		Ok(())1748	}17491750	fn add_refungible_item(1751		collection: &CollectionHandle<T>,1752		item: ReFungibleItemType<T::CrossAccountId>,1753	) -> DispatchResult {1754		let collection_id = collection.id;17551756		let current_index = <ItemListIndex>::get(collection_id)1757			.checked_add(1)1758			.ok_or(Error::<T>::NumOverflow)?;1759		let itemcopy = item.clone();17601761		ensure!(item.owner.len() == 1, Error::<T>::BadCreateRefungibleCall,);1762		let item_owner = item.owner.first().expect("only one owner is defined");17631764		let value = item_owner.fraction;1765		let owner = item_owner.owner.clone();17661767		Self::add_token_index(collection, current_index, &owner)?;17681769		<ItemListIndex>::insert(collection_id, current_index);1770		<ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);17711772		// Update balance1773		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1774			.checked_add(value)1775			.ok_or(Error::<T>::NumOverflow)?;1776		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);17771778		Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));1779		Ok(())1780	}17811782	fn add_nft_item(1783		collection: &CollectionHandle<T>,1784		item: NftItemType<T::CrossAccountId>,1785	) -> DispatchResult {1786		let collection_id = collection.id;17871788		let current_index = <ItemListIndex>::get(collection_id)1789			.checked_add(1)1790			.ok_or(Error::<T>::NumOverflow)?;17911792		let item_owner = item.owner.clone();1793		Self::add_token_index(collection, current_index, &item.owner)?;17941795		<ItemListIndex>::insert(collection_id, current_index);1796		<NftItemList<T>>::insert(collection_id, current_index, item);17971798		// Update balance1799		let new_balance = <Balance<T>>::get(collection_id, item_owner.as_sub())1800			.checked_add(1)1801			.ok_or(Error::<T>::NumOverflow)?;1802		<Balance<T>>::insert(collection_id, item_owner.as_sub(), new_balance);18031804		collection.log(ERC721Events::Transfer {1805			from: H160::default(),1806			to: *item_owner.as_eth(),1807			token_id: current_index.into(),1808		})?;1809		Self::deposit_event(RawEvent::ItemCreated(1810			collection_id,1811			current_index,1812			item_owner,1813		));1814		Ok(())1815	}18161817	fn burn_refungible_item(1818		collection: &CollectionHandle<T>,1819		item_id: TokenId,1820		owner: &T::CrossAccountId,1821	) -> DispatchResult {1822		let collection_id = collection.id;18231824		let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id)1825			.ok_or(Error::<T>::TokenNotFound)?;1826		let rft_balance = token1827			.owner1828			.iter()1829			.find(|&i| i.owner == *owner)1830			.ok_or(Error::<T>::TokenNotFound)?;1831		Self::remove_token_index(collection, item_id, owner)?;18321833		// update balance1834		let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.as_sub())1835			.checked_sub(rft_balance.fraction)1836			.ok_or(Error::<T>::NumOverflow)?;1837		<Balance<T>>::insert(collection_id, rft_balance.owner.as_sub(), new_balance);18381839		// Re-create owners list with sender removed1840		let index = token1841			.owner1842			.iter()1843			.position(|i| i.owner == *owner)1844			.expect("owned item is exists");1845		token.owner.remove(index);1846		let owner_count = token.owner.len();18471848		// Burn the token completely if this was the last (only) owner1849		if owner_count == 0 {1850			<ReFungibleItemList<T>>::remove(collection_id, item_id);1851			<VariableMetaDataBasket<T>>::remove(collection_id, item_id);1852		} else {1853			<ReFungibleItemList<T>>::insert(collection_id, item_id, token);1854		}18551856		Ok(())1857	}18581859	fn burn_nft_item(collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1860		let collection_id = collection.id;18611862		let item =1863			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;1864		Self::remove_token_index(collection, item_id, &item.owner)?;18651866		// update balance1867		let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())1868			.checked_sub(1)1869			.ok_or(Error::<T>::NumOverflow)?;1870		<Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);1871		<NftItemList<T>>::remove(collection_id, item_id);1872		<VariableMetaDataBasket<T>>::remove(collection_id, item_id);18731874		Self::deposit_event(RawEvent::ItemDestroyed(collection.id, item_id));1875		Ok(())1876	}18771878	fn burn_fungible_item(1879		owner: &T::CrossAccountId,1880		collection: &CollectionHandle<T>,1881		value: u128,1882	) -> DispatchResult {1883		let collection_id = collection.id;18841885		let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());1886		ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);18871888		// update balance1889		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1890			.checked_sub(value)1891			.ok_or(Error::<T>::NumOverflow)?;1892		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);18931894		if balance.value - value > 0 {1895			balance.value -= value;1896			<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);1897		} else {1898			<FungibleItemList<T>>::remove(collection_id, owner.as_sub());1899		}19001901		collection.log(ERC20Events::Transfer {1902			from: *owner.as_eth(),1903			to: H160::default(),1904			value: value.into(),1905		})?;1906		Ok(())1907	}19081909	pub fn get_collection(1910		collection_id: CollectionId,1911	) -> Result<CollectionHandle<T>, sp_runtime::DispatchError> {1912		Ok(<CollectionHandle<T>>::get(collection_id).ok_or(Error::<T>::CollectionNotFound)?)1913	}19141915	fn check_owner_permissions(1916		target_collection: &CollectionHandle<T>,1917		subject: &T::AccountId,1918	) -> DispatchResult {1919		ensure!(1920			*subject == target_collection.owner,1921			Error::<T>::NoPermission1922		);19231924		Ok(())1925	}19261927	fn is_owner_or_admin_permissions(1928		collection: &CollectionHandle<T>,1929		subject: &T::CrossAccountId,1930	) -> Result<bool, DispatchError> {1931		collection.consume_sload()?;1932		Ok(*subject.as_sub() == collection.owner1933			|| <AdminList<T>>::get(collection.id).contains(subject))1934	}19351936	fn check_owner_or_admin_permissions(1937		collection: &CollectionHandle<T>,1938		subject: &T::CrossAccountId,1939	) -> DispatchResult {1940		ensure!(1941			Self::is_owner_or_admin_permissions(collection, subject)?,1942			Error::<T>::NoPermission1943		);19441945		Ok(())1946	}19471948	fn owned_amount(1949		subject: &T::CrossAccountId,1950		collection: &CollectionHandle<T>,1951		item_id: TokenId,1952	) -> Result<Option<u128>, DispatchError> {1953		collection.consume_sload()?;1954		Ok(Self::owned_amount_unchecked(subject, collection, item_id))1955	}19561957	fn owned_amount_unchecked(1958		subject: &T::CrossAccountId,1959		target_collection: &CollectionHandle<T>,1960		item_id: TokenId,1961	) -> Option<u128> {1962		let collection_id = target_collection.id;19631964		match target_collection.mode {1965			CollectionMode::NFT => {1966				(<NftItemList<T>>::get(collection_id, item_id)?.owner == *subject).then(|| 1)1967			}1968			CollectionMode::Fungible(_) => {1969				Some(<FungibleItemList<T>>::get(collection_id, &subject.as_sub()).value)1970			}1971			CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)?1972				.owner1973				.iter()1974				.find(|i| i.owner == *subject)1975				.map(|i| i.fraction),1976			CollectionMode::Invalid => None,1977		}1978	}19791980	fn is_item_owner(1981		subject: &T::CrossAccountId,1982		target_collection: &CollectionHandle<T>,1983		item_id: TokenId,1984	) -> Result<bool, DispatchError> {1985		Ok(match target_collection.mode {1986			CollectionMode::Fungible(_) => true,1987			_ => Self::owned_amount(subject, target_collection, item_id)?.is_some(),1988		})1989	}19901991	fn check_white_list(1992		collection: &CollectionHandle<T>,1993		address: &T::CrossAccountId,1994	) -> DispatchResult {1995		collection.consume_sload()?;1996		ensure!(1997			<WhiteList<T>>::contains_key(collection.id, address.as_sub()),1998			Error::<T>::AddresNotInWhiteList,1999		);2000		Ok(())2001	}20022003	/// Check if token exists. In case of Fungible, check if there is an entry for2004	/// the owner in fungible balances double map2005	fn token_exists(target_collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {2006		let collection_id = target_collection.id;2007		let exists = match target_collection.mode {2008			CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),2009			CollectionMode::Fungible(_) => true,2010			CollectionMode::ReFungible => {2011				<ReFungibleItemList<T>>::contains_key(collection_id, item_id)2012			}2013			_ => false,2014		};20152016		ensure!(exists, Error::<T>::TokenNotFound);2017		Ok(())2018	}20192020	fn transfer_fungible(2021		collection: &CollectionHandle<T>,2022		value: u128,2023		owner: &T::CrossAccountId,2024		recipient: &T::CrossAccountId,2025	) -> DispatchResult {2026		let collection_id = collection.id;20272028		collection.consume_sload()?;2029		collection.consume_sload()?;2030		let mut recipient_balance = <FungibleItemList<T>>::get(collection_id, recipient.as_sub());2031		let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());20322033		recipient_balance.value = recipient_balance2034			.value2035			.checked_add(value)2036			.ok_or(Error::<T>::NumOverflow)?;2037		balance.value = balance2038			.value2039			.checked_sub(value)2040			.ok_or(Error::<T>::TokenValueTooLow)?;20412042		// update balanceOf2043		collection.consume_sstore()?;2044		collection.consume_sstore()?;2045		if balance.value != 0 {2046			<Balance<T>>::insert(collection_id, owner.as_sub(), balance.value);2047		} else {2048			<Balance<T>>::remove(collection_id, owner.as_sub());2049		}2050		<Balance<T>>::insert(collection_id, recipient.as_sub(), recipient_balance.value);20512052		// Reduce or remove sender2053		collection.consume_sstore()?;2054		collection.consume_sstore()?;2055		if balance.value != 0 {2056			<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);2057		} else {2058			<FungibleItemList<T>>::remove(collection_id, owner.as_sub());2059		}2060		<FungibleItemList<T>>::insert(collection_id, recipient.as_sub(), recipient_balance);20612062		collection.log(ERC20Events::Transfer {2063			from: *owner.as_eth(),2064			to: *recipient.as_eth(),2065			value: value.into(),2066		})?;2067		Self::deposit_event(RawEvent::Transfer(2068			collection.id,2069			1,2070			owner.clone(),2071			recipient.clone(),2072			value,2073		));20742075		Ok(())2076	}20772078	fn transfer_refungible(2079		collection: &CollectionHandle<T>,2080		item_id: TokenId,2081		value: u128,2082		owner: T::CrossAccountId,2083		new_owner: T::CrossAccountId,2084	) -> DispatchResult {2085		let collection_id = collection.id;2086		collection.consume_sload()?;2087		let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id)2088			.ok_or(Error::<T>::TokenNotFound)?;20892090		let item = full_item2091			.owner2092			.iter()2093			.find(|i| i.owner == owner)2094			.ok_or(Error::<T>::TokenNotFound)?;2095		let amount = item.fraction;20962097		ensure!(amount >= value, Error::<T>::TokenValueTooLow);20982099		collection.consume_sload()?;2100		// update balance2101		let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2102			.checked_sub(value)2103			.ok_or(Error::<T>::NumOverflow)?;2104		collection.consume_sstore()?;2105		<Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);21062107		collection.consume_sload()?;2108		let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2109			.checked_add(value)2110			.ok_or(Error::<T>::NumOverflow)?;2111		collection.consume_sstore()?;2112		<Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);21132114		let old_owner = item.owner.clone();2115		let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);21162117		let mut new_full_item = full_item.clone();2118		// transfer2119		if amount == value && !new_owner_has_account {2120			// change owner2121			// new owner do not have account2122			new_full_item2123				.owner2124				.iter_mut()2125				.find(|i| i.owner == owner)2126				.expect("old owner does present in refungible")2127				.owner = new_owner.clone();2128			collection.consume_sstore()?;2129			<ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);21302131			// update index collection2132			Self::move_token_index(collection, item_id, &old_owner, &new_owner)?;2133		} else {2134			new_full_item2135				.owner2136				.iter_mut()2137				.find(|i| i.owner == owner)2138				.expect("old owner does present in refungible")2139				.fraction -= value;21402141			// separate amount2142			if new_owner_has_account {2143				// new owner has account2144				new_full_item2145					.owner2146					.iter_mut()2147					.find(|i| i.owner == new_owner)2148					.expect("new owner has account")2149					.fraction += value;2150			} else {2151				// new owner do not have account2152				new_full_item.owner.push(Ownership {2153					owner: new_owner.clone(),2154					fraction: value,2155				});2156				Self::add_token_index(collection, item_id, &new_owner)?;2157			}21582159			collection.consume_sstore()?;2160			<ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2161		}21622163		Self::deposit_event(RawEvent::Transfer(2164			collection.id,2165			item_id,2166			owner,2167			new_owner,2168			amount,2169		));21702171		Ok(())2172	}21732174	fn transfer_nft(2175		collection: &CollectionHandle<T>,2176		item_id: TokenId,2177		sender: T::CrossAccountId,2178		new_owner: T::CrossAccountId,2179	) -> DispatchResult {2180		let collection_id = collection.id;2181		collection.consume_sload()?;2182		let mut item =2183			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;21842185		ensure!(sender == item.owner, Error::<T>::MustBeTokenOwner);21862187		collection.consume_sload()?;2188		// update balance2189		let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2190			.checked_sub(1)2191			.ok_or(Error::<T>::NumOverflow)?;2192		collection.consume_sstore()?;2193		<Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);21942195		collection.consume_sload()?;2196		let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2197			.checked_add(1)2198			.ok_or(Error::<T>::NumOverflow)?;2199		collection.consume_sstore()?;2200		<Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);22012202		// change owner2203		let old_owner = item.owner.clone();2204		item.owner = new_owner.clone();2205		collection.consume_sstore()?;2206		<NftItemList<T>>::insert(collection_id, item_id, item);22072208		// update index collection2209		Self::move_token_index(collection, item_id, &old_owner, &new_owner)?;22102211		collection.log(ERC721Events::Transfer {2212			from: *sender.as_eth(),2213			to: *new_owner.as_eth(),2214			token_id: item_id.into(),2215		})?;2216		Self::deposit_event(RawEvent::Transfer(2217			collection.id,2218			item_id,2219			sender,2220			new_owner,2221			1,2222		));22232224		Ok(())2225	}22262227	fn set_re_fungible_variable_data(2228		collection: &CollectionHandle<T>,2229		item_id: TokenId,2230		data: Vec<u8>,2231	) -> DispatchResult {2232		let collection_id = collection.id;2233		let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id)2234			.ok_or(Error::<T>::TokenNotFound)?;22352236		item.variable_data = data;22372238		<ReFungibleItemList<T>>::insert(collection_id, item_id, item);22392240		Ok(())2241	}22422243	fn set_nft_variable_data(2244		collection: &CollectionHandle<T>,2245		item_id: TokenId,2246		data: Vec<u8>,2247	) -> DispatchResult {2248		let collection_id = collection.id;2249		let mut item =2250			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;22512252		item.variable_data = data;22532254		<NftItemList<T>>::insert(collection_id, item_id, item);22552256		Ok(())2257	}22582259	#[allow(dead_code)]2260	fn init_collection(item: &Collection<T>) {2261		// check params2262		assert!(2263			item.decimal_points <= MAX_DECIMAL_POINTS,2264			"decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2265		);2266		assert!(2267			item.name.len() <= 64,2268			"Collection name can not be longer than 63 char"2269		);2270		assert!(2271			item.name.len() <= 256,2272			"Collection description can not be longer than 255 char"2273		);2274		assert!(2275			item.token_prefix.len() <= 16,2276			"Token prefix can not be longer than 15 char"2277		);22782279		// Generate next collection ID2280		let next_id = CreatedCollectionCount::get().checked_add(1).unwrap();22812282		CreatedCollectionCount::put(next_id);2283	}22842285	#[allow(dead_code)]2286	fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::CrossAccountId>) {2287		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();22882289		Self::add_token_index(2290			&CollectionHandle::get(collection_id).unwrap(),2291			current_index,2292			&item.owner,2293		)2294		.unwrap();22952296		<ItemListIndex>::insert(collection_id, current_index);22972298		// Update balance2299		let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2300			.checked_add(1)2301			.unwrap();2302		<Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2303	}23042305	#[allow(dead_code)]2306	fn init_fungible_token(2307		collection_id: CollectionId,2308		owner: &T::CrossAccountId,2309		item: &FungibleItemType,2310	) {2311		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();23122313		Self::add_token_index(2314			&CollectionHandle::get(collection_id).unwrap(),2315			current_index,2316			owner,2317		)2318		.unwrap();23192320		<ItemListIndex>::insert(collection_id, current_index);23212322		// Update balance2323		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2324			.checked_add(item.value)2325			.unwrap();2326		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2327	}23282329	#[allow(dead_code)]2330	fn init_refungible_token(2331		collection_id: CollectionId,2332		item: &ReFungibleItemType<T::CrossAccountId>,2333	) {2334		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();23352336		let value = item.owner.first().unwrap().fraction;2337		let owner = item.owner.first().unwrap().owner.clone();23382339		Self::add_token_index(2340			&CollectionHandle::get(collection_id).unwrap(),2341			current_index,2342			&owner,2343		)2344		.unwrap();23452346		<ItemListIndex>::insert(collection_id, current_index);23472348		// Update balance2349		let new_balance = <Balance<T>>::get(collection_id, &owner.as_sub())2350			.checked_add(value)2351			.unwrap();2352		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2353	}23542355	fn add_token_index(2356		collection: &CollectionHandle<T>,2357		item_index: TokenId,2358		owner: &T::CrossAccountId,2359	) -> DispatchResult {2360		// add to account limit2361		collection.consume_sload()?;2362		if <AccountItemCount<T>>::contains_key(owner.as_sub()) {2363			// bound Owned tokens by a single address2364			collection.consume_sload()?;2365			let count = <AccountItemCount<T>>::get(owner.as_sub());2366			ensure!(2367				count < ChainLimit::get().account_token_ownership_limit,2368				Error::<T>::AddressOwnershipLimitExceeded2369			);23702371			collection.consume_sstore()?;2372			<AccountItemCount<T>>::insert(2373				owner.as_sub(),2374				count.checked_add(1).ok_or(Error::<T>::NumOverflow)?,2375			);2376		} else {2377			collection.consume_sstore()?;2378			<AccountItemCount<T>>::insert(owner.as_sub(), 1);2379		}23802381		collection.consume_sload()?;2382		let list_exists = <AddressTokens<T>>::contains_key(collection.id, owner.as_sub());2383		if list_exists {2384			collection.consume_sload()?;2385			let mut list = <AddressTokens<T>>::get(collection.id, owner.as_sub());2386			let item_contains = list.contains(&item_index.clone());23872388			if !item_contains {2389				list.push(item_index);2390			}23912392			collection.consume_sstore()?;2393			<AddressTokens<T>>::insert(collection.id, owner.as_sub(), list);2394		} else {2395			let itm = vec![item_index];2396			collection.consume_sstore()?;2397			<AddressTokens<T>>::insert(collection.id, owner.as_sub(), itm);2398		}23992400		Ok(())2401	}24022403	fn remove_token_index(2404		collection: &CollectionHandle<T>,2405		item_index: TokenId,2406		owner: &T::CrossAccountId,2407	) -> DispatchResult {2408		// update counter2409		collection.consume_sload()?;2410		collection.consume_sstore()?;2411		<AccountItemCount<T>>::insert(2412			owner.as_sub(),2413			<AccountItemCount<T>>::get(owner.as_sub())2414				.checked_sub(1)2415				.ok_or(Error::<T>::NumOverflow)?,2416		);24172418		collection.consume_sload()?;2419		let list_exists = <AddressTokens<T>>::contains_key(collection.id, owner.as_sub());2420		if list_exists {2421			collection.consume_sload()?;2422			let mut list = <AddressTokens<T>>::get(collection.id, owner.as_sub());2423			let item_contains = list.contains(&item_index.clone());24242425			if item_contains {2426				list.retain(|&item| item != item_index);2427				collection.consume_sstore()?;2428				<AddressTokens<T>>::insert(collection.id, owner.as_sub(), list);2429			}2430		}24312432		Ok(())2433	}24342435	fn move_token_index(2436		collection: &CollectionHandle<T>,2437		item_index: TokenId,2438		old_owner: &T::CrossAccountId,2439		new_owner: &T::CrossAccountId,2440	) -> DispatchResult {2441		Self::remove_token_index(collection, item_index, old_owner)?;2442		Self::add_token_index(collection, item_index, new_owner)?;24432444		Ok(())2445	}2446}24472448sp_api::decl_runtime_apis! {2449	pub trait NftApi {2450		/// Used for ethereum integration2451		fn eth_contract_code(account: H160) -> Option<Vec<u8>>;2452	}2453}