git.delta.rocks / unique-network / refs/commits / 5192a06a09ff

difftreelog

source

pallets/nft/src/lib.rs72.1 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};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	CUSTOM_DATA_LIMIT, COLLECTION_NUMBER_LIMIT, ACCOUNT_TOKEN_OWNERSHIP_LIMIT,42	VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, COLLECTION_ADMINS_LIMIT,43	OFFCHAIN_SCHEMA_LIMIT, AccessMode, Collection, CreateItemData, CollectionLimits, CollectionId,44	CollectionMode, TokenId, SchemaVersion, SponsorshipState, Ownership, NftItemType,45	FungibleItemType, ReFungibleItemType,46};4748#[cfg(test)]49mod mock;5051#[cfg(test)]52mod tests;5354mod default_weights;55mod eth;56mod sponsorship;57pub use sponsorship::NftSponsorshipHandler;58pub use eth::sponsoring::NftEthSponsorshipHandler;5960pub use eth::NftErcSupport;61pub use eth::account::*;62use eth::erc::{ERC20Events, ERC721Events};6364#[cfg(feature = "runtime-benchmarks")]65mod benchmarking;6667pub trait WeightInfo {68	fn create_collection() -> Weight;69	fn destroy_collection() -> Weight;70	fn add_to_white_list() -> Weight;71	fn remove_from_white_list() -> Weight;72	fn set_public_access_mode() -> Weight;73	fn set_mint_permission() -> Weight;74	fn change_collection_owner() -> Weight;75	fn add_collection_admin() -> Weight;76	fn remove_collection_admin() -> Weight;77	fn set_collection_sponsor() -> Weight;78	fn confirm_sponsorship() -> Weight;79	fn remove_collection_sponsor() -> Weight;80	fn create_item(s: usize) -> Weight;81	fn burn_item() -> Weight;82	fn transfer() -> Weight;83	fn approve() -> Weight;84	fn transfer_from() -> Weight;85	fn set_offchain_schema() -> Weight;86	fn set_const_on_chain_schema() -> Weight;87	fn set_variable_on_chain_schema() -> Weight;88	fn set_variable_meta_data() -> Weight;89	fn enable_contract_sponsoring() -> Weight;90	fn set_schema_version() -> Weight;91	fn set_contract_sponsoring_rate_limit() -> Weight;92	fn set_variable_meta_data_sponsoring_rate_limit() -> Weight;93	fn toggle_contract_white_list() -> Weight;94	fn add_to_contract_white_list() -> Weight;95	fn remove_from_contract_white_list() -> Weight;96	fn set_collection_limits() -> Weight;97}9899decl_error! {100	/// Error for non-fungible-token module.101	pub enum Error for Module<T: Config> {102		/// Total collections bound exceeded.103		TotalCollectionsLimitExceeded,104		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.105		CollectionDecimalPointLimitExceeded,106		/// Collection name can not be longer than 63 char.107		CollectionNameLimitExceeded,108		/// Collection description can not be longer than 255 char.109		CollectionDescriptionLimitExceeded,110		/// Token prefix can not be longer than 15 char.111		CollectionTokenPrefixLimitExceeded,112		/// This collection does not exist.113		CollectionNotFound,114		/// Item not exists.115		TokenNotFound,116		/// Admin not found117		AdminNotFound,118		/// Arithmetic calculation overflow.119		NumOverflow,120		/// Account already has admin role.121		AlreadyAdmin,122		/// You do not own this collection.123		NoPermission,124		/// This address is not set as sponsor, use setCollectionSponsor first.125		ConfirmUnsetSponsorFail,126		/// Collection is not in mint mode.127		PublicMintingNotAllowed,128		/// Sender parameter and item owner must be equal.129		MustBeTokenOwner,130		/// Item balance not enough.131		TokenValueTooLow,132		/// Size of item is too large.133		NftSizeLimitExceeded,134		/// No approve found135		ApproveNotFound,136		/// Requested value more than approved.137		TokenValueNotEnough,138		/// Only approved addresses can call this method.139		ApproveRequired,140		/// Address is not in white list.141		AddresNotInWhiteList,142		/// Number of collection admins bound exceeded.143		CollectionAdminsLimitExceeded,144		/// Owned tokens by a single address bound exceeded.145		AddressOwnershipLimitExceeded,146		/// Length of items properties must be greater than 0.147		EmptyArgument,148		/// const_data exceeded data limit.149		TokenConstDataLimitExceeded,150		/// variable_data exceeded data limit.151		TokenVariableDataLimitExceeded,152		/// Not NFT item data used to mint in NFT collection.153		NotNftDataUsedToMintNftCollectionToken,154		/// Not Fungible item data used to mint in Fungible collection.155		NotFungibleDataUsedToMintFungibleCollectionToken,156		/// Not Re Fungible item data used to mint in Re Fungible collection.157		NotReFungibleDataUsedToMintReFungibleCollectionToken,158		/// Unexpected collection type.159		UnexpectedCollectionType,160		/// Can't store metadata in fungible tokens.161		CantStoreMetadataInFungibleTokens,162		/// Collection token limit exceeded163		CollectionTokenLimitExceeded,164		/// Account token limit exceeded per collection165		AccountTokenLimitExceeded,166		/// Collection limit bounds per collection exceeded167		CollectionLimitBoundsExceeded,168		/// Tried to enable permissions which are only permitted to be disabled169		OwnerPermissionsCantBeReverted,170		/// Schema data size limit bound exceeded171		SchemaDataLimitExceeded,172		/// Maximum refungibility exceeded173		WrongRefungiblePieces,174		/// createRefungible should be called with one owner175		BadCreateRefungibleCall,176		/// Gas limit exceeded177		OutOfGas,178		/// Collection settings not allowing items transferring179		TransferNotAllowed,180		/// Can't transfer tokens to ethereum zero address181		AddressIsZero,182	}183}184185#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]186pub struct CollectionHandle<T: Config> {187	pub id: CollectionId,188	collection: Collection<T>,189	recorder: pallet_evm_coder_substrate::SubstrateRecorder<T>,190}191impl<T: Config> CollectionHandle<T> {192	pub fn get_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {193		<CollectionById<T>>::get(id).map(|collection| Self {194			id,195			collection,196			recorder: pallet_evm_coder_substrate::SubstrateRecorder::new(197				eth::collection_id_to_address(id),198				gas_limit,199			),200		})201	}202	pub fn get(id: CollectionId) -> Option<Self> {203		Self::get_with_gas_limit(id, u64::MAX)204	}205	pub fn log(&self, log: impl evm_coder::ToLog) -> DispatchResult {206		self.recorder.log_sub(log)207	}208	#[allow(dead_code)]209	fn consume_gas(&self, gas: u64) -> DispatchResult {210		self.recorder.consume_gas_sub(gas)211	}212	fn consume_sload(&self) -> DispatchResult {213		self.recorder.consume_sload_sub()214	}215	fn consume_sstore(&self) -> DispatchResult {216		self.recorder.consume_sstore_sub()217	}218	pub fn submit_logs(self) -> DispatchResult {219		self.recorder.submit_logs()220	}221	pub fn save(self) -> DispatchResult {222		self.recorder.submit_logs()?;223		<CollectionById<T>>::insert(self.id, self.collection);224		Ok(())225	}226}227impl<T: Config> Deref for CollectionHandle<T> {228	type Target = Collection<T>;229230	fn deref(&self) -> &Self::Target {231		&self.collection232	}233}234235impl<T: Config> DerefMut for CollectionHandle<T> {236	fn deref_mut(&mut self) -> &mut Self::Target {237		&mut self.collection238	}239}240241pub trait Config: system::Config + pallet_evm_coder_substrate::Config + Sized {242	type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;243244	/// Weight information for extrinsics in this pallet.245	type WeightInfo: WeightInfo;246247	type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;248	type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;249250	type CrossAccountId: CrossAccountId<Self::AccountId>;251	type Currency: Currency<Self::AccountId>;252	type CollectionCreationPrice: Get<253		<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,254	>;255	type TreasuryAccountId: Get<Self::AccountId>;256}257258// # Used definitions259//260// ## User control levels261//262// chain-controlled - key is uncontrolled by user263//                    i.e autoincrementing index264//                    can use non-cryptographic hash265// real - key is controlled by user266//        but it is hard to generate enough colliding values, i.e owner of signed txs267//        can use non-cryptographic hash268// controlled - key is completly controlled by users269//              i.e maps with mutable keys270//              should use cryptographic hash271//272// ## User control level downgrade reasons273//274// ?1 - chain-controlled -> controlled275//      collections/tokens can be destroyed, resulting in massive holes276// ?2 - chain-controlled -> controlled277//      same as ?1, but can be only added, resulting in easier exploitation278// ?3 - real -> controlled279//      no confirmation required, so addresses can be easily generated280decl_storage! {281	trait Store for Module<T: Config> as Nft {282283		//#region Private members284		/// Id of next collection285		CreatedCollectionCount: u32;286		/// Used for migrations287		ChainVersion: u64;288		/// Id of last collection token289		/// Collection id (controlled?1)290		ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;291		//#endregion292293		//#region Bound counters294		/// Amount of collections destroyed, used for total amount tracking with295		/// CreatedCollectionCount296		DestroyedCollectionCount: u32;297		/// Total amount of account owned tokens (NFTs + RFTs + unique fungibles)298		/// Account id (real)299		pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;300		//#endregion301302		//#region Basic collections303		/// Collection info304		/// Collection id (controlled?1)305		pub CollectionById get(fn collection_id) config(): map hasher(blake2_128_concat) CollectionId => Option<Collection<T>> = None;306		/// List of collection admins307		/// Collection id (controlled?2)308		pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec<T::CrossAccountId>;309		/// Whitelisted collection users310		/// Collection id (controlled?2), user id (controlled?3)311		pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool;312		//#endregion313314		/// How many of collection items user have315		/// Collection id (controlled?2), account id (real)316		pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128;317318		/// Amount of items which spender can transfer out of owners account (via transferFrom)319		/// Collection id (controlled?2), (token id (controlled ?2) + owner account id (real) + spender account id (controlled?3))320		/// TODO: Off chain worker should remove from this map when token gets removed321		pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128;322323		//#region Item collections324		/// Collection id (controlled?2), token id (controlled?1)325		pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<NftItemType<T::CrossAccountId>>;326		/// Collection id (controlled?2), owner (controlled?2)327		pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType;328		/// Collection id (controlled?2), token id (controlled?1)329		pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<ReFungibleItemType<T::CrossAccountId>>;330		//#endregion331332		//#region Index list333		/// Collection id (controlled?2), tokens owner (controlled?2)334		pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;335		//#endregion336337		//#region Tokens transfer rate limit baskets338		/// (Collection id (controlled?2), who created (real))339		/// TODO: Off chain worker should remove from this map when collection gets removed340		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;341		/// Collection id (controlled?2), token id (controlled?2)342		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;343		/// Collection id (controlled?2), owning user (real)344		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;345		/// Collection id (controlled?2), token id (controlled?2)346		pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;347		//#endregion348349		/// Variable metadata sponsoring350		/// Collection id (controlled?2), token id (controlled?2)351		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;352	}353	add_extra_genesis {354		build(|config: &GenesisConfig<T>| {355			// Modification of storage356			for (_num, _c) in &config.collection_id {357				<Module<T>>::init_collection(_c);358			}359360			for (_num, _c, _i) in &config.nft_item_id {361				<Module<T>>::init_nft_token(*_c, _i);362			}363364			for (collection_id, account_id, fungible_item) in &config.fungible_item_id {365				<Module<T>>::init_fungible_token(*collection_id, &T::CrossAccountId::from_sub(account_id.clone()), fungible_item);366			}367368			for (_num, _c, _i) in &config.refungible_item_id {369				<Module<T>>::init_refungible_token(*_c, _i);370			}371		})372	}373}374375decl_event!(376	pub enum Event<T>377	where378		AccountId = <T as frame_system::Config>::AccountId,379		CrossAccountId = <T as Config>::CrossAccountId,380	{381		/// New collection was created382		///383		/// # Arguments384		///385		/// * collection_id: Globally unique identifier of newly created collection.386		///387		/// * mode: [CollectionMode] converted into u8.388		///389		/// * account_id: Collection owner.390		CollectionCreated(CollectionId, u8, AccountId),391392		/// New item was created.393		///394		/// # Arguments395		///396		/// * collection_id: Id of the collection where item was created.397		///398		/// * item_id: Id of an item. Unique within the collection.399		///400		/// * recipient: Owner of newly created item401		ItemCreated(CollectionId, TokenId, CrossAccountId),402403		/// Collection item was burned.404		///405		/// # Arguments406		///407		/// collection_id.408		///409		/// item_id: Identifier of burned NFT.410		ItemDestroyed(CollectionId, TokenId),411412		/// Item was transferred413		///414		/// * collection_id: Id of collection to which item is belong415		///416		/// * item_id: Id of an item417		///418		/// * sender: Original owner of item419		///420		/// * recipient: New owner of item421		///422		/// * amount: Always 1 for NFT423		Transfer(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),424425		/// * collection_id426		///427		/// * item_id428		///429		/// * sender430		///431		/// * spender432		///433		/// * amount434		Approved(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),435	}436);437438decl_module! {439	pub struct Module<T: Config> for enum Call440	where441		origin: T::Origin442	{443		fn deposit_event() = default;444		const CollectionAdminsLimit: u64 = COLLECTION_ADMINS_LIMIT;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 created_count = CreatedCollectionCount::get();497			let destroyed_count = DestroyedCollectionCount::get();498499			// bound Total number of collections500			ensure!(created_count - destroyed_count < COLLECTION_NUMBER_LIMIT, Error::<T>::TotalCollectionsLimitExceeded);501502			// check params503			ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);504			ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);505			ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);506			ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);507508			// Generate next collection ID509			let next_id = created_count510				.checked_add(1)511				.ok_or(Error::<T>::NumOverflow)?;512513			CreatedCollectionCount::put(next_id);514515			let limits = CollectionLimits {516				sponsored_data_size: CUSTOM_DATA_LIMIT,517				..Default::default()518			};519520			// Create new collection521			let new_collection = Collection {522				owner: who.clone(),523				name: collection_name,524				mode: mode.clone(),525				mint_mode: false,526				access: AccessMode::Normal,527				description: collection_description,528				decimal_points,529				token_prefix,530				offchain_schema: Vec::new(),531				schema_version: SchemaVersion::ImageURL,532				sponsorship: SponsorshipState::Disabled,533				variable_on_chain_schema: Vec::new(),534				const_on_chain_schema: Vec::new(),535				limits,536				transfers_enabled: true,537			};538539			// Add new collection to map540			<CollectionById<T>>::insert(next_id, new_collection);541542			// call event543			Self::deposit_event(RawEvent::CollectionCreated(next_id, mode.id(), who));544545			Ok(())546		}547548		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.549		///550		/// # Permissions551		///552		/// * Collection Owner.553		///554		/// # Arguments555		///556		/// * collection_id: collection to destroy.557		#[weight = <T as Config>::WeightInfo::destroy_collection()]558		#[transactional]559		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {560561			let sender = ensure_signed(origin)?;562			let collection = Self::get_collection(collection_id)?;563			Self::check_owner_permissions(&collection, &sender)?;564			if !collection.limits.owner_can_destroy {565				fail!(Error::<T>::NoPermission);566			}567568			<AddressTokens<T>>::remove_prefix(collection_id, None);569			<Allowances<T>>::remove_prefix(collection_id, None);570			<Balance<T>>::remove_prefix(collection_id, None);571			<ItemListIndex>::remove(collection_id);572			<AdminList<T>>::remove(collection_id);573			<CollectionById<T>>::remove(collection_id);574			<WhiteList<T>>::remove_prefix(collection_id, None);575576			<NftItemList<T>>::remove_prefix(collection_id, None);577			<FungibleItemList<T>>::remove_prefix(collection_id, None);578			<ReFungibleItemList<T>>::remove_prefix(collection_id, None);579580			<NftTransferBasket<T>>::remove_prefix(collection_id, None);581			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);582			<ReFungibleTransferBasket<T>>::remove_prefix(collection_id, None);583584			<VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);585586			DestroyedCollectionCount::put(DestroyedCollectionCount::get()587				.checked_add(1)588				.ok_or(Error::<T>::NumOverflow)?);589590			Ok(())591		}592593		/// Add an address to white list.594		///595		/// # Permissions596		///597		/// * Collection Owner598		/// * Collection Admin599		///600		/// # Arguments601		///602		/// * collection_id.603		///604		/// * address.605		#[weight = <T as Config>::WeightInfo::add_to_white_list()]606		#[transactional]607		pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{608609			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);610			let collection = Self::get_collection(collection_id)?;611612			Self::toggle_white_list_internal(613				&sender,614				&collection,615				&address,616				true,617			)?;618619			Ok(())620		}621622		/// Remove an address from white list.623		///624		/// # Permissions625		///626		/// * Collection Owner627		/// * Collection Admin628		///629		/// # Arguments630		///631		/// * collection_id.632		///633		/// * address.634		#[weight = <T as Config>::WeightInfo::remove_from_white_list()]635		#[transactional]636		pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{637638			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);639			let collection = Self::get_collection(collection_id)?;640641			Self::toggle_white_list_internal(642				&sender,643				&collection,644				&address,645				false,646			)?;647648			Ok(())649		}650651		/// Toggle between normal and white list access for the methods with access for `Anyone`.652		///653		/// # Permissions654		///655		/// * Collection Owner.656		///657		/// # Arguments658		///659		/// * collection_id.660		///661		/// * mode: [AccessMode]662		#[weight = <T as Config>::WeightInfo::set_public_access_mode()]663		#[transactional]664		pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult665		{666			let sender = ensure_signed(origin)?;667668			let mut target_collection = Self::get_collection(collection_id)?;669			Self::check_owner_permissions(&target_collection, &sender)?;670			target_collection.access = mode;671			target_collection.save()672		}673674		/// Allows Anyone to create tokens if:675		/// * White List is enabled, and676		/// * Address is added to white list, and677		/// * This method was called with True parameter678		///679		/// # Permissions680		/// * Collection Owner681		///682		/// # Arguments683		///684		/// * collection_id.685		///686		/// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.687		#[weight = <T as Config>::WeightInfo::set_mint_permission()]688		#[transactional]689		pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult690		{691			let sender = ensure_signed(origin)?;692693			let mut target_collection = Self::get_collection(collection_id)?;694			Self::check_owner_permissions(&target_collection, &sender)?;695			target_collection.mint_mode = mint_permission;696			target_collection.save()697		}698699		/// Change the owner of the collection.700		///701		/// # Permissions702		///703		/// * Collection Owner.704		///705		/// # Arguments706		///707		/// * collection_id.708		///709		/// * new_owner.710		#[weight = <T as Config>::WeightInfo::change_collection_owner()]711		#[transactional]712		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {713714			let sender = ensure_signed(origin)?;715			let mut target_collection = Self::get_collection(collection_id)?;716			Self::check_owner_permissions(&target_collection, &sender)?;717			target_collection.owner = new_owner;718			target_collection.save()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					ensure!(admin_arr.len() < COLLECTION_ADMINS_LIMIT as usize, Error::<T>::CollectionAdminsLimitExceeded);746					admin_arr.insert(idx, new_admin_id);747					<AdminList<T>>::insert(collection_id, admin_arr);748				}749			}750			Ok(())751		}752753		/// 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.754		///755		/// # Permissions756		///757		/// * Collection Owner.758		/// * Collection Admin.759		///760		/// # Arguments761		///762		/// * collection_id: ID of the Collection to remove admin for.763		///764		/// * account_id: Address of admin to remove.765		#[weight = <T as Config>::WeightInfo::remove_collection_admin()]766		#[transactional]767		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {768			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);769			let collection = Self::get_collection(collection_id)?;770			Self::check_owner_or_admin_permissions(&collection, &sender)?;771			let mut admin_arr = <AdminList<T>>::get(collection_id);772773			if let Ok(idx) = admin_arr.binary_search(&account_id) {774				admin_arr.remove(idx);775				<AdminList<T>>::insert(collection_id, admin_arr);776			}777			Ok(())778		}779780		/// # Permissions781		///782		/// * Collection Owner783		///784		/// # Arguments785		///786		/// * collection_id.787		///788		/// * new_sponsor.789		#[weight = <T as Config>::WeightInfo::set_collection_sponsor()]790		#[transactional]791		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {792			let sender = ensure_signed(origin)?;793			let mut target_collection = Self::get_collection(collection_id)?;794			Self::check_owner_permissions(&target_collection, &sender)?;795796			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);797			target_collection.save()798		}799800		/// # Permissions801		///802		/// * Sponsor.803		///804		/// # Arguments805		///806		/// * collection_id.807		#[weight = <T as Config>::WeightInfo::confirm_sponsorship()]808		#[transactional]809		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {810			let sender = ensure_signed(origin)?;811812			let mut target_collection = Self::get_collection(collection_id)?;813			ensure!(814				target_collection.sponsorship.pending_sponsor() == Some(&sender),815				Error::<T>::ConfirmUnsetSponsorFail816			);817818			target_collection.sponsorship = SponsorshipState::Confirmed(sender);819			target_collection.save()820		}821822		/// Switch back to pay-per-own-transaction model.823		///824		/// # Permissions825		///826		/// * Collection owner.827		///828		/// # Arguments829		///830		/// * collection_id.831		#[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]832		#[transactional]833		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {834			let sender = ensure_signed(origin)?;835836			let mut target_collection = Self::get_collection(collection_id)?;837			Self::check_owner_permissions(&target_collection, &sender)?;838839			target_collection.sponsorship = SponsorshipState::Disabled;840			target_collection.save()841		}842843		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.844		///845		/// # Permissions846		///847		/// * Collection Owner.848		/// * Collection Admin.849		/// * Anyone if850		///     * White List is enabled, and851		///     * Address is added to white list, and852		///     * MintPermission is enabled (see SetMintPermission method)853		///854		/// # Arguments855		///856		/// * collection_id: ID of the collection.857		///858		/// * owner: Address, initial owner of the NFT.859		///860		/// * data: Token data to store on chain.861		// #[weight =862		// (130_000_000 as Weight)863		// .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))864		// .saturating_add(RocksDbWeight::get().reads(10 as Weight))865		// .saturating_add(RocksDbWeight::get().writes(8 as Weight))]866867		#[weight = <T as Config>::WeightInfo::create_item(data.data_size())]868		#[transactional]869		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResult {870			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);871			let collection = Self::get_collection(collection_id)?;872873			Self::create_item_internal(&sender, &collection, &owner, data)?;874875			collection.submit_logs()876		}877878		/// This method creates multiple items in a collection created with CreateCollection method.879		///880		/// # Permissions881		///882		/// * Collection Owner.883		/// * Collection Admin.884		/// * Anyone if885		///     * White List is enabled, and886		///     * Address is added to white list, and887		///     * MintPermission is enabled (see SetMintPermission method)888		///889		/// # Arguments890		///891		/// * collection_id: ID of the collection.892		///893		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].894		///895		/// * owner: Address, initial owner of the NFT.896		#[weight = <T as Config>::WeightInfo::create_item(items_data.iter()897							   .map(|data| { data.data_size() })898							   .sum())]899		#[transactional]900		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResult {901902			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);903			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);904			let collection = Self::get_collection(collection_id)?;905906			Self::create_multiple_items_internal(&sender, &collection, &owner, items_data)?;907908			collection.submit_logs()909		}910911		// TODO! transaction weight912913		/// Set transfers_enabled value for particular collection914		///915		/// # Permissions916		///917		/// * Collection Owner.918		///919		/// # Arguments920		///921		/// * collection_id: ID of the collection.922		///923		/// * value: New flag value.924		#[weight = <T as Config>::WeightInfo::burn_item()]925		#[transactional]926		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {927928			let sender = ensure_signed(origin)?;929			let mut target_collection = Self::get_collection(collection_id)?;930931			Self::check_owner_permissions(&target_collection, &sender)?;932933			target_collection.transfers_enabled = value;934			target_collection.save()935		}936937		/// Destroys a concrete instance of NFT.938		///939		/// # Permissions940		///941		/// * Collection Owner.942		/// * Collection Admin.943		/// * Current NFT Owner.944		///945		/// # Arguments946		///947		/// * collection_id: ID of the collection.948		///949		/// * item_id: ID of NFT to burn.950		#[weight = <T as Config>::WeightInfo::burn_item()]951		#[transactional]952		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {953954			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);955			let target_collection = Self::get_collection(collection_id)?;956957			Self::burn_item_internal(&sender, &target_collection, item_id, value)?;958959			target_collection.submit_logs()960		}961962		/// Change ownership of the token.963		///964		/// # Permissions965		///966		/// * Collection Owner967		/// * Collection Admin968		/// * Current NFT owner969		///970		/// # Arguments971		///972		/// * recipient: Address of token recipient.973		///974		/// * collection_id.975		///976		/// * item_id: ID of the item977		///     * Non-Fungible Mode: Required.978		///     * Fungible Mode: Ignored.979		///     * Re-Fungible Mode: Required.980		///981		/// * value: Amount to transfer.982		///     * Non-Fungible Mode: Ignored983		///     * Fungible Mode: Must specify transferred amount984		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)985		#[weight = <T as Config>::WeightInfo::transfer()]986		#[transactional]987		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {988			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);989			let collection = Self::get_collection(collection_id)?;990991			Self::transfer_internal(&sender, &recipient, &collection, item_id, value)?;992993			collection.submit_logs()994		}995996		/// Set, change, or remove approved address to transfer the ownership of the NFT.997		///998		/// # Permissions999		///1000		/// * Collection Owner1001		/// * Collection Admin1002		/// * Current NFT owner1003		///1004		/// # Arguments1005		///1006		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1007		///1008		/// * collection_id.1009		///1010		/// * item_id: ID of the item.1011		#[weight = <T as Config>::WeightInfo::approve()]1012		#[transactional]1013		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {1014			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1015			let collection = Self::get_collection(collection_id)?;10161017			Self::approve_internal(&sender, &spender, &collection, item_id, amount)?;10181019			collection.submit_logs()1020		}10211022		/// 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.1023		///1024		/// # Permissions1025		/// * Collection Owner1026		/// * Collection Admin1027		/// * Current NFT owner1028		/// * Address approved by current NFT owner1029		///1030		/// # Arguments1031		///1032		/// * from: Address that owns token.1033		///1034		/// * recipient: Address of token recipient.1035		///1036		/// * collection_id.1037		///1038		/// * item_id: ID of the item.1039		///1040		/// * value: Amount to transfer.1041		#[weight = <T as Config>::WeightInfo::transfer_from()]1042		#[transactional]1043		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {1044			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1045			let collection = Self::get_collection(collection_id)?;10461047			Self::transfer_from_internal(&sender, &from, &recipient, &collection, item_id, value)?;10481049			collection.submit_logs()1050		}1051		// #[weight = 0]1052		//     // let no_perm_mes = "You do not have permissions to modify this collection";1053		//     // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1054		//     // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1055		//     // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);10561057		//     // // on_nft_received  call10581059		//     // Self::transfer(origin, collection_id, item_id, new_owner)?;10601061		//     Ok(())1062		// }10631064		/// Set off-chain data schema.1065		///1066		/// # Permissions1067		///1068		/// * Collection Owner1069		/// * Collection Admin1070		///1071		/// # Arguments1072		///1073		/// * collection_id.1074		///1075		/// * schema: String representing the offchain data schema.1076		#[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1077		#[transactional]1078		pub fn set_variable_meta_data (1079			origin,1080			collection_id: CollectionId,1081			item_id: TokenId,1082			data: Vec<u8>1083		) -> DispatchResult {1084			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);10851086			let collection = Self::get_collection(collection_id)?;10871088			Self::set_variable_meta_data_internal(&sender, &collection, item_id, data)?;10891090			Ok(())1091		}10921093		/// Set schema standard1094		/// ImageURL1095		/// Unique1096		///1097		/// # Permissions1098		///1099		/// * Collection Owner1100		/// * Collection Admin1101		///1102		/// # Arguments1103		///1104		/// * collection_id.1105		///1106		/// * schema: SchemaVersion: enum1107		#[weight = <T as Config>::WeightInfo::set_schema_version()]1108		#[transactional]1109		pub fn set_schema_version(1110			origin,1111			collection_id: CollectionId,1112			version: SchemaVersion1113		) -> DispatchResult {1114			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1115			let mut target_collection = Self::get_collection(collection_id)?;1116			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;1117			target_collection.schema_version = version;1118			target_collection.save()1119		}11201121		/// Set off-chain data schema.1122		///1123		/// # Permissions1124		///1125		/// * Collection Owner1126		/// * Collection Admin1127		///1128		/// # Arguments1129		///1130		/// * collection_id.1131		///1132		/// * schema: String representing the offchain data schema.1133		#[weight = <T as Config>::WeightInfo::set_offchain_schema()]1134		#[transactional]1135		pub fn set_offchain_schema(1136			origin,1137			collection_id: CollectionId,1138			schema: Vec<u8>1139		) -> DispatchResult {1140			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1141			let mut target_collection = Self::get_collection(collection_id)?;1142			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11431144			// check schema limit1145			ensure!(schema.len() as u32 <= OFFCHAIN_SCHEMA_LIMIT, "");11461147			target_collection.offchain_schema = schema;1148			target_collection.save()1149		}11501151		/// Set const on-chain data schema.1152		///1153		/// # Permissions1154		///1155		/// * Collection Owner1156		/// * Collection Admin1157		///1158		/// # Arguments1159		///1160		/// * collection_id.1161		///1162		/// * schema: String representing the const on-chain data schema.1163		#[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1164		#[transactional]1165		pub fn set_const_on_chain_schema (1166			origin,1167			collection_id: CollectionId,1168			schema: Vec<u8>1169		) -> DispatchResult {1170			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1171			let mut target_collection = Self::get_collection(collection_id)?;1172			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11731174			// check schema limit1175			ensure!(schema.len() as u32 <= CONST_ON_CHAIN_SCHEMA_LIMIT, "");11761177			target_collection.const_on_chain_schema = schema;1178			target_collection.save()1179		}11801181		/// Set variable on-chain data schema.1182		///1183		/// # Permissions1184		///1185		/// * Collection Owner1186		/// * Collection Admin1187		///1188		/// # Arguments1189		///1190		/// * collection_id.1191		///1192		/// * schema: String representing the variable on-chain data schema.1193		#[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1194		#[transactional]1195		pub fn set_variable_on_chain_schema (1196			origin,1197			collection_id: CollectionId,1198			schema: Vec<u8>1199		) -> DispatchResult {1200			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1201			let mut target_collection = Self::get_collection(collection_id)?;1202			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;12031204			// check schema limit1205			ensure!(schema.len() as u32 <= VARIABLE_ON_CHAIN_SCHEMA_LIMIT, "");12061207			target_collection.variable_on_chain_schema = schema;1208			target_collection.save()1209		}12101211		#[weight = <T as Config>::WeightInfo::set_collection_limits()]1212		#[transactional]1213		pub fn set_collection_limits(1214			origin,1215			collection_id: u32,1216			new_limits: CollectionLimits<T::BlockNumber>,1217		) -> DispatchResult {1218			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1219			let mut target_collection = Self::get_collection(collection_id)?;1220			Self::check_owner_permissions(&target_collection, sender.as_sub())?;1221			let old_limits = &target_collection.limits;12221223			// collection bounds1224			ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1225				new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP &&1226				new_limits.sponsored_data_size <= CUSTOM_DATA_LIMIT,1227				Error::<T>::CollectionLimitBoundsExceeded);12281229			// token_limit   check  prev1230			ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1231			ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);12321233			ensure!(1234				(old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1235				(old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1236				Error::<T>::OwnerPermissionsCantBeReverted,1237			);12381239			target_collection.limits = new_limits;12401241			target_collection.save()1242		}1243	}1244}12451246impl<T: Config> Module<T> {1247	pub fn create_item_internal(1248		sender: &T::CrossAccountId,1249		collection: &CollectionHandle<T>,1250		owner: &T::CrossAccountId,1251		data: CreateItemData,1252	) -> DispatchResult {1253		ensure!(1254			owner != &T::CrossAccountId::from_eth(H160([0; 20])),1255			Error::<T>::AddressIsZero1256		);12571258		Self::can_create_items_in_collection(collection, sender, owner, 1)?;1259		Self::validate_create_item_args(collection, &data)?;1260		Self::create_item_no_validation(collection, owner, data)?;12611262		Ok(())1263	}12641265	pub fn transfer_internal(1266		sender: &T::CrossAccountId,1267		recipient: &T::CrossAccountId,1268		target_collection: &CollectionHandle<T>,1269		item_id: TokenId,1270		value: u128,1271	) -> DispatchResult {1272		ensure!(1273			recipient != &T::CrossAccountId::from_eth(H160([0; 20])),1274			Error::<T>::AddressIsZero1275		);12761277		// Limits check1278		Self::is_correct_transfer(target_collection, recipient)?;12791280		// Transfer permissions check1281		ensure!(1282			Self::is_item_owner(sender, target_collection, item_id)?1283				|| Self::is_owner_or_admin_permissions(target_collection, sender)?,1284			Error::<T>::NoPermission1285		);12861287		if target_collection.access == AccessMode::WhiteList {1288			Self::check_white_list(target_collection, sender)?;1289			Self::check_white_list(target_collection, recipient)?;1290		}12911292		match target_collection.mode {1293			CollectionMode::NFT => Self::transfer_nft(1294				target_collection,1295				item_id,1296				sender.clone(),1297				recipient.clone(),1298			)?,1299			CollectionMode::Fungible(_) => {1300				Self::transfer_fungible(target_collection, value, sender, recipient)?1301			}1302			CollectionMode::ReFungible => Self::transfer_refungible(1303				target_collection,1304				item_id,1305				value,1306				sender.clone(),1307				recipient.clone(),1308			)?,1309			_ => (),1310		};13111312		Self::deposit_event(RawEvent::Transfer(1313			target_collection.id,1314			item_id,1315			sender.clone(),1316			recipient.clone(),1317			value,1318		));13191320		Ok(())1321	}13221323	pub fn approve_internal(1324		sender: &T::CrossAccountId,1325		spender: &T::CrossAccountId,1326		collection: &CollectionHandle<T>,1327		item_id: TokenId,1328		amount: u128,1329	) -> DispatchResult {1330		Self::token_exists(collection, item_id)?;13311332		// Transfer permissions check1333		let bypasses_limits = collection.limits.owner_can_transfer1334			&& Self::is_owner_or_admin_permissions(collection, sender)?;13351336		let allowance_limit = if bypasses_limits {1337			None1338		} else if let Some(amount) = Self::owned_amount(sender, collection, item_id)? {1339			Some(amount)1340		} else {1341			fail!(Error::<T>::NoPermission);1342		};13431344		if collection.access == AccessMode::WhiteList {1345			Self::check_white_list(collection, sender)?;1346			Self::check_white_list(collection, spender)?;1347		}13481349		collection.consume_sload()?;1350		let allowance: u128 = amount1351			.checked_add(<Allowances<T>>::get(1352				collection.id,1353				(item_id, sender.as_sub(), spender.as_sub()),1354			))1355			.ok_or(Error::<T>::NumOverflow)?;1356		if let Some(limit) = allowance_limit {1357			ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1358		}1359		collection.consume_sstore()?;1360		<Allowances<T>>::insert(1361			collection.id,1362			(item_id, sender.as_sub(), spender.as_sub()),1363			allowance,1364		);13651366		if matches!(collection.mode, CollectionMode::NFT) {1367			// TODO: NFT: only one owner may exist for token in ERC7211368			collection.log(ERC721Events::Approval {1369				owner: *sender.as_eth(),1370				approved: *spender.as_eth(),1371				token_id: item_id.into(),1372			})?;1373		}13741375		if matches!(collection.mode, CollectionMode::Fungible(_)) {1376			// TODO: NFT: only one owner may exist for token in ERC201377			collection.log(ERC20Events::Approval {1378				owner: *sender.as_eth(),1379				spender: *spender.as_eth(),1380				value: allowance.into(),1381			})?;1382		}13831384		Self::deposit_event(RawEvent::Approved(1385			collection.id,1386			item_id,1387			sender.clone(),1388			spender.clone(),1389			allowance,1390		));1391		Ok(())1392	}13931394	pub fn transfer_from_internal(1395		sender: &T::CrossAccountId,1396		from: &T::CrossAccountId,1397		recipient: &T::CrossAccountId,1398		collection: &CollectionHandle<T>,1399		item_id: TokenId,1400		amount: u128,1401	) -> DispatchResult {1402		if sender == from {1403			// Transfer by `from`, because it is either equal to sender, or derived from him1404			return Self::transfer_internal(from, recipient, collection, item_id, amount);1405		}14061407		// Check approval1408		collection.consume_sload()?;1409		let approval: u128 =1410			<Allowances<T>>::get(collection.id, (item_id, from.as_sub(), sender.as_sub()));14111412		// Limits check1413		Self::is_correct_transfer(collection, recipient)?;14141415		// Transfer permissions check1416		ensure!(1417			approval >= amount1418				|| (collection.limits.owner_can_transfer1419					&& Self::is_owner_or_admin_permissions(collection, sender)?),1420			Error::<T>::NoPermission1421		);14221423		if collection.access == AccessMode::WhiteList {1424			Self::check_white_list(collection, sender)?;1425			Self::check_white_list(collection, recipient)?;1426		}14271428		// Reduce approval by transferred amount or remove if remaining approval drops to 01429		let allowance = approval.saturating_sub(amount);1430		collection.consume_sstore()?;1431		if allowance > 0 {1432			<Allowances<T>>::insert(1433				collection.id,1434				(item_id, from.as_sub(), sender.as_sub()),1435				allowance,1436			);1437		} else {1438			<Allowances<T>>::remove(collection.id, (item_id, from.as_sub(), sender.as_sub()));1439		}14401441		match collection.mode {1442			CollectionMode::NFT => {1443				Self::transfer_nft(collection, item_id, from.clone(), recipient.clone())?1444			}1445			CollectionMode::Fungible(_) => {1446				Self::transfer_fungible(collection, amount, from, recipient)?1447			}1448			CollectionMode::ReFungible => Self::transfer_refungible(1449				collection,1450				item_id,1451				amount,1452				from.clone(),1453				recipient.clone(),1454			)?,1455			_ => (),1456		};14571458		if matches!(collection.mode, CollectionMode::Fungible(_)) {1459			collection.log(ERC20Events::Approval {1460				owner: *from.as_eth(),1461				spender: *sender.as_eth(),1462				value: allowance.into(),1463			})?;1464		}14651466		Ok(())1467	}14681469	pub fn set_variable_meta_data_internal(1470		sender: &T::CrossAccountId,1471		collection: &CollectionHandle<T>,1472		item_id: TokenId,1473		data: Vec<u8>,1474	) -> DispatchResult {1475		Self::token_exists(collection, item_id)?;14761477		ensure!(1478			CUSTOM_DATA_LIMIT >= data.len() as u32,1479			Error::<T>::TokenVariableDataLimitExceeded1480		);14811482		// Modify permissions check1483		ensure!(1484			Self::is_item_owner(sender, collection, item_id)?1485				|| Self::is_owner_or_admin_permissions(collection, sender)?,1486			Error::<T>::NoPermission1487		);14881489		match collection.mode {1490			CollectionMode::NFT => Self::set_nft_variable_data(collection, item_id, data)?,1491			CollectionMode::ReFungible => {1492				Self::set_re_fungible_variable_data(collection, item_id, data)?1493			}1494			CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1495			_ => fail!(Error::<T>::UnexpectedCollectionType),1496		};14971498		Ok(())1499	}15001501	pub fn create_multiple_items_internal(1502		sender: &T::CrossAccountId,1503		collection: &CollectionHandle<T>,1504		owner: &T::CrossAccountId,1505		items_data: Vec<CreateItemData>,1506	) -> DispatchResult {1507		Self::can_create_items_in_collection(collection, sender, owner, items_data.len() as u32)?;15081509		for data in &items_data {1510			Self::validate_create_item_args(collection, data)?;1511		}1512		for data in &items_data {1513			Self::create_item_no_validation(collection, owner, data.clone())?;1514		}15151516		Ok(())1517	}15181519	pub fn burn_item_internal(1520		sender: &T::CrossAccountId,1521		collection: &CollectionHandle<T>,1522		item_id: TokenId,1523		value: u128,1524	) -> DispatchResult {1525		ensure!(1526			Self::is_item_owner(sender, collection, item_id)?1527				|| (collection.limits.owner_can_transfer1528					&& Self::is_owner_or_admin_permissions(collection, sender)?),1529			Error::<T>::NoPermission1530		);15311532		if collection.access == AccessMode::WhiteList {1533			Self::check_white_list(collection, sender)?;1534		}15351536		match collection.mode {1537			CollectionMode::NFT => Self::burn_nft_item(collection, item_id)?,1538			CollectionMode::Fungible(_) => Self::burn_fungible_item(sender, collection, value)?,1539			CollectionMode::ReFungible => Self::burn_refungible_item(collection, item_id, sender)?,1540			_ => (),1541		};15421543		Ok(())1544	}15451546	pub fn toggle_white_list_internal(1547		sender: &T::CrossAccountId,1548		collection: &CollectionHandle<T>,1549		address: &T::CrossAccountId,1550		whitelisted: bool,1551	) -> DispatchResult {1552		Self::check_owner_or_admin_permissions(collection, sender)?;15531554		if whitelisted {1555			<WhiteList<T>>::insert(collection.id, address.as_sub(), true);1556		} else {1557			<WhiteList<T>>::remove(collection.id, address.as_sub());1558		}15591560		Ok(())1561	}15621563	fn is_correct_transfer(1564		collection: &CollectionHandle<T>,1565		recipient: &T::CrossAccountId,1566	) -> DispatchResult {1567		let collection_id = collection.id;15681569		// check token limit and account token limit1570		collection.consume_sload()?;1571		let account_items: u32 =1572			<AddressTokens<T>>::get(collection_id, recipient.as_sub()).len() as u32;1573		ensure!(1574			collection.limits.account_token_ownership_limit > account_items,1575			Error::<T>::AccountTokenLimitExceeded1576		);15771578		// preliminary transfer check1579		ensure!(collection.transfers_enabled, Error::<T>::TransferNotAllowed);15801581		Ok(())1582	}15831584	fn can_create_items_in_collection(1585		collection: &CollectionHandle<T>,1586		sender: &T::CrossAccountId,1587		owner: &T::CrossAccountId,1588		amount: u32,1589	) -> DispatchResult {1590		let collection_id = collection.id;15911592		// check token limit and account token limit1593		let total_items: u32 = ItemListIndex::get(collection_id)1594			.checked_add(amount)1595			.ok_or(Error::<T>::CollectionTokenLimitExceeded)?;1596		let account_items: u32 = (<AddressTokens<T>>::get(collection_id, owner.as_sub()).len()1597			as u32)1598			.checked_add(amount)1599			.ok_or(Error::<T>::AccountTokenLimitExceeded)?;1600		ensure!(1601			collection.limits.token_limit >= total_items,1602			Error::<T>::CollectionTokenLimitExceeded1603		);1604		ensure!(1605			collection.limits.account_token_ownership_limit >= account_items,1606			Error::<T>::AccountTokenLimitExceeded1607		);16081609		if !Self::is_owner_or_admin_permissions(collection, sender)? {1610			ensure!(collection.mint_mode, Error::<T>::PublicMintingNotAllowed);1611			Self::check_white_list(collection, owner)?;1612			Self::check_white_list(collection, sender)?;1613		}16141615		Ok(())1616	}16171618	fn validate_create_item_args(1619		target_collection: &CollectionHandle<T>,1620		data: &CreateItemData,1621	) -> DispatchResult {1622		match target_collection.mode {1623			CollectionMode::NFT => {1624				if !matches!(data, CreateItemData::NFT(_)) {1625					fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1626				}1627			}1628			CollectionMode::Fungible(_) => {1629				if !matches!(data, CreateItemData::Fungible(_)) {1630					fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1631				}1632			}1633			CollectionMode::ReFungible => {1634				if let CreateItemData::ReFungible(data) = data {1635					// Check refungibility limits1636					ensure!(1637						data.pieces <= MAX_REFUNGIBLE_PIECES,1638						Error::<T>::WrongRefungiblePieces1639					);1640					ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1641				} else {1642					fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1643				}1644			}1645			_ => {1646				fail!(Error::<T>::UnexpectedCollectionType);1647			}1648		};16491650		Ok(())1651	}16521653	fn create_item_no_validation(1654		collection: &CollectionHandle<T>,1655		owner: &T::CrossAccountId,1656		data: CreateItemData,1657	) -> DispatchResult {1658		match data {1659			CreateItemData::NFT(data) => {1660				let item = NftItemType {1661					owner: owner.clone(),1662					const_data: data.const_data.into_inner(),1663					variable_data: data.variable_data.into_inner(),1664				};16651666				Self::add_nft_item(collection, item)?;1667			}1668			CreateItemData::Fungible(data) => {1669				Self::add_fungible_item(collection, owner, data.value)?;1670			}1671			CreateItemData::ReFungible(data) => {1672				let owner_list = vec![Ownership {1673					owner: owner.clone(),1674					fraction: data.pieces,1675				}];16761677				let item = ReFungibleItemType {1678					owner: owner_list,1679					const_data: data.const_data.into_inner(),1680					variable_data: data.variable_data.into_inner(),1681				};16821683				Self::add_refungible_item(collection, item)?;1684			}1685		};16861687		Ok(())1688	}16891690	fn add_fungible_item(1691		collection: &CollectionHandle<T>,1692		owner: &T::CrossAccountId,1693		value: u128,1694	) -> DispatchResult {1695		let collection_id = collection.id;16961697		// Does new owner already have an account?1698		collection.consume_sload()?;1699		let balance: u128 = <FungibleItemList<T>>::get(collection_id, owner.as_sub()).value;17001701		// Mint1702		let item = FungibleItemType {1703			value: balance.checked_add(value).ok_or(Error::<T>::NumOverflow)?,1704		};1705		collection.consume_sstore()?;1706		<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), item);17071708		// Update balance1709		collection.consume_sload()?;1710		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1711			.checked_add(value)1712			.ok_or(Error::<T>::NumOverflow)?;1713		collection.consume_sstore()?;1714		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);17151716		collection.log(ERC20Events::Transfer {1717			from: H160::default(),1718			to: *owner.as_eth(),1719			value: value.into(),1720		})?;1721		Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));1722		Ok(())1723	}17241725	fn add_refungible_item(1726		collection: &CollectionHandle<T>,1727		item: ReFungibleItemType<T::CrossAccountId>,1728	) -> DispatchResult {1729		let collection_id = collection.id;17301731		let current_index = <ItemListIndex>::get(collection_id)1732			.checked_add(1)1733			.ok_or(Error::<T>::NumOverflow)?;1734		let itemcopy = item.clone();17351736		ensure!(item.owner.len() == 1, Error::<T>::BadCreateRefungibleCall,);1737		let item_owner = item.owner.first().expect("only one owner is defined");17381739		let value = item_owner.fraction;1740		let owner = item_owner.owner.clone();17411742		Self::add_token_index(collection, current_index, &owner)?;17431744		<ItemListIndex>::insert(collection_id, current_index);1745		<ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);17461747		// Update balance1748		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1749			.checked_add(value)1750			.ok_or(Error::<T>::NumOverflow)?;1751		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);17521753		Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));1754		Ok(())1755	}17561757	fn add_nft_item(1758		collection: &CollectionHandle<T>,1759		item: NftItemType<T::CrossAccountId>,1760	) -> DispatchResult {1761		let collection_id = collection.id;17621763		let current_index = <ItemListIndex>::get(collection_id)1764			.checked_add(1)1765			.ok_or(Error::<T>::NumOverflow)?;17661767		let item_owner = item.owner.clone();1768		Self::add_token_index(collection, current_index, &item.owner)?;17691770		<ItemListIndex>::insert(collection_id, current_index);1771		<NftItemList<T>>::insert(collection_id, current_index, item);17721773		// Update balance1774		let new_balance = <Balance<T>>::get(collection_id, item_owner.as_sub())1775			.checked_add(1)1776			.ok_or(Error::<T>::NumOverflow)?;1777		<Balance<T>>::insert(collection_id, item_owner.as_sub(), new_balance);17781779		collection.log(ERC721Events::Transfer {1780			from: H160::default(),1781			to: *item_owner.as_eth(),1782			token_id: current_index.into(),1783		})?;1784		Self::deposit_event(RawEvent::ItemCreated(1785			collection_id,1786			current_index,1787			item_owner,1788		));1789		Ok(())1790	}17911792	fn burn_refungible_item(1793		collection: &CollectionHandle<T>,1794		item_id: TokenId,1795		owner: &T::CrossAccountId,1796	) -> DispatchResult {1797		let collection_id = collection.id;17981799		let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id)1800			.ok_or(Error::<T>::TokenNotFound)?;1801		let rft_balance = token1802			.owner1803			.iter()1804			.find(|&i| i.owner == *owner)1805			.ok_or(Error::<T>::TokenNotFound)?;1806		Self::remove_token_index(collection, item_id, owner)?;18071808		// update balance1809		let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.as_sub())1810			.checked_sub(rft_balance.fraction)1811			.ok_or(Error::<T>::NumOverflow)?;1812		<Balance<T>>::insert(collection_id, rft_balance.owner.as_sub(), new_balance);18131814		// Re-create owners list with sender removed1815		let index = token1816			.owner1817			.iter()1818			.position(|i| i.owner == *owner)1819			.expect("owned item is exists");1820		token.owner.remove(index);1821		let owner_count = token.owner.len();18221823		// Burn the token completely if this was the last (only) owner1824		if owner_count == 0 {1825			<ReFungibleItemList<T>>::remove(collection_id, item_id);1826			<VariableMetaDataBasket<T>>::remove(collection_id, item_id);1827		} else {1828			<ReFungibleItemList<T>>::insert(collection_id, item_id, token);1829		}18301831		Ok(())1832	}18331834	fn burn_nft_item(collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1835		let collection_id = collection.id;18361837		let item =1838			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;1839		Self::remove_token_index(collection, item_id, &item.owner)?;18401841		// update balance1842		let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())1843			.checked_sub(1)1844			.ok_or(Error::<T>::NumOverflow)?;1845		<Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);1846		<NftItemList<T>>::remove(collection_id, item_id);1847		<VariableMetaDataBasket<T>>::remove(collection_id, item_id);18481849		collection.log(ERC721Events::Transfer {1850			from: *item.owner.as_eth(),1851			to: H160::default(),1852			token_id: item_id.into(),1853		})?;1854		Self::deposit_event(RawEvent::ItemDestroyed(collection.id, item_id));1855		Ok(())1856	}18571858	fn burn_fungible_item(1859		owner: &T::CrossAccountId,1860		collection: &CollectionHandle<T>,1861		value: u128,1862	) -> DispatchResult {1863		let collection_id = collection.id;18641865		let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());1866		ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);18671868		// update balance1869		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1870			.checked_sub(value)1871			.ok_or(Error::<T>::NumOverflow)?;1872		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);18731874		if balance.value - value > 0 {1875			balance.value -= value;1876			<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);1877		} else {1878			<FungibleItemList<T>>::remove(collection_id, owner.as_sub());1879		}18801881		collection.log(ERC20Events::Transfer {1882			from: *owner.as_eth(),1883			to: H160::default(),1884			value: value.into(),1885		})?;1886		Ok(())1887	}18881889	pub fn get_collection(1890		collection_id: CollectionId,1891	) -> Result<CollectionHandle<T>, sp_runtime::DispatchError> {1892		Ok(<CollectionHandle<T>>::get(collection_id).ok_or(Error::<T>::CollectionNotFound)?)1893	}18941895	fn check_owner_permissions(1896		target_collection: &CollectionHandle<T>,1897		subject: &T::AccountId,1898	) -> DispatchResult {1899		ensure!(1900			*subject == target_collection.owner,1901			Error::<T>::NoPermission1902		);19031904		Ok(())1905	}19061907	fn is_owner_or_admin_permissions(1908		collection: &CollectionHandle<T>,1909		subject: &T::CrossAccountId,1910	) -> Result<bool, DispatchError> {1911		collection.consume_sload()?;1912		Ok(*subject.as_sub() == collection.owner1913			|| <AdminList<T>>::get(collection.id).contains(subject))1914	}19151916	fn check_owner_or_admin_permissions(1917		collection: &CollectionHandle<T>,1918		subject: &T::CrossAccountId,1919	) -> DispatchResult {1920		ensure!(1921			Self::is_owner_or_admin_permissions(collection, subject)?,1922			Error::<T>::NoPermission1923		);19241925		Ok(())1926	}19271928	fn owned_amount(1929		subject: &T::CrossAccountId,1930		collection: &CollectionHandle<T>,1931		item_id: TokenId,1932	) -> Result<Option<u128>, DispatchError> {1933		collection.consume_sload()?;1934		Ok(Self::owned_amount_unchecked(subject, collection, item_id))1935	}19361937	fn owned_amount_unchecked(1938		subject: &T::CrossAccountId,1939		target_collection: &CollectionHandle<T>,1940		item_id: TokenId,1941	) -> Option<u128> {1942		let collection_id = target_collection.id;19431944		match target_collection.mode {1945			CollectionMode::NFT => {1946				(<NftItemList<T>>::get(collection_id, item_id)?.owner == *subject).then(|| 1)1947			}1948			CollectionMode::Fungible(_) => {1949				Some(<FungibleItemList<T>>::get(collection_id, &subject.as_sub()).value)1950			}1951			CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)?1952				.owner1953				.iter()1954				.find(|i| i.owner == *subject)1955				.map(|i| i.fraction),1956			CollectionMode::Invalid => None,1957		}1958	}19591960	fn is_item_owner(1961		subject: &T::CrossAccountId,1962		target_collection: &CollectionHandle<T>,1963		item_id: TokenId,1964	) -> Result<bool, DispatchError> {1965		Ok(match target_collection.mode {1966			CollectionMode::Fungible(_) => true,1967			_ => Self::owned_amount(subject, target_collection, item_id)?.is_some(),1968		})1969	}19701971	fn check_white_list(1972		collection: &CollectionHandle<T>,1973		address: &T::CrossAccountId,1974	) -> DispatchResult {1975		collection.consume_sload()?;1976		ensure!(1977			<WhiteList<T>>::contains_key(collection.id, address.as_sub()),1978			Error::<T>::AddresNotInWhiteList,1979		);1980		Ok(())1981	}19821983	/// Check if token exists. In case of Fungible, check if there is an entry for1984	/// the owner in fungible balances double map1985	fn token_exists(target_collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1986		let collection_id = target_collection.id;1987		let exists = match target_collection.mode {1988			CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),1989			CollectionMode::Fungible(_) => true,1990			CollectionMode::ReFungible => {1991				<ReFungibleItemList<T>>::contains_key(collection_id, item_id)1992			}1993			_ => false,1994		};19951996		ensure!(exists, Error::<T>::TokenNotFound);1997		Ok(())1998	}19992000	fn transfer_fungible(2001		collection: &CollectionHandle<T>,2002		value: u128,2003		owner: &T::CrossAccountId,2004		recipient: &T::CrossAccountId,2005	) -> DispatchResult {2006		let collection_id = collection.id;20072008		collection.consume_sload()?;2009		collection.consume_sload()?;2010		let mut recipient_balance = <FungibleItemList<T>>::get(collection_id, recipient.as_sub());2011		let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());20122013		recipient_balance.value = recipient_balance2014			.value2015			.checked_add(value)2016			.ok_or(Error::<T>::NumOverflow)?;2017		balance.value = balance2018			.value2019			.checked_sub(value)2020			.ok_or(Error::<T>::TokenValueTooLow)?;20212022		// update balanceOf2023		collection.consume_sstore()?;2024		collection.consume_sstore()?;2025		if balance.value != 0 {2026			<Balance<T>>::insert(collection_id, owner.as_sub(), balance.value);2027		} else {2028			<Balance<T>>::remove(collection_id, owner.as_sub());2029		}2030		<Balance<T>>::insert(collection_id, recipient.as_sub(), recipient_balance.value);20312032		// Reduce or remove sender2033		collection.consume_sstore()?;2034		collection.consume_sstore()?;2035		if balance.value != 0 {2036			<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);2037		} else {2038			<FungibleItemList<T>>::remove(collection_id, owner.as_sub());2039		}2040		<FungibleItemList<T>>::insert(collection_id, recipient.as_sub(), recipient_balance);20412042		collection.log(ERC20Events::Transfer {2043			from: *owner.as_eth(),2044			to: *recipient.as_eth(),2045			value: value.into(),2046		})?;2047		Self::deposit_event(RawEvent::Transfer(2048			collection.id,2049			1,2050			owner.clone(),2051			recipient.clone(),2052			value,2053		));20542055		Ok(())2056	}20572058	fn transfer_refungible(2059		collection: &CollectionHandle<T>,2060		item_id: TokenId,2061		value: u128,2062		owner: T::CrossAccountId,2063		new_owner: T::CrossAccountId,2064	) -> DispatchResult {2065		let collection_id = collection.id;2066		collection.consume_sload()?;2067		let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id)2068			.ok_or(Error::<T>::TokenNotFound)?;20692070		let item = full_item2071			.owner2072			.iter()2073			.find(|i| i.owner == owner)2074			.ok_or(Error::<T>::TokenNotFound)?;2075		let amount = item.fraction;20762077		ensure!(amount >= value, Error::<T>::TokenValueTooLow);20782079		collection.consume_sload()?;2080		// update balance2081		let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2082			.checked_sub(value)2083			.ok_or(Error::<T>::NumOverflow)?;2084		collection.consume_sstore()?;2085		<Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);20862087		collection.consume_sload()?;2088		let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2089			.checked_add(value)2090			.ok_or(Error::<T>::NumOverflow)?;2091		collection.consume_sstore()?;2092		<Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);20932094		let old_owner = item.owner.clone();2095		let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);20962097		let mut new_full_item = full_item.clone();2098		// transfer2099		if amount == value && !new_owner_has_account {2100			// change owner2101			// new owner do not have account2102			new_full_item2103				.owner2104				.iter_mut()2105				.find(|i| i.owner == owner)2106				.expect("old owner does present in refungible")2107				.owner = new_owner.clone();2108			collection.consume_sstore()?;2109			<ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);21102111			// update index collection2112			Self::move_token_index(collection, item_id, &old_owner, &new_owner)?;2113		} else {2114			new_full_item2115				.owner2116				.iter_mut()2117				.find(|i| i.owner == owner)2118				.expect("old owner does present in refungible")2119				.fraction -= value;21202121			// separate amount2122			if new_owner_has_account {2123				// new owner has account2124				new_full_item2125					.owner2126					.iter_mut()2127					.find(|i| i.owner == new_owner)2128					.expect("new owner has account")2129					.fraction += value;2130			} else {2131				// new owner do not have account2132				new_full_item.owner.push(Ownership {2133					owner: new_owner.clone(),2134					fraction: value,2135				});2136				Self::add_token_index(collection, item_id, &new_owner)?;2137			}21382139			collection.consume_sstore()?;2140			<ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2141		}21422143		Self::deposit_event(RawEvent::Transfer(2144			collection.id,2145			item_id,2146			owner,2147			new_owner,2148			amount,2149		));21502151		Ok(())2152	}21532154	fn transfer_nft(2155		collection: &CollectionHandle<T>,2156		item_id: TokenId,2157		sender: T::CrossAccountId,2158		new_owner: T::CrossAccountId,2159	) -> DispatchResult {2160		let collection_id = collection.id;2161		collection.consume_sload()?;2162		let mut item =2163			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;21642165		ensure!(sender == item.owner, Error::<T>::MustBeTokenOwner);21662167		collection.consume_sload()?;2168		// update balance2169		let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2170			.checked_sub(1)2171			.ok_or(Error::<T>::NumOverflow)?;2172		collection.consume_sstore()?;2173		<Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);21742175		collection.consume_sload()?;2176		let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2177			.checked_add(1)2178			.ok_or(Error::<T>::NumOverflow)?;2179		collection.consume_sstore()?;2180		<Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);21812182		// change owner2183		let old_owner = item.owner.clone();2184		item.owner = new_owner.clone();2185		collection.consume_sstore()?;2186		<NftItemList<T>>::insert(collection_id, item_id, item);21872188		// update index collection2189		Self::move_token_index(collection, item_id, &old_owner, &new_owner)?;21902191		collection.log(ERC721Events::Transfer {2192			from: *sender.as_eth(),2193			to: *new_owner.as_eth(),2194			token_id: item_id.into(),2195		})?;2196		Self::deposit_event(RawEvent::Transfer(2197			collection.id,2198			item_id,2199			sender,2200			new_owner,2201			1,2202		));22032204		Ok(())2205	}22062207	fn set_re_fungible_variable_data(2208		collection: &CollectionHandle<T>,2209		item_id: TokenId,2210		data: Vec<u8>,2211	) -> DispatchResult {2212		let collection_id = collection.id;2213		let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id)2214			.ok_or(Error::<T>::TokenNotFound)?;22152216		item.variable_data = data;22172218		<ReFungibleItemList<T>>::insert(collection_id, item_id, item);22192220		Ok(())2221	}22222223	fn set_nft_variable_data(2224		collection: &CollectionHandle<T>,2225		item_id: TokenId,2226		data: Vec<u8>,2227	) -> DispatchResult {2228		let collection_id = collection.id;2229		let mut item =2230			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;22312232		item.variable_data = data;22332234		<NftItemList<T>>::insert(collection_id, item_id, item);22352236		Ok(())2237	}22382239	#[allow(dead_code)]2240	fn init_collection(item: &Collection<T>) {2241		// check params2242		assert!(2243			item.decimal_points <= MAX_DECIMAL_POINTS,2244			"decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2245		);2246		assert!(2247			item.name.len() <= 64,2248			"Collection name can not be longer than 63 char"2249		);2250		assert!(2251			item.name.len() <= 256,2252			"Collection description can not be longer than 255 char"2253		);2254		assert!(2255			item.token_prefix.len() <= 16,2256			"Token prefix can not be longer than 15 char"2257		);22582259		// Generate next collection ID2260		let next_id = CreatedCollectionCount::get().checked_add(1).unwrap();22612262		CreatedCollectionCount::put(next_id);2263	}22642265	#[allow(dead_code)]2266	fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::CrossAccountId>) {2267		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();22682269		Self::add_token_index(2270			&CollectionHandle::get(collection_id).unwrap(),2271			current_index,2272			&item.owner,2273		)2274		.unwrap();22752276		<ItemListIndex>::insert(collection_id, current_index);22772278		// Update balance2279		let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2280			.checked_add(1)2281			.unwrap();2282		<Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2283	}22842285	#[allow(dead_code)]2286	fn init_fungible_token(2287		collection_id: CollectionId,2288		owner: &T::CrossAccountId,2289		item: &FungibleItemType,2290	) {2291		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();22922293		Self::add_token_index(2294			&CollectionHandle::get(collection_id).unwrap(),2295			current_index,2296			owner,2297		)2298		.unwrap();22992300		<ItemListIndex>::insert(collection_id, current_index);23012302		// Update balance2303		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2304			.checked_add(item.value)2305			.unwrap();2306		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2307	}23082309	#[allow(dead_code)]2310	fn init_refungible_token(2311		collection_id: CollectionId,2312		item: &ReFungibleItemType<T::CrossAccountId>,2313	) {2314		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();23152316		let value = item.owner.first().unwrap().fraction;2317		let owner = item.owner.first().unwrap().owner.clone();23182319		Self::add_token_index(2320			&CollectionHandle::get(collection_id).unwrap(),2321			current_index,2322			&owner,2323		)2324		.unwrap();23252326		<ItemListIndex>::insert(collection_id, current_index);23272328		// Update balance2329		let new_balance = <Balance<T>>::get(collection_id, &owner.as_sub())2330			.checked_add(value)2331			.unwrap();2332		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2333	}23342335	fn add_token_index(2336		collection: &CollectionHandle<T>,2337		item_index: TokenId,2338		owner: &T::CrossAccountId,2339	) -> DispatchResult {2340		// add to account limit2341		collection.consume_sload()?;2342		if <AccountItemCount<T>>::contains_key(owner.as_sub()) {2343			// bound Owned tokens by a single address2344			collection.consume_sload()?;2345			let count = <AccountItemCount<T>>::get(owner.as_sub());2346			ensure!(2347				count < ACCOUNT_TOKEN_OWNERSHIP_LIMIT,2348				Error::<T>::AddressOwnershipLimitExceeded2349			);23502351			collection.consume_sstore()?;2352			<AccountItemCount<T>>::insert(2353				owner.as_sub(),2354				count.checked_add(1).ok_or(Error::<T>::NumOverflow)?,2355			);2356		} else {2357			collection.consume_sstore()?;2358			<AccountItemCount<T>>::insert(owner.as_sub(), 1);2359		}23602361		collection.consume_sload()?;2362		let list_exists = <AddressTokens<T>>::contains_key(collection.id, owner.as_sub());2363		if list_exists {2364			collection.consume_sload()?;2365			let mut list = <AddressTokens<T>>::get(collection.id, owner.as_sub());2366			let item_contains = list.contains(&item_index.clone());23672368			if !item_contains {2369				list.push(item_index);2370			}23712372			collection.consume_sstore()?;2373			<AddressTokens<T>>::insert(collection.id, owner.as_sub(), list);2374		} else {2375			let itm = vec![item_index];2376			collection.consume_sstore()?;2377			<AddressTokens<T>>::insert(collection.id, owner.as_sub(), itm);2378		}23792380		Ok(())2381	}23822383	fn remove_token_index(2384		collection: &CollectionHandle<T>,2385		item_index: TokenId,2386		owner: &T::CrossAccountId,2387	) -> DispatchResult {2388		// update counter2389		collection.consume_sload()?;2390		collection.consume_sstore()?;2391		<AccountItemCount<T>>::insert(2392			owner.as_sub(),2393			<AccountItemCount<T>>::get(owner.as_sub())2394				.checked_sub(1)2395				.ok_or(Error::<T>::NumOverflow)?,2396		);23972398		collection.consume_sload()?;2399		let list_exists = <AddressTokens<T>>::contains_key(collection.id, owner.as_sub());2400		if list_exists {2401			collection.consume_sload()?;2402			let mut list = <AddressTokens<T>>::get(collection.id, owner.as_sub());2403			let item_contains = list.contains(&item_index.clone());24042405			if item_contains {2406				list.retain(|&item| item != item_index);2407				collection.consume_sstore()?;2408				<AddressTokens<T>>::insert(collection.id, owner.as_sub(), list);2409			}2410		}24112412		Ok(())2413	}24142415	fn move_token_index(2416		collection: &CollectionHandle<T>,2417		item_index: TokenId,2418		old_owner: &T::CrossAccountId,2419		new_owner: &T::CrossAccountId,2420	) -> DispatchResult {2421		Self::remove_token_index(collection, item_index, old_owner)?;2422		Self::add_token_index(collection, item_index, new_owner)?;24232424		Ok(())2425	}2426}24272428sp_api::decl_runtime_apis! {2429	pub trait NftApi {2430		/// Used for ethereum integration2431		fn eth_contract_code(account: H160) -> Option<Vec<u8>>;2432	}2433}