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

difftreelog

source

pallets/nft/src/lib.rs69.7 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::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	fn consume_gas(&self, gas: u64) -> DispatchResult {209		self.recorder.consume_gas_sub(gas)210	}211	pub fn submit_logs(self) -> DispatchResult {212		self.recorder.submit_logs()213	}214	pub fn save(self) -> DispatchResult {215		self.recorder.submit_logs()?;216		<CollectionById<T>>::insert(self.id, self.collection);217		Ok(())218	}219}220impl<T: Config> Deref for CollectionHandle<T> {221	type Target = Collection<T>;222223	fn deref(&self) -> &Self::Target {224		&self.collection225	}226}227228impl<T: Config> DerefMut for CollectionHandle<T> {229	fn deref_mut(&mut self) -> &mut Self::Target {230		&mut self.collection231	}232}233234pub trait Config: system::Config + pallet_evm_coder_substrate::Config + Sized {235	type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;236237	/// Weight information for extrinsics in this pallet.238	type WeightInfo: WeightInfo;239240	type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;241	type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;242243	type CrossAccountId: CrossAccountId<Self::AccountId>;244	type Currency: Currency<Self::AccountId>;245	type CollectionCreationPrice: Get<246		<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,247	>;248	type TreasuryAccountId: Get<Self::AccountId>;249}250251// # Used definitions252//253// ## User control levels254//255// chain-controlled - key is uncontrolled by user256//                    i.e autoincrementing index257//                    can use non-cryptographic hash258// real - key is controlled by user259//        but it is hard to generate enough colliding values, i.e owner of signed txs260//        can use non-cryptographic hash261// controlled - key is completly controlled by users262//              i.e maps with mutable keys263//              should use cryptographic hash264//265// ## User control level downgrade reasons266//267// ?1 - chain-controlled -> controlled268//      collections/tokens can be destroyed, resulting in massive holes269// ?2 - chain-controlled -> controlled270//      same as ?1, but can be only added, resulting in easier exploitation271// ?3 - real -> controlled272//      no confirmation required, so addresses can be easily generated273decl_storage! {274	trait Store for Module<T: Config> as Nft {275276		//#region Private members277		/// Id of next collection278		CreatedCollectionCount: u32;279		/// Used for migrations280		ChainVersion: u64;281		/// Id of last collection token282		/// Collection id (controlled?1)283		ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;284		//#endregion285286		//#region Bound counters287		/// Amount of collections destroyed, used for total amount tracking with288		/// CreatedCollectionCount289		DestroyedCollectionCount: u32;290		/// Total amount of account owned tokens (NFTs + RFTs + unique fungibles)291		/// Account id (real)292		pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;293		//#endregion294295		//#region Basic collections296		/// Collection info297		/// Collection id (controlled?1)298		pub CollectionById get(fn collection_id) config(): map hasher(blake2_128_concat) CollectionId => Option<Collection<T>> = None;299		/// List of collection admins300		/// Collection id (controlled?2)301		pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec<T::CrossAccountId>;302		/// Whitelisted collection users303		/// Collection id (controlled?2), user id (controlled?3)304		pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool;305		//#endregion306307		/// How many of collection items user have308		/// Collection id (controlled?2), account id (real)309		pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128;310311		/// Amount of items which spender can transfer out of owners account (via transferFrom)312		/// Collection id (controlled?2), (token id (controlled ?2) + owner account id (real) + spender account id (controlled?3))313		/// TODO: Off chain worker should remove from this map when token gets removed314		pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128;315316		//#region Item collections317		/// Collection id (controlled?2), token id (controlled?1)318		pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<NftItemType<T::CrossAccountId>>;319		/// Collection id (controlled?2), owner (controlled?2)320		pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType;321		/// Collection id (controlled?2), token id (controlled?1)322		pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<ReFungibleItemType<T::CrossAccountId>>;323		//#endregion324325		//#region Index list326		/// Collection id (controlled?2), tokens owner (controlled?2)327		pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;328		//#endregion329330		//#region Tokens transfer rate limit baskets331		/// (Collection id (controlled?2), who created (real))332		/// TODO: Off chain worker should remove from this map when collection gets removed333		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;334		/// Collection id (controlled?2), token id (controlled?2)335		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;336		/// Collection id (controlled?2), owning user (real)337		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;338		/// Collection id (controlled?2), token id (controlled?2)339		pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;340		//#endregion341342		/// Variable metadata sponsoring343		/// Collection id (controlled?2), token id (controlled?2)344		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;345	}346	add_extra_genesis {347		build(|config: &GenesisConfig<T>| {348			// Modification of storage349			for (_num, _c) in &config.collection_id {350				<Module<T>>::init_collection(_c);351			}352353			for (_num, _c, _i) in &config.nft_item_id {354				<Module<T>>::init_nft_token(*_c, _i);355			}356357			for (collection_id, account_id, fungible_item) in &config.fungible_item_id {358				<Module<T>>::init_fungible_token(*collection_id, &T::CrossAccountId::from_sub(account_id.clone()), fungible_item);359			}360361			for (_num, _c, _i) in &config.refungible_item_id {362				<Module<T>>::init_refungible_token(*_c, _i);363			}364		})365	}366}367368decl_event!(369	pub enum Event<T>370	where371		AccountId = <T as frame_system::Config>::AccountId,372		CrossAccountId = <T as Config>::CrossAccountId,373	{374		/// New collection was created375		///376		/// # Arguments377		///378		/// * collection_id: Globally unique identifier of newly created collection.379		///380		/// * mode: [CollectionMode] converted into u8.381		///382		/// * account_id: Collection owner.383		CollectionCreated(CollectionId, u8, AccountId),384385		/// New item was created.386		///387		/// # Arguments388		///389		/// * collection_id: Id of the collection where item was created.390		///391		/// * item_id: Id of an item. Unique within the collection.392		///393		/// * recipient: Owner of newly created item394		ItemCreated(CollectionId, TokenId, CrossAccountId),395396		/// Collection item was burned.397		///398		/// # Arguments399		///400		/// collection_id.401		///402		/// item_id: Identifier of burned NFT.403		ItemDestroyed(CollectionId, TokenId),404405		/// Item was transferred406		///407		/// * collection_id: Id of collection to which item is belong408		///409		/// * item_id: Id of an item410		///411		/// * sender: Original owner of item412		///413		/// * recipient: New owner of item414		///415		/// * amount: Always 1 for NFT416		Transfer(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),417418		/// * collection_id419		///420		/// * item_id421		///422		/// * sender423		///424		/// * spender425		///426		/// * amount427		Approved(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),428	}429);430431decl_module! {432	pub struct Module<T: Config> for enum Call433	where434		origin: T::Origin435	{436		fn deposit_event() = default;437		const CollectionAdminsLimit: u64 = COLLECTION_ADMINS_LIMIT;438		type Error = Error<T>;439440		fn on_initialize(_now: T::BlockNumber) -> Weight {441			0442		}443444		/// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.445		///446		/// # Permissions447		///448		/// * Anyone.449		///450		/// # Arguments451		///452		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.453		///454		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.455		///456		/// * token_prefix: UTF-8 string with token prefix.457		///458		/// * mode: [CollectionMode] collection type and type dependent data.459		// returns collection ID460		#[weight = <T as Config>::WeightInfo::create_collection()]461		#[transactional]462		pub fn create_collection(origin,463								 collection_name: Vec<u16>,464								 collection_description: Vec<u16>,465								 token_prefix: Vec<u8>,466								 mode: CollectionMode) -> DispatchResult {467468			// Anyone can create a collection469			let who = ensure_signed(origin)?;470471			// Take a (non-refundable) deposit of collection creation472			let mut imbalance = <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();473			imbalance.subsume(<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(474				&T::TreasuryAccountId::get(),475				T::CollectionCreationPrice::get(),476			));477			<T as Config>::Currency::settle(478				&who,479				imbalance,480				WithdrawReasons::TRANSFER,481				ExistenceRequirement::KeepAlive,482			).map_err(|_| Error::<T>::NoPermission)?;483484			let decimal_points = match mode {485				CollectionMode::Fungible(points) => points,486				_ => 0487			};488489			let created_count = CreatedCollectionCount::get();490			let destroyed_count = DestroyedCollectionCount::get();491492			// bound Total number of collections493			ensure!(created_count - destroyed_count < COLLECTION_NUMBER_LIMIT, Error::<T>::TotalCollectionsLimitExceeded);494495			// check params496			ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);497			ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);498			ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);499			ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);500501			// Generate next collection ID502			let next_id = created_count503				.checked_add(1)504				.ok_or(Error::<T>::NumOverflow)?;505506			CreatedCollectionCount::put(next_id);507508			let limits = CollectionLimits {509				sponsored_data_size: CUSTOM_DATA_LIMIT,510				..Default::default()511			};512513			// Create new collection514			let new_collection = Collection {515				owner: who.clone(),516				name: collection_name,517				mode: mode.clone(),518				mint_mode: false,519				access: AccessMode::Normal,520				description: collection_description,521				decimal_points,522				token_prefix,523				offchain_schema: Vec::new(),524				schema_version: SchemaVersion::ImageURL,525				sponsorship: SponsorshipState::Disabled,526				variable_on_chain_schema: Vec::new(),527				const_on_chain_schema: Vec::new(),528				limits,529				transfers_enabled: true,530			};531532			// Add new collection to map533			<CollectionById<T>>::insert(next_id, new_collection);534535			// call event536			Self::deposit_event(RawEvent::CollectionCreated(next_id, mode.id(), who));537538			Ok(())539		}540541		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.542		///543		/// # Permissions544		///545		/// * Collection Owner.546		///547		/// # Arguments548		///549		/// * collection_id: collection to destroy.550		#[weight = <T as Config>::WeightInfo::destroy_collection()]551		#[transactional]552		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {553554			let sender = ensure_signed(origin)?;555			let collection = Self::get_collection(collection_id)?;556			Self::check_owner_permissions(&collection, &sender)?;557			if !collection.limits.owner_can_destroy {558				fail!(Error::<T>::NoPermission);559			}560561			<AddressTokens<T>>::remove_prefix(collection_id, None);562			<Allowances<T>>::remove_prefix(collection_id, None);563			<Balance<T>>::remove_prefix(collection_id, None);564			<ItemListIndex>::remove(collection_id);565			<AdminList<T>>::remove(collection_id);566			<CollectionById<T>>::remove(collection_id);567			<WhiteList<T>>::remove_prefix(collection_id, None);568569			<NftItemList<T>>::remove_prefix(collection_id, None);570			<FungibleItemList<T>>::remove_prefix(collection_id, None);571			<ReFungibleItemList<T>>::remove_prefix(collection_id, None);572573			<NftTransferBasket<T>>::remove_prefix(collection_id, None);574			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);575			<ReFungibleTransferBasket<T>>::remove_prefix(collection_id, None);576577			<VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);578579			DestroyedCollectionCount::put(DestroyedCollectionCount::get()580				.checked_add(1)581				.ok_or(Error::<T>::NumOverflow)?);582583			Ok(())584		}585586		/// Add an address to white list.587		///588		/// # Permissions589		///590		/// * Collection Owner591		/// * Collection Admin592		///593		/// # Arguments594		///595		/// * collection_id.596		///597		/// * address.598		#[weight = <T as Config>::WeightInfo::add_to_white_list()]599		#[transactional]600		pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{601602			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);603			let collection = Self::get_collection(collection_id)?;604605			Self::toggle_white_list_internal(606				&sender,607				&collection,608				&address,609				true,610			)?;611612			Ok(())613		}614615		/// Remove an address from white list.616		///617		/// # Permissions618		///619		/// * Collection Owner620		/// * Collection Admin621		///622		/// # Arguments623		///624		/// * collection_id.625		///626		/// * address.627		#[weight = <T as Config>::WeightInfo::remove_from_white_list()]628		#[transactional]629		pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{630631			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);632			let collection = Self::get_collection(collection_id)?;633634			Self::toggle_white_list_internal(635				&sender,636				&collection,637				&address,638				false,639			)?;640641			Ok(())642		}643644		/// Toggle between normal and white list access for the methods with access for `Anyone`.645		///646		/// # Permissions647		///648		/// * Collection Owner.649		///650		/// # Arguments651		///652		/// * collection_id.653		///654		/// * mode: [AccessMode]655		#[weight = <T as Config>::WeightInfo::set_public_access_mode()]656		#[transactional]657		pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult658		{659			let sender = ensure_signed(origin)?;660661			let mut target_collection = Self::get_collection(collection_id)?;662			Self::check_owner_permissions(&target_collection, &sender)?;663			target_collection.access = mode;664			target_collection.save()665		}666667		/// Allows Anyone to create tokens if:668		/// * White List is enabled, and669		/// * Address is added to white list, and670		/// * This method was called with True parameter671		///672		/// # Permissions673		/// * Collection Owner674		///675		/// # Arguments676		///677		/// * collection_id.678		///679		/// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.680		#[weight = <T as Config>::WeightInfo::set_mint_permission()]681		#[transactional]682		pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult683		{684			let sender = ensure_signed(origin)?;685686			let mut target_collection = Self::get_collection(collection_id)?;687			Self::check_owner_permissions(&target_collection, &sender)?;688			target_collection.mint_mode = mint_permission;689			target_collection.save()690		}691692		/// Change the owner of the collection.693		///694		/// # Permissions695		///696		/// * Collection Owner.697		///698		/// # Arguments699		///700		/// * collection_id.701		///702		/// * new_owner.703		#[weight = <T as Config>::WeightInfo::change_collection_owner()]704		#[transactional]705		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {706707			let sender = ensure_signed(origin)?;708			let mut target_collection = Self::get_collection(collection_id)?;709			Self::check_owner_permissions(&target_collection, &sender)?;710			target_collection.owner = new_owner;711			target_collection.save()712		}713714		/// Adds an admin of the Collection.715		/// 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.716		///717		/// # Permissions718		///719		/// * Collection Owner.720		/// * Collection Admin.721		///722		/// # Arguments723		///724		/// * collection_id: ID of the Collection to add admin for.725		///726		/// * new_admin_id: Address of new admin to add.727		#[weight = <T as Config>::WeightInfo::add_collection_admin()]728		#[transactional]729		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {730			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);731			let collection = Self::get_collection(collection_id)?;732			Self::check_owner_or_admin_permissions(&collection, &sender)?;733			let mut admin_arr = <AdminList<T>>::get(collection_id);734735			match admin_arr.binary_search(&new_admin_id) {736				Ok(_) => {},737				Err(idx) => {738					ensure!(admin_arr.len() < COLLECTION_ADMINS_LIMIT as usize, Error::<T>::CollectionAdminsLimitExceeded);739					admin_arr.insert(idx, new_admin_id);740					<AdminList<T>>::insert(collection_id, admin_arr);741				}742			}743			Ok(())744		}745746		/// 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.747		///748		/// # Permissions749		///750		/// * Collection Owner.751		/// * Collection Admin.752		///753		/// # Arguments754		///755		/// * collection_id: ID of the Collection to remove admin for.756		///757		/// * account_id: Address of admin to remove.758		#[weight = <T as Config>::WeightInfo::remove_collection_admin()]759		#[transactional]760		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {761			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);762			let collection = Self::get_collection(collection_id)?;763			Self::check_owner_or_admin_permissions(&collection, &sender)?;764			let mut admin_arr = <AdminList<T>>::get(collection_id);765766			if let Ok(idx) = admin_arr.binary_search(&account_id) {767				admin_arr.remove(idx);768				<AdminList<T>>::insert(collection_id, admin_arr);769			}770			Ok(())771		}772773		/// # Permissions774		///775		/// * Collection Owner776		///777		/// # Arguments778		///779		/// * collection_id.780		///781		/// * new_sponsor.782		#[weight = <T as Config>::WeightInfo::set_collection_sponsor()]783		#[transactional]784		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {785			let sender = ensure_signed(origin)?;786			let mut target_collection = Self::get_collection(collection_id)?;787			Self::check_owner_permissions(&target_collection, &sender)?;788789			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);790			target_collection.save()791		}792793		/// # Permissions794		///795		/// * Sponsor.796		///797		/// # Arguments798		///799		/// * collection_id.800		#[weight = <T as Config>::WeightInfo::confirm_sponsorship()]801		#[transactional]802		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {803			let sender = ensure_signed(origin)?;804805			let mut target_collection = Self::get_collection(collection_id)?;806			ensure!(807				target_collection.sponsorship.pending_sponsor() == Some(&sender),808				Error::<T>::ConfirmUnsetSponsorFail809			);810811			target_collection.sponsorship = SponsorshipState::Confirmed(sender);812			target_collection.save()813		}814815		/// Switch back to pay-per-own-transaction model.816		///817		/// # Permissions818		///819		/// * Collection owner.820		///821		/// # Arguments822		///823		/// * collection_id.824		#[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]825		#[transactional]826		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {827			let sender = ensure_signed(origin)?;828829			let mut target_collection = Self::get_collection(collection_id)?;830			Self::check_owner_permissions(&target_collection, &sender)?;831832			target_collection.sponsorship = SponsorshipState::Disabled;833			target_collection.save()834		}835836		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.837		///838		/// # Permissions839		///840		/// * Collection Owner.841		/// * Collection Admin.842		/// * Anyone if843		///     * White List is enabled, and844		///     * Address is added to white list, and845		///     * MintPermission is enabled (see SetMintPermission method)846		///847		/// # Arguments848		///849		/// * collection_id: ID of the collection.850		///851		/// * owner: Address, initial owner of the NFT.852		///853		/// * data: Token data to store on chain.854		// #[weight =855		// (130_000_000 as Weight)856		// .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))857		// .saturating_add(RocksDbWeight::get().reads(10 as Weight))858		// .saturating_add(RocksDbWeight::get().writes(8 as Weight))]859860		#[weight = <T as Config>::WeightInfo::create_item(data.data_size())]861		#[transactional]862		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResult {863			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);864			let collection = Self::get_collection(collection_id)?;865866			Self::create_item_internal(&sender, &collection, &owner, data)?;867868			collection.submit_logs()869		}870871		/// This method creates multiple items in a collection created with CreateCollection method.872		///873		/// # Permissions874		///875		/// * Collection Owner.876		/// * Collection Admin.877		/// * Anyone if878		///     * White List is enabled, and879		///     * Address is added to white list, and880		///     * MintPermission is enabled (see SetMintPermission method)881		///882		/// # Arguments883		///884		/// * collection_id: ID of the collection.885		///886		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].887		///888		/// * owner: Address, initial owner of the NFT.889		#[weight = <T as Config>::WeightInfo::create_item(items_data.iter()890							   .map(|data| { data.data_size() })891							   .sum())]892		#[transactional]893		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResult {894895			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);896			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);897			let collection = Self::get_collection(collection_id)?;898899			Self::create_multiple_items_internal(&sender, &collection, &owner, items_data)?;900901			collection.submit_logs()902		}903904		// TODO! transaction weight905906		/// Set transfers_enabled value for particular collection907		///908		/// # Permissions909		///910		/// * Collection Owner.911		///912		/// # Arguments913		///914		/// * collection_id: ID of the collection.915		///916		/// * value: New flag value.917		#[weight = <T as Config>::WeightInfo::burn_item()]918		#[transactional]919		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {920921			let sender = ensure_signed(origin)?;922			let mut target_collection = Self::get_collection(collection_id)?;923924			Self::check_owner_permissions(&target_collection, &sender)?;925926			target_collection.transfers_enabled = value;927			target_collection.save()928		}929930		/// Destroys a concrete instance of NFT.931		///932		/// # Permissions933		///934		/// * Collection Owner.935		/// * Collection Admin.936		/// * Current NFT Owner.937		///938		/// # Arguments939		///940		/// * collection_id: ID of the collection.941		///942		/// * item_id: ID of NFT to burn.943		#[weight = <T as Config>::WeightInfo::burn_item()]944		#[transactional]945		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {946947			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);948			let target_collection = Self::get_collection(collection_id)?;949950			Self::burn_item_internal(&sender, &target_collection, item_id, value)?;951952			target_collection.submit_logs()953		}954955		/// Change ownership of the token.956		///957		/// # Permissions958		///959		/// * Collection Owner960		/// * Collection Admin961		/// * Current NFT owner962		///963		/// # Arguments964		///965		/// * recipient: Address of token recipient.966		///967		/// * collection_id.968		///969		/// * item_id: ID of the item970		///     * Non-Fungible Mode: Required.971		///     * Fungible Mode: Ignored.972		///     * Re-Fungible Mode: Required.973		///974		/// * value: Amount to transfer.975		///     * Non-Fungible Mode: Ignored976		///     * Fungible Mode: Must specify transferred amount977		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)978		#[weight = <T as Config>::WeightInfo::transfer()]979		#[transactional]980		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {981			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);982			let collection = Self::get_collection(collection_id)?;983984			Self::transfer_internal(&sender, &recipient, &collection, item_id, value)?;985986			collection.submit_logs()987		}988989		/// Set, change, or remove approved address to transfer the ownership of the NFT.990		///991		/// # Permissions992		///993		/// * Collection Owner994		/// * Collection Admin995		/// * Current NFT owner996		///997		/// # Arguments998		///999		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1000		///1001		/// * collection_id.1002		///1003		/// * item_id: ID of the item.1004		#[weight = <T as Config>::WeightInfo::approve()]1005		#[transactional]1006		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {1007			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1008			let collection = Self::get_collection(collection_id)?;10091010			Self::approve_internal(&sender, &spender, &collection, item_id, amount)?;10111012			collection.submit_logs()1013		}10141015		/// 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.1016		///1017		/// # Permissions1018		/// * Collection Owner1019		/// * Collection Admin1020		/// * Current NFT owner1021		/// * Address approved by current NFT owner1022		///1023		/// # Arguments1024		///1025		/// * from: Address that owns token.1026		///1027		/// * recipient: Address of token recipient.1028		///1029		/// * collection_id.1030		///1031		/// * item_id: ID of the item.1032		///1033		/// * value: Amount to transfer.1034		#[weight = <T as Config>::WeightInfo::transfer_from()]1035		#[transactional]1036		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {1037			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1038			let collection = Self::get_collection(collection_id)?;10391040			Self::transfer_from_internal(&sender, &from, &recipient, &collection, item_id, value)?;10411042			collection.submit_logs()1043		}1044		// #[weight = 0]1045		//     // let no_perm_mes = "You do not have permissions to modify this collection";1046		//     // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1047		//     // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1048		//     // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);10491050		//     // // on_nft_received  call10511052		//     // Self::transfer(origin, collection_id, item_id, new_owner)?;10531054		//     Ok(())1055		// }10561057		/// Set off-chain data schema.1058		///1059		/// # Permissions1060		///1061		/// * Collection Owner1062		/// * Collection Admin1063		///1064		/// # Arguments1065		///1066		/// * collection_id.1067		///1068		/// * schema: String representing the offchain data schema.1069		#[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1070		#[transactional]1071		pub fn set_variable_meta_data (1072			origin,1073			collection_id: CollectionId,1074			item_id: TokenId,1075			data: Vec<u8>1076		) -> DispatchResult {1077			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);10781079			let collection = Self::get_collection(collection_id)?;10801081			Self::set_variable_meta_data_internal(&sender, &collection, item_id, data)?;10821083			Ok(())1084		}10851086		/// Set schema standard1087		/// ImageURL1088		/// Unique1089		///1090		/// # Permissions1091		///1092		/// * Collection Owner1093		/// * Collection Admin1094		///1095		/// # Arguments1096		///1097		/// * collection_id.1098		///1099		/// * schema: SchemaVersion: enum1100		#[weight = <T as Config>::WeightInfo::set_schema_version()]1101		#[transactional]1102		pub fn set_schema_version(1103			origin,1104			collection_id: CollectionId,1105			version: SchemaVersion1106		) -> DispatchResult {1107			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1108			let mut target_collection = Self::get_collection(collection_id)?;1109			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;1110			target_collection.schema_version = version;1111			target_collection.save()1112		}11131114		/// Set off-chain data schema.1115		///1116		/// # Permissions1117		///1118		/// * Collection Owner1119		/// * Collection Admin1120		///1121		/// # Arguments1122		///1123		/// * collection_id.1124		///1125		/// * schema: String representing the offchain data schema.1126		#[weight = <T as Config>::WeightInfo::set_offchain_schema()]1127		#[transactional]1128		pub fn set_offchain_schema(1129			origin,1130			collection_id: CollectionId,1131			schema: Vec<u8>1132		) -> DispatchResult {1133			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1134			let mut target_collection = Self::get_collection(collection_id)?;1135			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11361137			// check schema limit1138			ensure!(schema.len() as u32 <= OFFCHAIN_SCHEMA_LIMIT, "");11391140			target_collection.offchain_schema = schema;1141			target_collection.save()1142		}11431144		/// Set const on-chain data schema.1145		///1146		/// # Permissions1147		///1148		/// * Collection Owner1149		/// * Collection Admin1150		///1151		/// # Arguments1152		///1153		/// * collection_id.1154		///1155		/// * schema: String representing the const on-chain data schema.1156		#[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1157		#[transactional]1158		pub fn set_const_on_chain_schema (1159			origin,1160			collection_id: CollectionId,1161			schema: Vec<u8>1162		) -> DispatchResult {1163			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1164			let mut target_collection = Self::get_collection(collection_id)?;1165			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11661167			// check schema limit1168			ensure!(schema.len() as u32 <= CONST_ON_CHAIN_SCHEMA_LIMIT, "");11691170			target_collection.const_on_chain_schema = schema;1171			target_collection.save()1172		}11731174		/// Set variable on-chain data schema.1175		///1176		/// # Permissions1177		///1178		/// * Collection Owner1179		/// * Collection Admin1180		///1181		/// # Arguments1182		///1183		/// * collection_id.1184		///1185		/// * schema: String representing the variable on-chain data schema.1186		#[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1187		#[transactional]1188		pub fn set_variable_on_chain_schema (1189			origin,1190			collection_id: CollectionId,1191			schema: Vec<u8>1192		) -> DispatchResult {1193			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1194			let mut target_collection = Self::get_collection(collection_id)?;1195			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11961197			// check schema limit1198			ensure!(schema.len() as u32 <= VARIABLE_ON_CHAIN_SCHEMA_LIMIT, "");11991200			target_collection.variable_on_chain_schema = schema;1201			target_collection.save()1202		}12031204		#[weight = <T as Config>::WeightInfo::set_collection_limits()]1205		#[transactional]1206		pub fn set_collection_limits(1207			origin,1208			collection_id: u32,1209			new_limits: CollectionLimits<T::BlockNumber>,1210		) -> DispatchResult {1211			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1212			let mut target_collection = Self::get_collection(collection_id)?;1213			Self::check_owner_permissions(&target_collection, sender.as_sub())?;1214			let old_limits = &target_collection.limits;12151216			// collection bounds1217			ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1218				new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP &&1219				new_limits.sponsored_data_size <= CUSTOM_DATA_LIMIT,1220				Error::<T>::CollectionLimitBoundsExceeded);12211222			// token_limit   check  prev1223			ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1224			ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);12251226			ensure!(1227				(old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1228				(old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1229				Error::<T>::OwnerPermissionsCantBeReverted,1230			);12311232			target_collection.limits = new_limits;12331234			target_collection.save()1235		}1236	}1237}12381239impl<T: Config> Module<T> {1240	pub fn create_item_internal(1241		sender: &T::CrossAccountId,1242		collection: &CollectionHandle<T>,1243		owner: &T::CrossAccountId,1244		data: CreateItemData,1245	) -> DispatchResult {1246		ensure!(1247			owner != &T::CrossAccountId::from_eth(H160([0; 20])),1248			Error::<T>::AddressIsZero1249		);12501251		Self::can_create_items_in_collection(collection, sender, owner, 1)?;1252		Self::validate_create_item_args(collection, &data)?;1253		Self::create_item_no_validation(collection, owner, data)?;12541255		Ok(())1256	}12571258	pub fn transfer_internal(1259		sender: &T::CrossAccountId,1260		recipient: &T::CrossAccountId,1261		target_collection: &CollectionHandle<T>,1262		item_id: TokenId,1263		value: u128,1264	) -> DispatchResult {1265		ensure!(1266			recipient != &T::CrossAccountId::from_eth(H160([0; 20])),1267			Error::<T>::AddressIsZero1268		);12691270		target_collection.consume_gas(2000000)?;1271		// Limits check1272		Self::is_correct_transfer(target_collection, recipient)?;12731274		// Transfer permissions check1275		ensure!(1276			Self::is_item_owner(sender, target_collection, item_id)1277				|| Self::is_owner_or_admin_permissions(target_collection, sender),1278			Error::<T>::NoPermission1279		);12801281		if target_collection.access == AccessMode::WhiteList {1282			Self::check_white_list(target_collection, sender)?;1283			Self::check_white_list(target_collection, recipient)?;1284		}12851286		match target_collection.mode {1287			CollectionMode::NFT => Self::transfer_nft(1288				target_collection,1289				item_id,1290				sender.clone(),1291				recipient.clone(),1292			)?,1293			CollectionMode::Fungible(_) => {1294				Self::transfer_fungible(target_collection, value, sender, recipient)?1295			}1296			CollectionMode::ReFungible => Self::transfer_refungible(1297				target_collection,1298				item_id,1299				value,1300				sender.clone(),1301				recipient.clone(),1302			)?,1303			_ => (),1304		};13051306		Self::deposit_event(RawEvent::Transfer(1307			target_collection.id,1308			item_id,1309			sender.clone(),1310			recipient.clone(),1311			value,1312		));13131314		Ok(())1315	}13161317	pub fn approve_internal(1318		sender: &T::CrossAccountId,1319		spender: &T::CrossAccountId,1320		collection: &CollectionHandle<T>,1321		item_id: TokenId,1322		amount: u128,1323	) -> DispatchResult {1324		collection.consume_gas(2000000)?;1325		Self::token_exists(collection, item_id)?;13261327		// Transfer permissions check1328		let bypasses_limits = collection.limits.owner_can_transfer1329			&& Self::is_owner_or_admin_permissions(collection, sender);13301331		let allowance_limit = if bypasses_limits {1332			None1333		} else if let Some(amount) = Self::owned_amount(sender, collection, item_id) {1334			Some(amount)1335		} else {1336			fail!(Error::<T>::NoPermission);1337		};13381339		if collection.access == AccessMode::WhiteList {1340			Self::check_white_list(collection, sender)?;1341			Self::check_white_list(collection, spender)?;1342		}13431344		let allowance: u128 = amount1345			.checked_add(<Allowances<T>>::get(1346				collection.id,1347				(item_id, sender.as_sub(), spender.as_sub()),1348			))1349			.ok_or(Error::<T>::NumOverflow)?;1350		if let Some(limit) = allowance_limit {1351			ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1352		}1353		<Allowances<T>>::insert(1354			collection.id,1355			(item_id, sender.as_sub(), spender.as_sub()),1356			allowance,1357		);13581359		if matches!(collection.mode, CollectionMode::NFT) {1360			// TODO: NFT: only one owner may exist for token in ERC7211361			collection.log(ERC721Events::Approval {1362				owner: *sender.as_eth(),1363				approved: *spender.as_eth(),1364				token_id: item_id.into(),1365			})?;1366		}13671368		if matches!(collection.mode, CollectionMode::Fungible(_)) {1369			// TODO: NFT: only one owner may exist for token in ERC201370			collection.log(ERC20Events::Approval {1371				owner: *sender.as_eth(),1372				spender: *spender.as_eth(),1373				value: allowance.into(),1374			})?;1375		}13761377		Self::deposit_event(RawEvent::Approved(1378			collection.id,1379			item_id,1380			sender.clone(),1381			spender.clone(),1382			allowance,1383		));1384		Ok(())1385	}13861387	pub fn transfer_from_internal(1388		sender: &T::CrossAccountId,1389		from: &T::CrossAccountId,1390		recipient: &T::CrossAccountId,1391		collection: &CollectionHandle<T>,1392		item_id: TokenId,1393		amount: u128,1394	) -> DispatchResult {1395		collection.consume_gas(2000000)?;1396		// Check approval1397		let approval: u128 =1398			<Allowances<T>>::get(collection.id, (item_id, from.as_sub(), sender.as_sub()));13991400		// Limits check1401		Self::is_correct_transfer(collection, recipient)?;14021403		// Transfer permissions check1404		ensure!(1405			approval >= amount1406				|| (collection.limits.owner_can_transfer1407					&& Self::is_owner_or_admin_permissions(collection, sender)),1408			Error::<T>::NoPermission1409		);14101411		if collection.access == AccessMode::WhiteList {1412			Self::check_white_list(collection, sender)?;1413			Self::check_white_list(collection, recipient)?;1414		}14151416		// Reduce approval by transferred amount or remove if remaining approval drops to 01417		let allowance = approval.saturating_sub(amount);1418		if allowance > 0 {1419			<Allowances<T>>::insert(1420				collection.id,1421				(item_id, from.as_sub(), sender.as_sub()),1422				allowance,1423			);1424		} else {1425			<Allowances<T>>::remove(collection.id, (item_id, from.as_sub(), sender.as_sub()));1426		}14271428		match collection.mode {1429			CollectionMode::NFT => {1430				Self::transfer_nft(collection, item_id, from.clone(), recipient.clone())?1431			}1432			CollectionMode::Fungible(_) => {1433				Self::transfer_fungible(collection, amount, from, recipient)?1434			}1435			CollectionMode::ReFungible => Self::transfer_refungible(1436				collection,1437				item_id,1438				amount,1439				from.clone(),1440				recipient.clone(),1441			)?,1442			_ => (),1443		};14441445		if matches!(collection.mode, CollectionMode::Fungible(_)) {1446			collection.log(ERC20Events::Approval {1447				owner: *from.as_eth(),1448				spender: *sender.as_eth(),1449				value: allowance.into(),1450			})?;1451		}14521453		Ok(())1454	}14551456	pub fn set_variable_meta_data_internal(1457		sender: &T::CrossAccountId,1458		collection: &CollectionHandle<T>,1459		item_id: TokenId,1460		data: Vec<u8>,1461	) -> DispatchResult {1462		Self::token_exists(collection, item_id)?;14631464		ensure!(1465			CUSTOM_DATA_LIMIT >= data.len() as u32,1466			Error::<T>::TokenVariableDataLimitExceeded1467		);14681469		// Modify permissions check1470		ensure!(1471			Self::is_item_owner(sender, collection, item_id)1472				|| Self::is_owner_or_admin_permissions(collection, sender),1473			Error::<T>::NoPermission1474		);14751476		match collection.mode {1477			CollectionMode::NFT => Self::set_nft_variable_data(collection, item_id, data)?,1478			CollectionMode::ReFungible => {1479				Self::set_re_fungible_variable_data(collection, item_id, data)?1480			}1481			CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1482			_ => fail!(Error::<T>::UnexpectedCollectionType),1483		};14841485		Ok(())1486	}14871488	pub fn create_multiple_items_internal(1489		sender: &T::CrossAccountId,1490		collection: &CollectionHandle<T>,1491		owner: &T::CrossAccountId,1492		items_data: Vec<CreateItemData>,1493	) -> DispatchResult {1494		Self::can_create_items_in_collection(collection, sender, owner, items_data.len() as u32)?;14951496		for data in &items_data {1497			Self::validate_create_item_args(collection, data)?;1498		}1499		for data in &items_data {1500			Self::create_item_no_validation(collection, owner, data.clone())?;1501		}15021503		Ok(())1504	}15051506	pub fn burn_item_internal(1507		sender: &T::CrossAccountId,1508		collection: &CollectionHandle<T>,1509		item_id: TokenId,1510		value: u128,1511	) -> DispatchResult {1512		ensure!(1513			Self::is_item_owner(sender, collection, item_id)1514				|| (collection.limits.owner_can_transfer1515					&& Self::is_owner_or_admin_permissions(collection, sender)),1516			Error::<T>::NoPermission1517		);15181519		if collection.access == AccessMode::WhiteList {1520			Self::check_white_list(collection, sender)?;1521		}15221523		match collection.mode {1524			CollectionMode::NFT => Self::burn_nft_item(collection, item_id)?,1525			CollectionMode::Fungible(_) => Self::burn_fungible_item(sender, collection, value)?,1526			CollectionMode::ReFungible => Self::burn_refungible_item(collection, item_id, sender)?,1527			_ => (),1528		};15291530		Ok(())1531	}15321533	pub fn toggle_white_list_internal(1534		sender: &T::CrossAccountId,1535		collection: &CollectionHandle<T>,1536		address: &T::CrossAccountId,1537		whitelisted: bool,1538	) -> DispatchResult {1539		Self::check_owner_or_admin_permissions(collection, sender)?;15401541		if whitelisted {1542			<WhiteList<T>>::insert(collection.id, address.as_sub(), true);1543		} else {1544			<WhiteList<T>>::remove(collection.id, address.as_sub());1545		}15461547		Ok(())1548	}15491550	fn is_correct_transfer(1551		collection: &CollectionHandle<T>,1552		recipient: &T::CrossAccountId,1553	) -> DispatchResult {1554		let collection_id = collection.id;15551556		// check token limit and account token limit1557		let account_items: u32 =1558			<AddressTokens<T>>::get(collection_id, recipient.as_sub()).len() as u32;1559		ensure!(1560			collection.limits.account_token_ownership_limit > account_items,1561			Error::<T>::AccountTokenLimitExceeded1562		);15631564		// preliminary transfer check1565		ensure!(collection.transfers_enabled, Error::<T>::TransferNotAllowed);15661567		Ok(())1568	}15691570	fn can_create_items_in_collection(1571		collection: &CollectionHandle<T>,1572		sender: &T::CrossAccountId,1573		owner: &T::CrossAccountId,1574		amount: u32,1575	) -> DispatchResult {1576		let collection_id = collection.id;15771578		// check token limit and account token limit1579		let total_items: u32 = ItemListIndex::get(collection_id)1580			.checked_add(amount)1581			.ok_or(Error::<T>::CollectionTokenLimitExceeded)?;1582		let account_items: u32 = (<AddressTokens<T>>::get(collection_id, owner.as_sub()).len()1583			as u32)1584			.checked_add(amount)1585			.ok_or(Error::<T>::AccountTokenLimitExceeded)?;1586		ensure!(1587			collection.limits.token_limit >= total_items,1588			Error::<T>::CollectionTokenLimitExceeded1589		);1590		ensure!(1591			collection.limits.account_token_ownership_limit >= account_items,1592			Error::<T>::AccountTokenLimitExceeded1593		);15941595		if !Self::is_owner_or_admin_permissions(collection, sender) {1596			ensure!(collection.mint_mode, Error::<T>::PublicMintingNotAllowed);1597			Self::check_white_list(collection, owner)?;1598			Self::check_white_list(collection, sender)?;1599		}16001601		Ok(())1602	}16031604	fn validate_create_item_args(1605		target_collection: &CollectionHandle<T>,1606		data: &CreateItemData,1607	) -> DispatchResult {1608		match target_collection.mode {1609			CollectionMode::NFT => {1610				if !matches!(data, CreateItemData::NFT(_)) {1611					fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1612				}1613			}1614			CollectionMode::Fungible(_) => {1615				if !matches!(data, CreateItemData::Fungible(_)) {1616					fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1617				}1618			}1619			CollectionMode::ReFungible => {1620				if let CreateItemData::ReFungible(data) = data {1621					// Check refungibility limits1622					ensure!(1623						data.pieces <= MAX_REFUNGIBLE_PIECES,1624						Error::<T>::WrongRefungiblePieces1625					);1626					ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1627				} else {1628					fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1629				}1630			}1631			_ => {1632				fail!(Error::<T>::UnexpectedCollectionType);1633			}1634		};16351636		Ok(())1637	}16381639	fn create_item_no_validation(1640		collection: &CollectionHandle<T>,1641		owner: &T::CrossAccountId,1642		data: CreateItemData,1643	) -> DispatchResult {1644		match data {1645			CreateItemData::NFT(data) => {1646				let item = NftItemType {1647					owner: owner.clone(),1648					const_data: data.const_data.into_inner(),1649					variable_data: data.variable_data.into_inner(),1650				};16511652				Self::add_nft_item(collection, item)?;1653			}1654			CreateItemData::Fungible(data) => {1655				Self::add_fungible_item(collection, owner, data.value)?;1656			}1657			CreateItemData::ReFungible(data) => {1658				let owner_list = vec![Ownership {1659					owner: owner.clone(),1660					fraction: data.pieces,1661				}];16621663				let item = ReFungibleItemType {1664					owner: owner_list,1665					const_data: data.const_data.into_inner(),1666					variable_data: data.variable_data.into_inner(),1667				};16681669				Self::add_refungible_item(collection, item)?;1670			}1671		};16721673		Ok(())1674	}16751676	fn add_fungible_item(1677		collection: &CollectionHandle<T>,1678		owner: &T::CrossAccountId,1679		value: u128,1680	) -> DispatchResult {1681		let collection_id = collection.id;16821683		// Does new owner already have an account?1684		let balance: u128 = <FungibleItemList<T>>::get(collection_id, owner.as_sub()).value;16851686		// Mint1687		let item = FungibleItemType {1688			value: balance.checked_add(value).ok_or(Error::<T>::NumOverflow)?,1689		};1690		<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), item);16911692		// Update balance1693		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1694			.checked_add(value)1695			.ok_or(Error::<T>::NumOverflow)?;1696		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);16971698		Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));1699		Ok(())1700	}17011702	fn add_refungible_item(1703		collection: &CollectionHandle<T>,1704		item: ReFungibleItemType<T::CrossAccountId>,1705	) -> DispatchResult {1706		let collection_id = collection.id;17071708		let current_index = <ItemListIndex>::get(collection_id)1709			.checked_add(1)1710			.ok_or(Error::<T>::NumOverflow)?;1711		let itemcopy = item.clone();17121713		ensure!(item.owner.len() == 1, Error::<T>::BadCreateRefungibleCall,);1714		let item_owner = item.owner.first().expect("only one owner is defined");17151716		let value = item_owner.fraction;1717		let owner = item_owner.owner.clone();17181719		Self::add_token_index(collection_id, current_index, &owner)?;17201721		<ItemListIndex>::insert(collection_id, current_index);1722		<ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);17231724		// Update balance1725		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1726			.checked_add(value)1727			.ok_or(Error::<T>::NumOverflow)?;1728		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);17291730		Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));1731		Ok(())1732	}17331734	fn add_nft_item(1735		collection: &CollectionHandle<T>,1736		item: NftItemType<T::CrossAccountId>,1737	) -> DispatchResult {1738		let collection_id = collection.id;17391740		let current_index = <ItemListIndex>::get(collection_id)1741			.checked_add(1)1742			.ok_or(Error::<T>::NumOverflow)?;17431744		let item_owner = item.owner.clone();1745		Self::add_token_index(collection_id, current_index, &item.owner)?;17461747		<ItemListIndex>::insert(collection_id, current_index);1748		<NftItemList<T>>::insert(collection_id, current_index, item);17491750		// Update balance1751		let new_balance = <Balance<T>>::get(collection_id, item_owner.as_sub())1752			.checked_add(1)1753			.ok_or(Error::<T>::NumOverflow)?;1754		<Balance<T>>::insert(collection_id, item_owner.as_sub(), new_balance);17551756		collection.log(ERC721Events::Transfer {1757			from: H160::default(),1758			to: *item_owner.as_eth(),1759			token_id: current_index.into(),1760		})?;1761		Self::deposit_event(RawEvent::ItemCreated(1762			collection_id,1763			current_index,1764			item_owner,1765		));1766		Ok(())1767	}17681769	fn burn_refungible_item(1770		collection: &CollectionHandle<T>,1771		item_id: TokenId,1772		owner: &T::CrossAccountId,1773	) -> DispatchResult {1774		let collection_id = collection.id;17751776		let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id)1777			.ok_or(Error::<T>::TokenNotFound)?;1778		let rft_balance = token1779			.owner1780			.iter()1781			.find(|&i| i.owner == *owner)1782			.ok_or(Error::<T>::TokenNotFound)?;1783		Self::remove_token_index(collection_id, item_id, owner)?;17841785		// update balance1786		let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.as_sub())1787			.checked_sub(rft_balance.fraction)1788			.ok_or(Error::<T>::NumOverflow)?;1789		<Balance<T>>::insert(collection_id, rft_balance.owner.as_sub(), new_balance);17901791		// Re-create owners list with sender removed1792		let index = token1793			.owner1794			.iter()1795			.position(|i| i.owner == *owner)1796			.expect("owned item is exists");1797		token.owner.remove(index);1798		let owner_count = token.owner.len();17991800		// Burn the token completely if this was the last (only) owner1801		if owner_count == 0 {1802			<ReFungibleItemList<T>>::remove(collection_id, item_id);1803			<VariableMetaDataBasket<T>>::remove(collection_id, item_id);1804		} else {1805			<ReFungibleItemList<T>>::insert(collection_id, item_id, token);1806		}18071808		Ok(())1809	}18101811	fn burn_nft_item(collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1812		let collection_id = collection.id;18131814		let item =1815			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;1816		Self::remove_token_index(collection_id, item_id, &item.owner)?;18171818		// update balance1819		let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())1820			.checked_sub(1)1821			.ok_or(Error::<T>::NumOverflow)?;1822		<Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);1823		<NftItemList<T>>::remove(collection_id, item_id);1824		<VariableMetaDataBasket<T>>::remove(collection_id, item_id);18251826		collection.log(ERC721Events::Transfer {1827			from: *item.owner.as_eth(),1828			to: H160::default(),1829			token_id: item_id.into(),1830		})?;1831		Self::deposit_event(RawEvent::ItemDestroyed(collection.id, item_id));1832		Ok(())1833	}18341835	fn burn_fungible_item(1836		owner: &T::CrossAccountId,1837		collection: &CollectionHandle<T>,1838		value: u128,1839	) -> DispatchResult {1840		let collection_id = collection.id;18411842		let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());1843		ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);18441845		// update balance1846		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1847			.checked_sub(value)1848			.ok_or(Error::<T>::NumOverflow)?;1849		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);18501851		if balance.value - value > 0 {1852			balance.value -= value;1853			<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);1854		} else {1855			<FungibleItemList<T>>::remove(collection_id, owner.as_sub());1856		}18571858		collection.log(ERC20Events::Transfer {1859			from: *owner.as_eth(),1860			to: H160::default(),1861			value: value.into(),1862		})?;1863		Ok(())1864	}18651866	pub fn get_collection(1867		collection_id: CollectionId,1868	) -> Result<CollectionHandle<T>, sp_runtime::DispatchError> {1869		Ok(<CollectionHandle<T>>::get(collection_id).ok_or(Error::<T>::CollectionNotFound)?)1870	}18711872	fn check_owner_permissions(1873		target_collection: &CollectionHandle<T>,1874		subject: &T::AccountId,1875	) -> DispatchResult {1876		ensure!(1877			*subject == target_collection.owner,1878			Error::<T>::NoPermission1879		);18801881		Ok(())1882	}18831884	fn is_owner_or_admin_permissions(1885		collection: &CollectionHandle<T>,1886		subject: &T::CrossAccountId,1887	) -> bool {1888		*subject.as_sub() == collection.owner1889			|| <AdminList<T>>::get(collection.id).contains(subject)1890	}18911892	fn check_owner_or_admin_permissions(1893		collection: &CollectionHandle<T>,1894		subject: &T::CrossAccountId,1895	) -> DispatchResult {1896		ensure!(1897			Self::is_owner_or_admin_permissions(collection, subject),1898			Error::<T>::NoPermission1899		);19001901		Ok(())1902	}19031904	fn owned_amount(1905		subject: &T::CrossAccountId,1906		target_collection: &CollectionHandle<T>,1907		item_id: TokenId,1908	) -> Option<u128> {1909		let collection_id = target_collection.id;19101911		match target_collection.mode {1912			CollectionMode::NFT => {1913				(<NftItemList<T>>::get(collection_id, item_id)?.owner == *subject).then(|| 1)1914			}1915			CollectionMode::Fungible(_) => {1916				Some(<FungibleItemList<T>>::get(collection_id, &subject.as_sub()).value)1917			}1918			CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)?1919				.owner1920				.iter()1921				.find(|i| i.owner == *subject)1922				.map(|i| i.fraction),1923			CollectionMode::Invalid => None,1924		}1925	}19261927	fn is_item_owner(1928		subject: &T::CrossAccountId,1929		target_collection: &CollectionHandle<T>,1930		item_id: TokenId,1931	) -> bool {1932		match target_collection.mode {1933			CollectionMode::Fungible(_) => true,1934			_ => Self::owned_amount(subject, target_collection, item_id).is_some(),1935		}1936	}19371938	fn check_white_list(1939		collection: &CollectionHandle<T>,1940		address: &T::CrossAccountId,1941	) -> DispatchResult {1942		let collection_id = collection.id;19431944		let mes = Error::<T>::AddresNotInWhiteList;1945		ensure!(1946			<WhiteList<T>>::contains_key(collection_id, address.as_sub()),1947			mes1948		);19491950		Ok(())1951	}19521953	/// Check if token exists. In case of Fungible, check if there is an entry for1954	/// the owner in fungible balances double map1955	fn token_exists(target_collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1956		let collection_id = target_collection.id;1957		let exists = match target_collection.mode {1958			CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),1959			CollectionMode::Fungible(_) => true,1960			CollectionMode::ReFungible => {1961				<ReFungibleItemList<T>>::contains_key(collection_id, item_id)1962			}1963			_ => false,1964		};19651966		ensure!(exists, Error::<T>::TokenNotFound);1967		Ok(())1968	}19691970	fn transfer_fungible(1971		collection: &CollectionHandle<T>,1972		value: u128,1973		owner: &T::CrossAccountId,1974		recipient: &T::CrossAccountId,1975	) -> DispatchResult {1976		let collection_id = collection.id;19771978		let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());1979		ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);19801981		// Send balance to recipient (updates balanceOf of recipient)1982		Self::add_fungible_item(collection, recipient, value)?;19831984		// update balanceOf of sender1985		<Balance<T>>::insert(collection_id, owner.as_sub(), balance.value - value);19861987		// Reduce or remove sender1988		if balance.value == value {1989			<FungibleItemList<T>>::remove(collection_id, owner.as_sub());1990		} else {1991			balance.value -= value;1992			<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);1993		}19941995		collection.log(ERC20Events::Transfer {1996			from: *owner.as_eth(),1997			to: *recipient.as_eth(),1998			value: value.into(),1999		})?;2000		Self::deposit_event(RawEvent::Transfer(2001			collection.id,2002			1,2003			owner.clone(),2004			recipient.clone(),2005			value,2006		));20072008		Ok(())2009	}20102011	fn transfer_refungible(2012		collection: &CollectionHandle<T>,2013		item_id: TokenId,2014		value: u128,2015		owner: T::CrossAccountId,2016		new_owner: T::CrossAccountId,2017	) -> DispatchResult {2018		let collection_id = collection.id;2019		let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id)2020			.ok_or(Error::<T>::TokenNotFound)?;20212022		let item = full_item2023			.owner2024			.iter()2025			.find(|i| i.owner == owner)2026			.ok_or(Error::<T>::TokenNotFound)?;2027		let amount = item.fraction;20282029		ensure!(amount >= value, Error::<T>::TokenValueTooLow);20302031		// update balance2032		let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2033			.checked_sub(value)2034			.ok_or(Error::<T>::NumOverflow)?;2035		<Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);20362037		let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2038			.checked_add(value)2039			.ok_or(Error::<T>::NumOverflow)?;2040		<Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);20412042		let old_owner = item.owner.clone();2043		let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);20442045		let mut new_full_item = full_item.clone();2046		// transfer2047		if amount == value && !new_owner_has_account {2048			// change owner2049			// new owner do not have account2050			new_full_item2051				.owner2052				.iter_mut()2053				.find(|i| i.owner == owner)2054				.expect("old owner does present in refungible")2055				.owner = new_owner.clone();2056			<ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);20572058			// update index collection2059			Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;2060		} else {2061			new_full_item2062				.owner2063				.iter_mut()2064				.find(|i| i.owner == owner)2065				.expect("old owner does present in refungible")2066				.fraction -= value;20672068			// separate amount2069			if new_owner_has_account {2070				// new owner has account2071				new_full_item2072					.owner2073					.iter_mut()2074					.find(|i| i.owner == new_owner)2075					.expect("new owner has account")2076					.fraction += value;2077			} else {2078				// new owner do not have account2079				new_full_item.owner.push(Ownership {2080					owner: new_owner.clone(),2081					fraction: value,2082				});2083				Self::add_token_index(collection_id, item_id, &new_owner)?;2084			}20852086			<ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2087		}20882089		Self::deposit_event(RawEvent::Transfer(2090			collection.id,2091			item_id,2092			owner,2093			new_owner,2094			amount,2095		));20962097		Ok(())2098	}20992100	fn transfer_nft(2101		collection: &CollectionHandle<T>,2102		item_id: TokenId,2103		sender: T::CrossAccountId,2104		new_owner: T::CrossAccountId,2105	) -> DispatchResult {2106		let collection_id = collection.id;2107		let mut item =2108			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;21092110		ensure!(sender == item.owner, Error::<T>::MustBeTokenOwner);21112112		// update balance2113		let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2114			.checked_sub(1)2115			.ok_or(Error::<T>::NumOverflow)?;2116		<Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);21172118		let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2119			.checked_add(1)2120			.ok_or(Error::<T>::NumOverflow)?;2121		<Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);21222123		// change owner2124		let old_owner = item.owner.clone();2125		item.owner = new_owner.clone();2126		<NftItemList<T>>::insert(collection_id, item_id, item);21272128		// update index collection2129		Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;21302131		collection.log(ERC721Events::Transfer {2132			from: *sender.as_eth(),2133			to: *new_owner.as_eth(),2134			token_id: item_id.into(),2135		})?;2136		Self::deposit_event(RawEvent::Transfer(2137			collection.id,2138			item_id,2139			sender,2140			new_owner,2141			1,2142		));21432144		Ok(())2145	}21462147	fn set_re_fungible_variable_data(2148		collection: &CollectionHandle<T>,2149		item_id: TokenId,2150		data: Vec<u8>,2151	) -> DispatchResult {2152		let collection_id = collection.id;2153		let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id)2154			.ok_or(Error::<T>::TokenNotFound)?;21552156		item.variable_data = data;21572158		<ReFungibleItemList<T>>::insert(collection_id, item_id, item);21592160		Ok(())2161	}21622163	fn set_nft_variable_data(2164		collection: &CollectionHandle<T>,2165		item_id: TokenId,2166		data: Vec<u8>,2167	) -> DispatchResult {2168		let collection_id = collection.id;2169		let mut item =2170			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;21712172		item.variable_data = data;21732174		<NftItemList<T>>::insert(collection_id, item_id, item);21752176		Ok(())2177	}21782179	#[allow(dead_code)]2180	fn init_collection(item: &Collection<T>) {2181		// check params2182		assert!(2183			item.decimal_points <= MAX_DECIMAL_POINTS,2184			"decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2185		);2186		assert!(2187			item.name.len() <= 64,2188			"Collection name can not be longer than 63 char"2189		);2190		assert!(2191			item.name.len() <= 256,2192			"Collection description can not be longer than 255 char"2193		);2194		assert!(2195			item.token_prefix.len() <= 16,2196			"Token prefix can not be longer than 15 char"2197		);21982199		// Generate next collection ID2200		let next_id = CreatedCollectionCount::get().checked_add(1).unwrap();22012202		CreatedCollectionCount::put(next_id);2203	}22042205	#[allow(dead_code)]2206	fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::CrossAccountId>) {2207		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();22082209		Self::add_token_index(collection_id, current_index, &item.owner).unwrap();22102211		<ItemListIndex>::insert(collection_id, current_index);22122213		// Update balance2214		let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2215			.checked_add(1)2216			.unwrap();2217		<Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2218	}22192220	#[allow(dead_code)]2221	fn init_fungible_token(2222		collection_id: CollectionId,2223		owner: &T::CrossAccountId,2224		item: &FungibleItemType,2225	) {2226		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();22272228		Self::add_token_index(collection_id, current_index, owner).unwrap();22292230		<ItemListIndex>::insert(collection_id, current_index);22312232		// Update balance2233		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2234			.checked_add(item.value)2235			.unwrap();2236		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2237	}22382239	#[allow(dead_code)]2240	fn init_refungible_token(2241		collection_id: CollectionId,2242		item: &ReFungibleItemType<T::CrossAccountId>,2243	) {2244		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();22452246		let value = item.owner.first().unwrap().fraction;2247		let owner = item.owner.first().unwrap().owner.clone();22482249		Self::add_token_index(collection_id, current_index, &owner).unwrap();22502251		<ItemListIndex>::insert(collection_id, current_index);22522253		// Update balance2254		let new_balance = <Balance<T>>::get(collection_id, &owner.as_sub())2255			.checked_add(value)2256			.unwrap();2257		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2258	}22592260	fn add_token_index(2261		collection_id: CollectionId,2262		item_index: TokenId,2263		owner: &T::CrossAccountId,2264	) -> DispatchResult {2265		// add to account limit2266		if <AccountItemCount<T>>::contains_key(owner.as_sub()) {2267			// bound Owned tokens by a single address2268			let count = <AccountItemCount<T>>::get(owner.as_sub());2269			ensure!(2270				count < ACCOUNT_TOKEN_OWNERSHIP_LIMIT,2271				Error::<T>::AddressOwnershipLimitExceeded2272			);22732274			<AccountItemCount<T>>::insert(2275				owner.as_sub(),2276				count.checked_add(1).ok_or(Error::<T>::NumOverflow)?,2277			);2278		} else {2279			<AccountItemCount<T>>::insert(owner.as_sub(), 1);2280		}22812282		let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2283		if list_exists {2284			let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2285			let item_contains = list.contains(&item_index.clone());22862287			if !item_contains {2288				list.push(item_index);2289			}22902291			<AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2292		} else {2293			let itm = vec![item_index];2294			<AddressTokens<T>>::insert(collection_id, owner.as_sub(), itm);2295		}22962297		Ok(())2298	}22992300	fn remove_token_index(2301		collection_id: CollectionId,2302		item_index: TokenId,2303		owner: &T::CrossAccountId,2304	) -> DispatchResult {2305		// update counter2306		<AccountItemCount<T>>::insert(2307			owner.as_sub(),2308			<AccountItemCount<T>>::get(owner.as_sub())2309				.checked_sub(1)2310				.ok_or(Error::<T>::NumOverflow)?,2311		);23122313		let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2314		if list_exists {2315			let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2316			let item_contains = list.contains(&item_index.clone());23172318			if item_contains {2319				list.retain(|&item| item != item_index);2320				<AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2321			}2322		}23232324		Ok(())2325	}23262327	fn move_token_index(2328		collection_id: CollectionId,2329		item_index: TokenId,2330		old_owner: &T::CrossAccountId,2331		new_owner: &T::CrossAccountId,2332	) -> DispatchResult {2333		Self::remove_token_index(collection_id, item_index, old_owner)?;2334		Self::add_token_index(collection_id, item_index, new_owner)?;23352336		Ok(())2337	}2338}23392340sp_api::decl_runtime_apis! {2341	pub trait NftApi {2342		/// Used for ethereum integration2343		fn eth_contract_code(account: H160) -> Option<Vec<u8>>;2344	}2345}