git.delta.rocks / unique-network / refs/commits / 70da175fb9fe

difftreelog

source

pallets/nft/src/lib.rs70.3 KiBsourcehistory
1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]7#![cfg_attr(not(feature = "std"), no_std)]8#![allow(9	clippy::too_many_arguments,10	clippy::unnecessary_mut_passed,11	clippy::unused_unit12)]1314extern crate alloc;1516pub use serde::{Serialize, Deserialize};1718pub use frame_support::{19	construct_runtime, decl_event, decl_module, decl_storage, decl_error,20	dispatch::DispatchResult,21	ensure, fail, parameter_types,22	traits::{23		Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,24		Randomness, IsSubType, WithdrawReasons,25	},26	weights::{27		constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},28		DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,29		WeightToFeePolynomial, DispatchClass,30	},31	StorageValue, transactional,32};3334use frame_system::{self as system, ensure_signed, ensure_root};35use sp_core::H160;36use sp_std::vec;37use sp_runtime::sp_std::prelude::Vec;38use core::ops::{Deref, DerefMut};39use nft_data_structs::{40	MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, MAX_REFUNGIBLE_PIECES,41	AccessMode, ChainLimits, Collection, CreateItemData, CollectionLimits, CollectionId,42	CollectionMode, TokenId, SchemaVersion, SponsorshipState, Ownership, NftItemType,43	FungibleItemType, ReFungibleItemType,44};4546#[cfg(test)]47mod mock;4849#[cfg(test)]50mod tests;5152mod default_weights;53mod eth;54mod sponsorship;55pub use sponsorship::NftSponsorshipHandler;56pub use eth::sponsoring::NftEthSponsorshipHandler;5758pub use eth::NftErcSupport;59pub use eth::account::*;60use eth::erc::{ERC20Events, ERC721Events};6162#[cfg(feature = "runtime-benchmarks")]63mod benchmarking;6465pub trait WeightInfo {66	fn create_collection() -> Weight;67	fn destroy_collection() -> Weight;68	fn add_to_white_list() -> Weight;69	fn remove_from_white_list() -> Weight;70	fn set_public_access_mode() -> Weight;71	fn set_mint_permission() -> Weight;72	fn change_collection_owner() -> Weight;73	fn add_collection_admin() -> Weight;74	fn remove_collection_admin() -> Weight;75	fn set_collection_sponsor() -> Weight;76	fn confirm_sponsorship() -> Weight;77	fn remove_collection_sponsor() -> Weight;78	fn create_item(s: usize) -> Weight;79	fn burn_item() -> Weight;80	fn transfer() -> Weight;81	fn approve() -> Weight;82	fn transfer_from() -> Weight;83	fn set_offchain_schema() -> Weight;84	fn set_const_on_chain_schema() -> Weight;85	fn set_variable_on_chain_schema() -> Weight;86	fn set_variable_meta_data() -> Weight;87	fn enable_contract_sponsoring() -> Weight;88	fn set_schema_version() -> Weight;89	fn set_chain_limits() -> Weight;90	fn set_contract_sponsoring_rate_limit() -> Weight;91	fn set_variable_meta_data_sponsoring_rate_limit() -> Weight;92	fn toggle_contract_white_list() -> Weight;93	fn add_to_contract_white_list() -> Weight;94	fn remove_from_contract_white_list() -> Weight;95	fn set_collection_limits() -> Weight;96}9798decl_error! {99	/// Error for non-fungible-token module.100	pub enum Error for Module<T: Config> {101		/// Total collections bound exceeded.102		TotalCollectionsLimitExceeded,103		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.104		CollectionDecimalPointLimitExceeded,105		/// Collection name can not be longer than 63 char.106		CollectionNameLimitExceeded,107		/// Collection description can not be longer than 255 char.108		CollectionDescriptionLimitExceeded,109		/// Token prefix can not be longer than 15 char.110		CollectionTokenPrefixLimitExceeded,111		/// This collection does not exist.112		CollectionNotFound,113		/// Item not exists.114		TokenNotFound,115		/// Admin not found116		AdminNotFound,117		/// Arithmetic calculation overflow.118		NumOverflow,119		/// Account already has admin role.120		AlreadyAdmin,121		/// You do not own this collection.122		NoPermission,123		/// This address is not set as sponsor, use setCollectionSponsor first.124		ConfirmUnsetSponsorFail,125		/// Collection is not in mint mode.126		PublicMintingNotAllowed,127		/// Sender parameter and item owner must be equal.128		MustBeTokenOwner,129		/// Item balance not enough.130		TokenValueTooLow,131		/// Size of item is too large.132		NftSizeLimitExceeded,133		/// No approve found134		ApproveNotFound,135		/// Requested value more than approved.136		TokenValueNotEnough,137		/// Only approved addresses can call this method.138		ApproveRequired,139		/// Address is not in white list.140		AddresNotInWhiteList,141		/// Number of collection admins bound exceeded.142		CollectionAdminsLimitExceeded,143		/// Owned tokens by a single address bound exceeded.144		AddressOwnershipLimitExceeded,145		/// Length of items properties must be greater than 0.146		EmptyArgument,147		/// const_data exceeded data limit.148		TokenConstDataLimitExceeded,149		/// variable_data exceeded data limit.150		TokenVariableDataLimitExceeded,151		/// Not NFT item data used to mint in NFT collection.152		NotNftDataUsedToMintNftCollectionToken,153		/// Not Fungible item data used to mint in Fungible collection.154		NotFungibleDataUsedToMintFungibleCollectionToken,155		/// Not Re Fungible item data used to mint in Re Fungible collection.156		NotReFungibleDataUsedToMintReFungibleCollectionToken,157		/// Unexpected collection type.158		UnexpectedCollectionType,159		/// Can't store metadata in fungible tokens.160		CantStoreMetadataInFungibleTokens,161		/// Collection token limit exceeded162		CollectionTokenLimitExceeded,163		/// Account token limit exceeded per collection164		AccountTokenLimitExceeded,165		/// Collection limit bounds per collection exceeded166		CollectionLimitBoundsExceeded,167		/// Tried to enable permissions which are only permitted to be disabled168		OwnerPermissionsCantBeReverted,169		/// Schema data size limit bound exceeded170		SchemaDataLimitExceeded,171		/// Maximum refungibility exceeded172		WrongRefungiblePieces,173		/// createRefungible should be called with one owner174		BadCreateRefungibleCall,175		/// Gas limit exceeded176		OutOfGas,177		/// Collection settings not allowing items transferring178		TransferNotAllowed,179	}180}181182#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]183pub struct CollectionHandle<T: Config> {184	pub id: CollectionId,185	collection: Collection<T>,186	recorder: pallet_evm_coder_substrate::SubstrateRecorder<T>,187}188impl<T: Config> CollectionHandle<T> {189	pub fn get_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {190		<CollectionById<T>>::get(id).map(|collection| Self {191			id,192			collection,193			recorder: pallet_evm_coder_substrate::SubstrateRecorder::new(194				eth::collection_id_to_address(id),195				gas_limit,196			),197		})198	}199	pub fn get(id: CollectionId) -> Option<Self> {200		Self::get_with_gas_limit(id, u64::MAX)201	}202	pub fn log(&self, log: impl evm_coder::ToLog) -> DispatchResult {203		self.recorder.log_sub(log)204	}205	fn consume_gas(&self, gas: u64) -> DispatchResult {206		self.recorder.consume_gas_sub(gas)207	}208	pub fn submit_logs(self) -> DispatchResult {209		self.recorder.submit_logs()210	}211	pub fn save(self) -> DispatchResult {212		self.recorder.submit_logs()?;213		<CollectionById<T>>::insert(self.id, self.collection);214		Ok(())215	}216}217impl<T: Config> Deref for CollectionHandle<T> {218	type Target = Collection<T>;219220	fn deref(&self) -> &Self::Target {221		&self.collection222	}223}224225impl<T: Config> DerefMut for CollectionHandle<T> {226	fn deref_mut(&mut self) -> &mut Self::Target {227		&mut self.collection228	}229}230231pub trait Config: system::Config + pallet_evm_coder_substrate::Config + Sized {232	type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;233234	/// Weight information for extrinsics in this pallet.235	type WeightInfo: WeightInfo;236237	type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;238	type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;239240	type CrossAccountId: CrossAccountId<Self::AccountId>;241	type Currency: Currency<Self::AccountId>;242	type CollectionCreationPrice: Get<243		<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,244	>;245	type TreasuryAccountId: Get<Self::AccountId>;246}247248// # Used definitions249//250// ## User control levels251//252// chain-controlled - key is uncontrolled by user253//                    i.e autoincrementing index254//                    can use non-cryptographic hash255// real - key is controlled by user256//        but it is hard to generate enough colliding values, i.e owner of signed txs257//        can use non-cryptographic hash258// controlled - key is completly controlled by users259//              i.e maps with mutable keys260//              should use cryptographic hash261//262// ## User control level downgrade reasons263//264// ?1 - chain-controlled -> controlled265//      collections/tokens can be destroyed, resulting in massive holes266// ?2 - chain-controlled -> controlled267//      same as ?1, but can be only added, resulting in easier exploitation268// ?3 - real -> controlled269//      no confirmation required, so addresses can be easily generated270decl_storage! {271	trait Store for Module<T: Config> as Nft {272273		//#region Private members274		/// Id of next collection275		CreatedCollectionCount: u32;276		/// Used for migrations277		ChainVersion: u64;278		/// Id of last collection token279		/// Collection id (controlled?1)280		ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;281		//#endregion282283		//#region Chain limits struct284		pub ChainLimit get(fn chain_limit) config(): ChainLimits;285		//#endregion286287		//#region Bound counters288		/// Amount of collections destroyed, used for total amount tracking with289		/// CreatedCollectionCount290		DestroyedCollectionCount: u32;291		/// Total amount of account owned tokens (NFTs + RFTs + unique fungibles)292		/// Account id (real)293		pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;294		//#endregion295296		//#region Basic collections297		/// Collection info298		/// Collection id (controlled?1)299		pub CollectionById get(fn collection_id) config(): map hasher(blake2_128_concat) CollectionId => Option<Collection<T>> = None;300		/// List of collection admins301		/// Collection id (controlled?2)302		pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec<T::CrossAccountId>;303		/// Whitelisted collection users304		/// Collection id (controlled?2), user id (controlled?3)305		pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool;306		//#endregion307308		/// How many of collection items user have309		/// Collection id (controlled?2), account id (real)310		pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128;311312		/// Amount of items which spender can transfer out of owners account (via transferFrom)313		/// Collection id (controlled?2), (token id (controlled ?2) + owner account id (real) + spender account id (controlled?3))314		/// TODO: Off chain worker should remove from this map when token gets removed315		pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128;316317		//#region Item collections318		/// Collection id (controlled?2), token id (controlled?1)319		pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<NftItemType<T::CrossAccountId>>;320		/// Collection id (controlled?2), owner (controlled?2)321		pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType;322		/// Collection id (controlled?2), token id (controlled?1)323		pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<ReFungibleItemType<T::CrossAccountId>>;324		//#endregion325326		//#region Index list327		/// Collection id (controlled?2), tokens owner (controlled?2)328		pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;329		//#endregion330331		//#region Tokens transfer rate limit baskets332		/// (Collection id (controlled?2), who created (real))333		/// TODO: Off chain worker should remove from this map when collection gets removed334		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;335		/// Collection id (controlled?2), token id (controlled?2)336		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;337		/// Collection id (controlled?2), owning user (real)338		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;339		/// Collection id (controlled?2), token id (controlled?2)340		pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;341		//#endregion342343		/// Variable metadata sponsoring344		/// Collection id (controlled?2), token id (controlled?2)345		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;346	}347	add_extra_genesis {348		build(|config: &GenesisConfig<T>| {349			// Modification of storage350			for (_num, _c) in &config.collection_id {351				<Module<T>>::init_collection(_c);352			}353354			for (_num, _c, _i) in &config.nft_item_id {355				<Module<T>>::init_nft_token(*_c, _i);356			}357358			for (collection_id, account_id, fungible_item) in &config.fungible_item_id {359				<Module<T>>::init_fungible_token(*collection_id, &T::CrossAccountId::from_sub(account_id.clone()), fungible_item);360			}361362			for (_num, _c, _i) in &config.refungible_item_id {363				<Module<T>>::init_refungible_token(*_c, _i);364			}365		})366	}367}368369decl_event!(370	pub enum Event<T>371	where372		AccountId = <T as frame_system::Config>::AccountId,373		CrossAccountId = <T as Config>::CrossAccountId,374	{375		/// New collection was created376		///377		/// # Arguments378		///379		/// * collection_id: Globally unique identifier of newly created collection.380		///381		/// * mode: [CollectionMode] converted into u8.382		///383		/// * account_id: Collection owner.384		CollectionCreated(CollectionId, u8, AccountId),385386		/// New item was created.387		///388		/// # Arguments389		///390		/// * collection_id: Id of the collection where item was created.391		///392		/// * item_id: Id of an item. Unique within the collection.393		///394		/// * recipient: Owner of newly created item395		ItemCreated(CollectionId, TokenId, CrossAccountId),396397		/// Collection item was burned.398		///399		/// # Arguments400		///401		/// collection_id.402		///403		/// item_id: Identifier of burned NFT.404		ItemDestroyed(CollectionId, TokenId),405406		/// Item was transferred407		///408		/// * collection_id: Id of collection to which item is belong409		///410		/// * item_id: Id of an item411		///412		/// * sender: Original owner of item413		///414		/// * recipient: New owner of item415		///416		/// * amount: Always 1 for NFT417		Transfer(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),418419		/// * collection_id420		///421		/// * item_id422		///423		/// * sender424		///425		/// * spender426		///427		/// * amount428		Approved(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),429	}430);431432decl_module! {433	pub struct Module<T: Config> for enum Call434	where435		origin: T::Origin436	{437		fn deposit_event() = default;438		type Error = Error<T>;439440		fn on_initialize(_now: T::BlockNumber) -> Weight {441			0442		}443444		/// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.445		///446		/// # Permissions447		///448		/// * Anyone.449		///450		/// # Arguments451		///452		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.453		///454		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.455		///456		/// * token_prefix: UTF-8 string with token prefix.457		///458		/// * mode: [CollectionMode] collection type and type dependent data.459		// returns collection ID460		#[weight = <T as Config>::WeightInfo::create_collection()]461		#[transactional]462		pub fn create_collection(origin,463								 collection_name: Vec<u16>,464								 collection_description: Vec<u16>,465								 token_prefix: Vec<u8>,466								 mode: CollectionMode) -> DispatchResult {467468			// Anyone can create a collection469			let who = ensure_signed(origin)?;470471			// Take a (non-refundable) deposit of collection creation472			let mut imbalance = <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();473			imbalance.subsume(<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(474				&T::TreasuryAccountId::get(),475				T::CollectionCreationPrice::get(),476			));477			<T as Config>::Currency::settle(478				&who,479				imbalance,480				WithdrawReasons::TRANSFER,481				ExistenceRequirement::KeepAlive,482			).map_err(|_| Error::<T>::NoPermission)?;483484			let decimal_points = match mode {485				CollectionMode::Fungible(points) => points,486				_ => 0487			};488489			let chain_limit = ChainLimit::get();490491			let created_count = CreatedCollectionCount::get();492			let destroyed_count = DestroyedCollectionCount::get();493494			// bound Total number of collections495			ensure!(created_count - destroyed_count < chain_limit.collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);496497			// check params498			ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);499			ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);500			ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);501			ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);502503			// Generate next collection ID504			let next_id = created_count505				.checked_add(1)506				.ok_or(Error::<T>::NumOverflow)?;507508			CreatedCollectionCount::put(next_id);509510			let limits = CollectionLimits {511				sponsored_data_size: chain_limit.custom_data_limit,512				..Default::default()513			};514515			// Create new collection516			let new_collection = Collection {517				owner: who.clone(),518				name: collection_name,519				mode: mode.clone(),520				mint_mode: false,521				access: AccessMode::Normal,522				description: collection_description,523				decimal_points,524				token_prefix,525				offchain_schema: Vec::new(),526				schema_version: SchemaVersion::ImageURL,527				sponsorship: SponsorshipState::Disabled,528				variable_on_chain_schema: Vec::new(),529				const_on_chain_schema: Vec::new(),530				limits,531				transfers_enabled: true,532			};533534			// Add new collection to map535			<CollectionById<T>>::insert(next_id, new_collection);536537			// call event538			Self::deposit_event(RawEvent::CollectionCreated(next_id, mode.id(), who));539540			Ok(())541		}542543		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.544		///545		/// # Permissions546		///547		/// * Collection Owner.548		///549		/// # Arguments550		///551		/// * collection_id: collection to destroy.552		#[weight = <T as Config>::WeightInfo::destroy_collection()]553		#[transactional]554		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {555556			let sender = ensure_signed(origin)?;557			let collection = Self::get_collection(collection_id)?;558			Self::check_owner_permissions(&collection, &sender)?;559			if !collection.limits.owner_can_destroy {560				fail!(Error::<T>::NoPermission);561			}562563			<AddressTokens<T>>::remove_prefix(collection_id, None);564			<Allowances<T>>::remove_prefix(collection_id, None);565			<Balance<T>>::remove_prefix(collection_id, None);566			<ItemListIndex>::remove(collection_id);567			<AdminList<T>>::remove(collection_id);568			<CollectionById<T>>::remove(collection_id);569			<WhiteList<T>>::remove_prefix(collection_id, None);570571			<NftItemList<T>>::remove_prefix(collection_id, None);572			<FungibleItemList<T>>::remove_prefix(collection_id, None);573			<ReFungibleItemList<T>>::remove_prefix(collection_id, None);574575			<NftTransferBasket<T>>::remove_prefix(collection_id, None);576			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);577			<ReFungibleTransferBasket<T>>::remove_prefix(collection_id, None);578579			<VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);580581			DestroyedCollectionCount::put(DestroyedCollectionCount::get()582				.checked_add(1)583				.ok_or(Error::<T>::NumOverflow)?);584585			Ok(())586		}587588		/// Add an address to white list.589		///590		/// # Permissions591		///592		/// * Collection Owner593		/// * Collection Admin594		///595		/// # Arguments596		///597		/// * collection_id.598		///599		/// * address.600		#[weight = <T as Config>::WeightInfo::add_to_white_list()]601		#[transactional]602		pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{603604			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);605			let collection = Self::get_collection(collection_id)?;606607			Self::toggle_white_list_internal(608				&sender,609				&collection,610				&address,611				true,612			)?;613614			Ok(())615		}616617		/// Remove an address from white list.618		///619		/// # Permissions620		///621		/// * Collection Owner622		/// * Collection Admin623		///624		/// # Arguments625		///626		/// * collection_id.627		///628		/// * address.629		#[weight = <T as Config>::WeightInfo::remove_from_white_list()]630		#[transactional]631		pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{632633			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);634			let collection = Self::get_collection(collection_id)?;635636			Self::toggle_white_list_internal(637				&sender,638				&collection,639				&address,640				false,641			)?;642643			Ok(())644		}645646		/// Toggle between normal and white list access for the methods with access for `Anyone`.647		///648		/// # Permissions649		///650		/// * Collection Owner.651		///652		/// # Arguments653		///654		/// * collection_id.655		///656		/// * mode: [AccessMode]657		#[weight = <T as Config>::WeightInfo::set_public_access_mode()]658		#[transactional]659		pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult660		{661			let sender = ensure_signed(origin)?;662663			let mut target_collection = Self::get_collection(collection_id)?;664			Self::check_owner_permissions(&target_collection, &sender)?;665			target_collection.access = mode;666			target_collection.save()667		}668669		/// Allows Anyone to create tokens if:670		/// * White List is enabled, and671		/// * Address is added to white list, and672		/// * This method was called with True parameter673		///674		/// # Permissions675		/// * Collection Owner676		///677		/// # Arguments678		///679		/// * collection_id.680		///681		/// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.682		#[weight = <T as Config>::WeightInfo::set_mint_permission()]683		#[transactional]684		pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult685		{686			let sender = ensure_signed(origin)?;687688			let mut target_collection = Self::get_collection(collection_id)?;689			Self::check_owner_permissions(&target_collection, &sender)?;690			target_collection.mint_mode = mint_permission;691			target_collection.save()692		}693694		/// Change the owner of the collection.695		///696		/// # Permissions697		///698		/// * Collection Owner.699		///700		/// # Arguments701		///702		/// * collection_id.703		///704		/// * new_owner.705		#[weight = <T as Config>::WeightInfo::change_collection_owner()]706		#[transactional]707		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {708709			let sender = ensure_signed(origin)?;710			let mut target_collection = Self::get_collection(collection_id)?;711			Self::check_owner_permissions(&target_collection, &sender)?;712			target_collection.owner = new_owner;713			target_collection.save()714		}715716		/// Adds an admin of the Collection.717		/// 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.718		///719		/// # Permissions720		///721		/// * Collection Owner.722		/// * Collection Admin.723		///724		/// # Arguments725		///726		/// * collection_id: ID of the Collection to add admin for.727		///728		/// * new_admin_id: Address of new admin to add.729		#[weight = <T as Config>::WeightInfo::add_collection_admin()]730		#[transactional]731		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {732			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);733			let collection = Self::get_collection(collection_id)?;734			Self::check_owner_or_admin_permissions(&collection, &sender)?;735			let mut admin_arr = <AdminList<T>>::get(collection_id);736737			match admin_arr.binary_search(&new_admin_id) {738				Ok(_) => {},739				Err(idx) => {740					let limits = ChainLimit::get();741					ensure!(admin_arr.len() < limits.collections_admins_limit as usize, Error::<T>::CollectionAdminsLimitExceeded);742					admin_arr.insert(idx, new_admin_id);743					<AdminList<T>>::insert(collection_id, admin_arr);744				}745			}746			Ok(())747		}748749		/// 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.750		///751		/// # Permissions752		///753		/// * Collection Owner.754		/// * Collection Admin.755		///756		/// # Arguments757		///758		/// * collection_id: ID of the Collection to remove admin for.759		///760		/// * account_id: Address of admin to remove.761		#[weight = <T as Config>::WeightInfo::remove_collection_admin()]762		#[transactional]763		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {764			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);765			let collection = Self::get_collection(collection_id)?;766			Self::check_owner_or_admin_permissions(&collection, &sender)?;767			let mut admin_arr = <AdminList<T>>::get(collection_id);768769			if let Ok(idx) = admin_arr.binary_search(&account_id) {770				admin_arr.remove(idx);771				<AdminList<T>>::insert(collection_id, admin_arr);772			}773			Ok(())774		}775776		/// # Permissions777		///778		/// * Collection Owner779		///780		/// # Arguments781		///782		/// * collection_id.783		///784		/// * new_sponsor.785		#[weight = <T as Config>::WeightInfo::set_collection_sponsor()]786		#[transactional]787		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {788			let sender = ensure_signed(origin)?;789			let mut target_collection = Self::get_collection(collection_id)?;790			Self::check_owner_permissions(&target_collection, &sender)?;791792			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);793			target_collection.save()794		}795796		/// # Permissions797		///798		/// * Sponsor.799		///800		/// # Arguments801		///802		/// * collection_id.803		#[weight = <T as Config>::WeightInfo::confirm_sponsorship()]804		#[transactional]805		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {806			let sender = ensure_signed(origin)?;807808			let mut target_collection = Self::get_collection(collection_id)?;809			ensure!(810				target_collection.sponsorship.pending_sponsor() == Some(&sender),811				Error::<T>::ConfirmUnsetSponsorFail812			);813814			target_collection.sponsorship = SponsorshipState::Confirmed(sender);815			target_collection.save()816		}817818		/// Switch back to pay-per-own-transaction model.819		///820		/// # Permissions821		///822		/// * Collection owner.823		///824		/// # Arguments825		///826		/// * collection_id.827		#[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]828		#[transactional]829		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {830			let sender = ensure_signed(origin)?;831832			let mut target_collection = Self::get_collection(collection_id)?;833			Self::check_owner_permissions(&target_collection, &sender)?;834835			target_collection.sponsorship = SponsorshipState::Disabled;836			target_collection.save()837		}838839		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.840		///841		/// # Permissions842		///843		/// * Collection Owner.844		/// * Collection Admin.845		/// * Anyone if846		///     * White List is enabled, and847		///     * Address is added to white list, and848		///     * MintPermission is enabled (see SetMintPermission method)849		///850		/// # Arguments851		///852		/// * collection_id: ID of the collection.853		///854		/// * owner: Address, initial owner of the NFT.855		///856		/// * data: Token data to store on chain.857		// #[weight =858		// (130_000_000 as Weight)859		// .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))860		// .saturating_add(RocksDbWeight::get().reads(10 as Weight))861		// .saturating_add(RocksDbWeight::get().writes(8 as Weight))]862863		#[weight = <T as Config>::WeightInfo::create_item(data.data_size())]864		#[transactional]865		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResult {866			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);867			let collection = Self::get_collection(collection_id)?;868869			Self::create_item_internal(&sender, &collection, &owner, data)?;870871			collection.submit_logs()872		}873874		/// This method creates multiple items in a collection created with CreateCollection method.875		///876		/// # Permissions877		///878		/// * Collection Owner.879		/// * Collection Admin.880		/// * Anyone if881		///     * White List is enabled, and882		///     * Address is added to white list, and883		///     * MintPermission is enabled (see SetMintPermission method)884		///885		/// # Arguments886		///887		/// * collection_id: ID of the collection.888		///889		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].890		///891		/// * owner: Address, initial owner of the NFT.892		#[weight = <T as Config>::WeightInfo::create_item(items_data.iter()893							   .map(|data| { data.data_size() })894							   .sum())]895		#[transactional]896		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResult {897898			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);899			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);900			let collection = Self::get_collection(collection_id)?;901902			Self::create_multiple_items_internal(&sender, &collection, &owner, items_data)?;903904			collection.submit_logs()905		}906907		// TODO! transaction weight908909		/// Set transfers_enabled value for particular collection910		///911		/// # Permissions912		///913		/// * Collection Owner.914		///915		/// # Arguments916		///917		/// * collection_id: ID of the collection.918		///919		/// * value: New flag value.920		#[weight = <T as Config>::WeightInfo::burn_item()]921		#[transactional]922		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {923924			let sender = ensure_signed(origin)?;925			let mut target_collection = Self::get_collection(collection_id)?;926927			Self::check_owner_permissions(&target_collection, &sender)?;928929			target_collection.transfers_enabled = value;930			target_collection.save()931		}932933		/// Destroys a concrete instance of NFT.934		///935		/// # Permissions936		///937		/// * Collection Owner.938		/// * Collection Admin.939		/// * Current NFT Owner.940		///941		/// # Arguments942		///943		/// * collection_id: ID of the collection.944		///945		/// * item_id: ID of NFT to burn.946		#[weight = <T as Config>::WeightInfo::burn_item()]947		#[transactional]948		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {949950			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);951			let target_collection = Self::get_collection(collection_id)?;952953			Self::burn_item_internal(&sender, &target_collection, item_id, value)?;954955			target_collection.submit_logs()956		}957958		/// Change ownership of the token.959		///960		/// # Permissions961		///962		/// * Collection Owner963		/// * Collection Admin964		/// * Current NFT owner965		///966		/// # Arguments967		///968		/// * recipient: Address of token recipient.969		///970		/// * collection_id.971		///972		/// * item_id: ID of the item973		///     * Non-Fungible Mode: Required.974		///     * Fungible Mode: Ignored.975		///     * Re-Fungible Mode: Required.976		///977		/// * value: Amount to transfer.978		///     * Non-Fungible Mode: Ignored979		///     * Fungible Mode: Must specify transferred amount980		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)981		#[weight = <T as Config>::WeightInfo::transfer()]982		#[transactional]983		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {984			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);985			let collection = Self::get_collection(collection_id)?;986987			Self::transfer_internal(&sender, &recipient, &collection, item_id, value)?;988989			collection.submit_logs()990		}991992		/// Set, change, or remove approved address to transfer the ownership of the NFT.993		///994		/// # Permissions995		///996		/// * Collection Owner997		/// * Collection Admin998		/// * Current NFT owner999		///1000		/// # Arguments1001		///1002		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1003		///1004		/// * collection_id.1005		///1006		/// * item_id: ID of the item.1007		#[weight = <T as Config>::WeightInfo::approve()]1008		#[transactional]1009		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {1010			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1011			let collection = Self::get_collection(collection_id)?;10121013			Self::approve_internal(&sender, &spender, &collection, item_id, amount)?;10141015			collection.submit_logs()1016		}10171018		/// 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.1019		///1020		/// # Permissions1021		/// * Collection Owner1022		/// * Collection Admin1023		/// * Current NFT owner1024		/// * Address approved by current NFT owner1025		///1026		/// # Arguments1027		///1028		/// * from: Address that owns token.1029		///1030		/// * recipient: Address of token recipient.1031		///1032		/// * collection_id.1033		///1034		/// * item_id: ID of the item.1035		///1036		/// * value: Amount to transfer.1037		#[weight = <T as Config>::WeightInfo::transfer_from()]1038		#[transactional]1039		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {1040			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1041			let collection = Self::get_collection(collection_id)?;10421043			Self::transfer_from_internal(&sender, &from, &recipient, &collection, item_id, value)?;10441045			collection.submit_logs()1046		}1047		// #[weight = 0]1048		//     // let no_perm_mes = "You do not have permissions to modify this collection";1049		//     // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1050		//     // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1051		//     // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);10521053		//     // // on_nft_received  call10541055		//     // Self::transfer(origin, collection_id, item_id, new_owner)?;10561057		//     Ok(())1058		// }10591060		/// Set off-chain data schema.1061		///1062		/// # Permissions1063		///1064		/// * Collection Owner1065		/// * Collection Admin1066		///1067		/// # Arguments1068		///1069		/// * collection_id.1070		///1071		/// * schema: String representing the offchain data schema.1072		#[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1073		#[transactional]1074		pub fn set_variable_meta_data (1075			origin,1076			collection_id: CollectionId,1077			item_id: TokenId,1078			data: Vec<u8>1079		) -> DispatchResult {1080			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);10811082			let collection = Self::get_collection(collection_id)?;10831084			Self::set_variable_meta_data_internal(&sender, &collection, item_id, data)?;10851086			Ok(())1087		}10881089		/// Set schema standard1090		/// ImageURL1091		/// Unique1092		///1093		/// # Permissions1094		///1095		/// * Collection Owner1096		/// * Collection Admin1097		///1098		/// # Arguments1099		///1100		/// * collection_id.1101		///1102		/// * schema: SchemaVersion: enum1103		#[weight = <T as Config>::WeightInfo::set_schema_version()]1104		#[transactional]1105		pub fn set_schema_version(1106			origin,1107			collection_id: CollectionId,1108			version: SchemaVersion1109		) -> DispatchResult {1110			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1111			let mut target_collection = Self::get_collection(collection_id)?;1112			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;1113			target_collection.schema_version = version;1114			target_collection.save()1115		}11161117		/// Set off-chain data schema.1118		///1119		/// # Permissions1120		///1121		/// * Collection Owner1122		/// * Collection Admin1123		///1124		/// # Arguments1125		///1126		/// * collection_id.1127		///1128		/// * schema: String representing the offchain data schema.1129		#[weight = <T as Config>::WeightInfo::set_offchain_schema()]1130		#[transactional]1131		pub fn set_offchain_schema(1132			origin,1133			collection_id: CollectionId,1134			schema: Vec<u8>1135		) -> DispatchResult {1136			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1137			let mut target_collection = Self::get_collection(collection_id)?;1138			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11391140			// check schema limit1141			ensure!(schema.len() as u32 <= ChainLimit::get().offchain_schema_limit, "");11421143			target_collection.offchain_schema = schema;1144			target_collection.save()1145		}11461147		/// Set const on-chain data schema.1148		///1149		/// # Permissions1150		///1151		/// * Collection Owner1152		/// * Collection Admin1153		///1154		/// # Arguments1155		///1156		/// * collection_id.1157		///1158		/// * schema: String representing the const on-chain data schema.1159		#[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1160		#[transactional]1161		pub fn set_const_on_chain_schema (1162			origin,1163			collection_id: CollectionId,1164			schema: Vec<u8>1165		) -> DispatchResult {1166			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1167			let mut target_collection = Self::get_collection(collection_id)?;1168			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11691170			// check schema limit1171			ensure!(schema.len() as u32 <= ChainLimit::get().const_on_chain_schema_limit, "");11721173			target_collection.const_on_chain_schema = schema;1174			target_collection.save()1175		}11761177		/// Set variable on-chain data schema.1178		///1179		/// # Permissions1180		///1181		/// * Collection Owner1182		/// * Collection Admin1183		///1184		/// # Arguments1185		///1186		/// * collection_id.1187		///1188		/// * schema: String representing the variable on-chain data schema.1189		#[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1190		#[transactional]1191		pub fn set_variable_on_chain_schema (1192			origin,1193			collection_id: CollectionId,1194			schema: Vec<u8>1195		) -> DispatchResult {1196			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1197			let mut target_collection = Self::get_collection(collection_id)?;1198			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11991200			// check schema limit1201			ensure!(schema.len() as u32 <= ChainLimit::get().variable_on_chain_schema_limit, "");12021203			target_collection.variable_on_chain_schema = schema;1204			target_collection.save()1205		}12061207		// Sudo permissions function1208		#[weight = <T as Config>::WeightInfo::set_chain_limits()]1209		#[transactional]1210		pub fn set_chain_limits(1211			origin,1212			limits: ChainLimits1213		) -> DispatchResult {12141215			#[cfg(not(feature = "runtime-benchmarks"))]1216			ensure_root(origin)?;12171218			<ChainLimit>::put(limits);1219			Ok(())1220		}12211222		#[weight = <T as Config>::WeightInfo::set_collection_limits()]1223		#[transactional]1224		pub fn set_collection_limits(1225			origin,1226			collection_id: u32,1227			new_limits: CollectionLimits<T::BlockNumber>,1228		) -> DispatchResult {1229			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1230			let mut target_collection = Self::get_collection(collection_id)?;1231			Self::check_owner_permissions(&target_collection, sender.as_sub())?;1232			let old_limits = &target_collection.limits;1233			let chain_limits = ChainLimit::get();12341235			// collection bounds1236			ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1237				new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP &&1238				new_limits.sponsored_data_size <= chain_limits.custom_data_limit,1239				Error::<T>::CollectionLimitBoundsExceeded);12401241			// token_limit   check  prev1242			ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1243			ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);12441245			ensure!(1246				(old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1247				(old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1248				Error::<T>::OwnerPermissionsCantBeReverted,1249			);12501251			target_collection.limits = new_limits;12521253			target_collection.save()1254		}1255	}1256}12571258impl<T: Config> Module<T> {1259	pub fn create_item_internal(1260		sender: &T::CrossAccountId,1261		collection: &CollectionHandle<T>,1262		owner: &T::CrossAccountId,1263		data: CreateItemData,1264	) -> DispatchResult {1265		Self::can_create_items_in_collection(collection, sender, owner, 1)?;1266		Self::validate_create_item_args(collection, &data)?;1267		Self::create_item_no_validation(collection, owner, data)?;12681269		Ok(())1270	}12711272	pub fn transfer_internal(1273		sender: &T::CrossAccountId,1274		recipient: &T::CrossAccountId,1275		target_collection: &CollectionHandle<T>,1276		item_id: TokenId,1277		value: u128,1278	) -> DispatchResult {1279		target_collection.consume_gas(2000000)?;1280		// Limits check1281		Self::is_correct_transfer(target_collection, recipient)?;12821283		// Transfer permissions check1284		ensure!(1285			Self::is_item_owner(sender, target_collection, item_id)1286				|| Self::is_owner_or_admin_permissions(target_collection, sender),1287			Error::<T>::NoPermission1288		);12891290		if target_collection.access == AccessMode::WhiteList {1291			Self::check_white_list(target_collection, sender)?;1292			Self::check_white_list(target_collection, recipient)?;1293		}12941295		match target_collection.mode {1296			CollectionMode::NFT => Self::transfer_nft(1297				target_collection,1298				item_id,1299				sender.clone(),1300				recipient.clone(),1301			)?,1302			CollectionMode::Fungible(_) => {1303				Self::transfer_fungible(target_collection, value, sender, recipient)?1304			}1305			CollectionMode::ReFungible => Self::transfer_refungible(1306				target_collection,1307				item_id,1308				value,1309				sender.clone(),1310				recipient.clone(),1311			)?,1312			_ => (),1313		};13141315		Self::deposit_event(RawEvent::Transfer(1316			target_collection.id,1317			item_id,1318			sender.clone(),1319			recipient.clone(),1320			value,1321		));13221323		Ok(())1324	}13251326	pub fn approve_internal(1327		sender: &T::CrossAccountId,1328		spender: &T::CrossAccountId,1329		collection: &CollectionHandle<T>,1330		item_id: TokenId,1331		amount: u128,1332	) -> DispatchResult {1333		collection.consume_gas(2000000)?;1334		Self::token_exists(collection, item_id)?;13351336		// Transfer permissions check1337		let bypasses_limits = collection.limits.owner_can_transfer1338			&& Self::is_owner_or_admin_permissions(collection, sender);13391340		let allowance_limit = if bypasses_limits {1341			None1342		} else if let Some(amount) = Self::owned_amount(sender, collection, item_id) {1343			Some(amount)1344		} else {1345			fail!(Error::<T>::NoPermission);1346		};13471348		if collection.access == AccessMode::WhiteList {1349			Self::check_white_list(collection, sender)?;1350			Self::check_white_list(collection, spender)?;1351		}13521353		let allowance: u128 = amount1354			.checked_add(<Allowances<T>>::get(1355				collection.id,1356				(item_id, sender.as_sub(), spender.as_sub()),1357			))1358			.ok_or(Error::<T>::NumOverflow)?;1359		if let Some(limit) = allowance_limit {1360			ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1361		}1362		<Allowances<T>>::insert(1363			collection.id,1364			(item_id, sender.as_sub(), spender.as_sub()),1365			allowance,1366		);13671368		if matches!(collection.mode, CollectionMode::NFT) {1369			// TODO: NFT: only one owner may exist for token in ERC7211370			collection.log(ERC721Events::Approval {1371				owner: *sender.as_eth(),1372				approved: *spender.as_eth(),1373				token_id: item_id.into(),1374			})?;1375		}13761377		if matches!(collection.mode, CollectionMode::Fungible(_)) {1378			// TODO: NFT: only one owner may exist for token in ERC201379			collection.log(ERC20Events::Approval {1380				owner: *sender.as_eth(),1381				spender: *spender.as_eth(),1382				value: allowance.into(),1383			})?;1384		}13851386		Self::deposit_event(RawEvent::Approved(1387			collection.id,1388			item_id,1389			sender.clone(),1390			spender.clone(),1391			allowance,1392		));1393		Ok(())1394	}13951396	pub fn transfer_from_internal(1397		sender: &T::CrossAccountId,1398		from: &T::CrossAccountId,1399		recipient: &T::CrossAccountId,1400		collection: &CollectionHandle<T>,1401		item_id: TokenId,1402		amount: u128,1403	) -> DispatchResult {1404		collection.consume_gas(2000000)?;1405		// Check approval1406		let approval: u128 =1407			<Allowances<T>>::get(collection.id, (item_id, from.as_sub(), sender.as_sub()));14081409		// Limits check1410		Self::is_correct_transfer(collection, recipient)?;14111412		// Transfer permissions check1413		ensure!(1414			approval >= amount1415				|| (collection.limits.owner_can_transfer1416					&& Self::is_owner_or_admin_permissions(collection, sender)),1417			Error::<T>::NoPermission1418		);14191420		if collection.access == AccessMode::WhiteList {1421			Self::check_white_list(collection, sender)?;1422			Self::check_white_list(collection, recipient)?;1423		}14241425		// Reduce approval by transferred amount or remove if remaining approval drops to 01426		let allowance = approval.saturating_sub(amount);1427		if allowance > 0 {1428			<Allowances<T>>::insert(1429				collection.id,1430				(item_id, from.as_sub(), sender.as_sub()),1431				allowance,1432			);1433		} else {1434			<Allowances<T>>::remove(collection.id, (item_id, from.as_sub(), sender.as_sub()));1435		}14361437		match collection.mode {1438			CollectionMode::NFT => {1439				Self::transfer_nft(collection, item_id, from.clone(), recipient.clone())?1440			}1441			CollectionMode::Fungible(_) => {1442				Self::transfer_fungible(collection, amount, from, recipient)?1443			}1444			CollectionMode::ReFungible => Self::transfer_refungible(1445				collection,1446				item_id,1447				amount,1448				from.clone(),1449				recipient.clone(),1450			)?,1451			_ => (),1452		};14531454		if matches!(collection.mode, CollectionMode::Fungible(_)) {1455			collection.log(ERC20Events::Approval {1456				owner: *from.as_eth(),1457				spender: *sender.as_eth(),1458				value: allowance.into(),1459			})?;1460		}14611462		Ok(())1463	}14641465	pub fn set_variable_meta_data_internal(1466		sender: &T::CrossAccountId,1467		collection: &CollectionHandle<T>,1468		item_id: TokenId,1469		data: Vec<u8>,1470	) -> DispatchResult {1471		Self::token_exists(collection, item_id)?;14721473		ensure!(1474			ChainLimit::get().custom_data_limit >= data.len() as u32,1475			Error::<T>::TokenVariableDataLimitExceeded1476		);14771478		// Modify permissions check1479		ensure!(1480			Self::is_item_owner(sender, collection, item_id)1481				|| Self::is_owner_or_admin_permissions(collection, sender),1482			Error::<T>::NoPermission1483		);14841485		match collection.mode {1486			CollectionMode::NFT => Self::set_nft_variable_data(collection, item_id, data)?,1487			CollectionMode::ReFungible => {1488				Self::set_re_fungible_variable_data(collection, item_id, data)?1489			}1490			CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1491			_ => fail!(Error::<T>::UnexpectedCollectionType),1492		};14931494		Ok(())1495	}14961497	pub fn create_multiple_items_internal(1498		sender: &T::CrossAccountId,1499		collection: &CollectionHandle<T>,1500		owner: &T::CrossAccountId,1501		items_data: Vec<CreateItemData>,1502	) -> DispatchResult {1503		Self::can_create_items_in_collection(collection, sender, owner, items_data.len() as u32)?;15041505		for data in &items_data {1506			Self::validate_create_item_args(collection, data)?;1507		}1508		for data in &items_data {1509			Self::create_item_no_validation(collection, owner, data.clone())?;1510		}15111512		Ok(())1513	}15141515	pub fn burn_item_internal(1516		sender: &T::CrossAccountId,1517		collection: &CollectionHandle<T>,1518		item_id: TokenId,1519		value: u128,1520	) -> DispatchResult {1521		ensure!(1522			Self::is_item_owner(sender, collection, item_id)1523				|| (collection.limits.owner_can_transfer1524					&& Self::is_owner_or_admin_permissions(collection, sender)),1525			Error::<T>::NoPermission1526		);15271528		if collection.access == AccessMode::WhiteList {1529			Self::check_white_list(collection, sender)?;1530		}15311532		match collection.mode {1533			CollectionMode::NFT => Self::burn_nft_item(collection, item_id)?,1534			CollectionMode::Fungible(_) => Self::burn_fungible_item(sender, collection, value)?,1535			CollectionMode::ReFungible => Self::burn_refungible_item(collection, item_id, sender)?,1536			_ => (),1537		};15381539		Ok(())1540	}15411542	pub fn toggle_white_list_internal(1543		sender: &T::CrossAccountId,1544		collection: &CollectionHandle<T>,1545		address: &T::CrossAccountId,1546		whitelisted: bool,1547	) -> DispatchResult {1548		Self::check_owner_or_admin_permissions(collection, sender)?;15491550		if whitelisted {1551			<WhiteList<T>>::insert(collection.id, address.as_sub(), true);1552		} else {1553			<WhiteList<T>>::remove(collection.id, address.as_sub());1554		}15551556		Ok(())1557	}15581559	fn is_correct_transfer(1560		collection: &CollectionHandle<T>,1561		recipient: &T::CrossAccountId,1562	) -> DispatchResult {1563		let collection_id = collection.id;15641565		// check token limit and account token limit1566		let account_items: u32 =1567			<AddressTokens<T>>::get(collection_id, recipient.as_sub()).len() as u32;1568		ensure!(1569			collection.limits.account_token_ownership_limit > account_items,1570			Error::<T>::AccountTokenLimitExceeded1571		);15721573		// preliminary transfer check1574		ensure!(collection.transfers_enabled, Error::<T>::TransferNotAllowed);15751576		Ok(())1577	}15781579	fn can_create_items_in_collection(1580		collection: &CollectionHandle<T>,1581		sender: &T::CrossAccountId,1582		owner: &T::CrossAccountId,1583		amount: u32,1584	) -> DispatchResult {1585		let collection_id = collection.id;15861587		// check token limit and account token limit1588		let total_items: u32 = ItemListIndex::get(collection_id)1589			.checked_add(amount)1590			.ok_or(Error::<T>::CollectionTokenLimitExceeded)?;1591		let account_items: u32 = (<AddressTokens<T>>::get(collection_id, owner.as_sub()).len()1592			as u32)1593			.checked_add(amount)1594			.ok_or(Error::<T>::AccountTokenLimitExceeded)?;1595		ensure!(1596			collection.limits.token_limit >= total_items,1597			Error::<T>::CollectionTokenLimitExceeded1598		);1599		ensure!(1600			collection.limits.account_token_ownership_limit >= account_items,1601			Error::<T>::AccountTokenLimitExceeded1602		);16031604		if !Self::is_owner_or_admin_permissions(collection, sender) {1605			ensure!(collection.mint_mode, Error::<T>::PublicMintingNotAllowed);1606			Self::check_white_list(collection, owner)?;1607			Self::check_white_list(collection, sender)?;1608		}16091610		Ok(())1611	}16121613	fn validate_create_item_args(1614		target_collection: &CollectionHandle<T>,1615		data: &CreateItemData,1616	) -> DispatchResult {1617		match target_collection.mode {1618			CollectionMode::NFT => {1619				if let CreateItemData::NFT(data) = data {1620					// check sizes1621					ensure!(1622						ChainLimit::get().custom_data_limit >= data.const_data.len() as u32,1623						Error::<T>::TokenConstDataLimitExceeded1624					);1625					ensure!(1626						ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32,1627						Error::<T>::TokenVariableDataLimitExceeded1628					);1629				} else {1630					fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1631				}1632			}1633			CollectionMode::Fungible(_) => {1634				if let CreateItemData::Fungible(_) = data {1635				} else {1636					fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1637				}1638			}1639			CollectionMode::ReFungible => {1640				if let CreateItemData::ReFungible(data) = data {1641					// check sizes1642					ensure!(1643						ChainLimit::get().custom_data_limit >= data.const_data.len() as u32,1644						Error::<T>::TokenConstDataLimitExceeded1645					);1646					ensure!(1647						ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32,1648						Error::<T>::TokenVariableDataLimitExceeded1649					);16501651					// Check refungibility limits1652					ensure!(1653						data.pieces <= MAX_REFUNGIBLE_PIECES,1654						Error::<T>::WrongRefungiblePieces1655					);1656					ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1657				} else {1658					fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1659				}1660			}1661			_ => {1662				fail!(Error::<T>::UnexpectedCollectionType);1663			}1664		};16651666		Ok(())1667	}16681669	fn create_item_no_validation(1670		collection: &CollectionHandle<T>,1671		owner: &T::CrossAccountId,1672		data: CreateItemData,1673	) -> DispatchResult {1674		match data {1675			CreateItemData::NFT(data) => {1676				let item = NftItemType {1677					owner: owner.clone(),1678					const_data: data.const_data,1679					variable_data: data.variable_data,1680				};16811682				Self::add_nft_item(collection, item)?;1683			}1684			CreateItemData::Fungible(data) => {1685				Self::add_fungible_item(collection, owner, data.value)?;1686			}1687			CreateItemData::ReFungible(data) => {1688				let owner_list = vec![Ownership {1689					owner: owner.clone(),1690					fraction: data.pieces,1691				}];16921693				let item = ReFungibleItemType {1694					owner: owner_list,1695					const_data: data.const_data,1696					variable_data: data.variable_data,1697				};16981699				Self::add_refungible_item(collection, item)?;1700			}1701		};17021703		Ok(())1704	}17051706	fn add_fungible_item(1707		collection: &CollectionHandle<T>,1708		owner: &T::CrossAccountId,1709		value: u128,1710	) -> DispatchResult {1711		let collection_id = collection.id;17121713		// Does new owner already have an account?1714		let balance: u128 = <FungibleItemList<T>>::get(collection_id, owner.as_sub()).value;17151716		// Mint1717		let item = FungibleItemType {1718			value: balance.checked_add(value).ok_or(Error::<T>::NumOverflow)?,1719		};1720		<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), item);17211722		// Update balance1723		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1724			.checked_add(value)1725			.ok_or(Error::<T>::NumOverflow)?;1726		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);17271728		Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));1729		Ok(())1730	}17311732	fn add_refungible_item(1733		collection: &CollectionHandle<T>,1734		item: ReFungibleItemType<T::CrossAccountId>,1735	) -> DispatchResult {1736		let collection_id = collection.id;17371738		let current_index = <ItemListIndex>::get(collection_id)1739			.checked_add(1)1740			.ok_or(Error::<T>::NumOverflow)?;1741		let itemcopy = item.clone();17421743		ensure!(item.owner.len() == 1, Error::<T>::BadCreateRefungibleCall,);1744		let item_owner = item.owner.first().expect("only one owner is defined");17451746		let value = item_owner.fraction;1747		let owner = item_owner.owner.clone();17481749		Self::add_token_index(collection_id, current_index, &owner)?;17501751		<ItemListIndex>::insert(collection_id, current_index);1752		<ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);17531754		// Update balance1755		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1756			.checked_add(value)1757			.ok_or(Error::<T>::NumOverflow)?;1758		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);17591760		Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));1761		Ok(())1762	}17631764	fn add_nft_item(1765		collection: &CollectionHandle<T>,1766		item: NftItemType<T::CrossAccountId>,1767	) -> DispatchResult {1768		let collection_id = collection.id;17691770		let current_index = <ItemListIndex>::get(collection_id)1771			.checked_add(1)1772			.ok_or(Error::<T>::NumOverflow)?;17731774		let item_owner = item.owner.clone();1775		Self::add_token_index(collection_id, current_index, &item.owner)?;17761777		<ItemListIndex>::insert(collection_id, current_index);1778		<NftItemList<T>>::insert(collection_id, current_index, item);17791780		// Update balance1781		let new_balance = <Balance<T>>::get(collection_id, item_owner.as_sub())1782			.checked_add(1)1783			.ok_or(Error::<T>::NumOverflow)?;1784		<Balance<T>>::insert(collection_id, item_owner.as_sub(), new_balance);17851786		collection.log(ERC721Events::Transfer {1787			from: H160::default(),1788			to: *item_owner.as_eth(),1789			token_id: current_index.into(),1790		})?;1791		Self::deposit_event(RawEvent::ItemCreated(1792			collection_id,1793			current_index,1794			item_owner,1795		));1796		Ok(())1797	}17981799	fn burn_refungible_item(1800		collection: &CollectionHandle<T>,1801		item_id: TokenId,1802		owner: &T::CrossAccountId,1803	) -> DispatchResult {1804		let collection_id = collection.id;18051806		let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id)1807			.ok_or(Error::<T>::TokenNotFound)?;1808		let rft_balance = token1809			.owner1810			.iter()1811			.find(|&i| i.owner == *owner)1812			.ok_or(Error::<T>::TokenNotFound)?;1813		Self::remove_token_index(collection_id, item_id, owner)?;18141815		// update balance1816		let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.as_sub())1817			.checked_sub(rft_balance.fraction)1818			.ok_or(Error::<T>::NumOverflow)?;1819		<Balance<T>>::insert(collection_id, rft_balance.owner.as_sub(), new_balance);18201821		// Re-create owners list with sender removed1822		let index = token1823			.owner1824			.iter()1825			.position(|i| i.owner == *owner)1826			.expect("owned item is exists");1827		token.owner.remove(index);1828		let owner_count = token.owner.len();18291830		// Burn the token completely if this was the last (only) owner1831		if owner_count == 0 {1832			<ReFungibleItemList<T>>::remove(collection_id, item_id);1833			<VariableMetaDataBasket<T>>::remove(collection_id, item_id);1834		} else {1835			<ReFungibleItemList<T>>::insert(collection_id, item_id, token);1836		}18371838		Ok(())1839	}18401841	fn burn_nft_item(collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1842		let collection_id = collection.id;18431844		let item =1845			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;1846		Self::remove_token_index(collection_id, item_id, &item.owner)?;18471848		// update balance1849		let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())1850			.checked_sub(1)1851			.ok_or(Error::<T>::NumOverflow)?;1852		<Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);1853		<NftItemList<T>>::remove(collection_id, item_id);1854		<VariableMetaDataBasket<T>>::remove(collection_id, item_id);18551856		Self::deposit_event(RawEvent::ItemDestroyed(collection.id, item_id));1857		Ok(())1858	}18591860	fn burn_fungible_item(1861		owner: &T::CrossAccountId,1862		collection: &CollectionHandle<T>,1863		value: u128,1864	) -> DispatchResult {1865		let collection_id = collection.id;18661867		let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());1868		ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);18691870		// update balance1871		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1872			.checked_sub(value)1873			.ok_or(Error::<T>::NumOverflow)?;1874		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);18751876		if balance.value - value > 0 {1877			balance.value -= value;1878			<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);1879		} else {1880			<FungibleItemList<T>>::remove(collection_id, owner.as_sub());1881		}18821883		collection.log(ERC20Events::Transfer {1884			from: *owner.as_eth(),1885			to: H160::default(),1886			value: value.into(),1887		})?;1888		Ok(())1889	}18901891	pub fn get_collection(1892		collection_id: CollectionId,1893	) -> Result<CollectionHandle<T>, sp_runtime::DispatchError> {1894		Ok(<CollectionHandle<T>>::get(collection_id).ok_or(Error::<T>::CollectionNotFound)?)1895	}18961897	fn check_owner_permissions(1898		target_collection: &CollectionHandle<T>,1899		subject: &T::AccountId,1900	) -> DispatchResult {1901		ensure!(1902			*subject == target_collection.owner,1903			Error::<T>::NoPermission1904		);19051906		Ok(())1907	}19081909	fn is_owner_or_admin_permissions(1910		collection: &CollectionHandle<T>,1911		subject: &T::CrossAccountId,1912	) -> bool {1913		*subject.as_sub() == collection.owner1914			|| <AdminList<T>>::get(collection.id).contains(subject)1915	}19161917	fn check_owner_or_admin_permissions(1918		collection: &CollectionHandle<T>,1919		subject: &T::CrossAccountId,1920	) -> DispatchResult {1921		ensure!(1922			Self::is_owner_or_admin_permissions(collection, subject),1923			Error::<T>::NoPermission1924		);19251926		Ok(())1927	}19281929	fn owned_amount(1930		subject: &T::CrossAccountId,1931		target_collection: &CollectionHandle<T>,1932		item_id: TokenId,1933	) -> Option<u128> {1934		let collection_id = target_collection.id;19351936		match target_collection.mode {1937			CollectionMode::NFT => {1938				(<NftItemList<T>>::get(collection_id, item_id)?.owner == *subject).then(|| 1)1939			}1940			CollectionMode::Fungible(_) => {1941				Some(<FungibleItemList<T>>::get(collection_id, &subject.as_sub()).value)1942			}1943			CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)?1944				.owner1945				.iter()1946				.find(|i| i.owner == *subject)1947				.map(|i| i.fraction),1948			CollectionMode::Invalid => None,1949		}1950	}19511952	fn is_item_owner(1953		subject: &T::CrossAccountId,1954		target_collection: &CollectionHandle<T>,1955		item_id: TokenId,1956	) -> bool {1957		match target_collection.mode {1958			CollectionMode::Fungible(_) => true,1959			_ => Self::owned_amount(subject, target_collection, item_id).is_some(),1960		}1961	}19621963	fn check_white_list(1964		collection: &CollectionHandle<T>,1965		address: &T::CrossAccountId,1966	) -> DispatchResult {1967		let collection_id = collection.id;19681969		let mes = Error::<T>::AddresNotInWhiteList;1970		ensure!(1971			<WhiteList<T>>::contains_key(collection_id, address.as_sub()),1972			mes1973		);19741975		Ok(())1976	}19771978	/// Check if token exists. In case of Fungible, check if there is an entry for1979	/// the owner in fungible balances double map1980	fn token_exists(target_collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1981		let collection_id = target_collection.id;1982		let exists = match target_collection.mode {1983			CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),1984			CollectionMode::Fungible(_) => true,1985			CollectionMode::ReFungible => {1986				<ReFungibleItemList<T>>::contains_key(collection_id, item_id)1987			}1988			_ => false,1989		};19901991		ensure!(exists, Error::<T>::TokenNotFound);1992		Ok(())1993	}19941995	fn transfer_fungible(1996		collection: &CollectionHandle<T>,1997		value: u128,1998		owner: &T::CrossAccountId,1999		recipient: &T::CrossAccountId,2000	) -> DispatchResult {2001		let collection_id = collection.id;20022003		let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());2004		ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);20052006		// Send balance to recipient (updates balanceOf of recipient)2007		Self::add_fungible_item(collection, recipient, value)?;20082009		// update balanceOf of sender2010		<Balance<T>>::insert(collection_id, owner.as_sub(), balance.value - value);20112012		// Reduce or remove sender2013		if balance.value == value {2014			<FungibleItemList<T>>::remove(collection_id, owner.as_sub());2015		} else {2016			balance.value -= value;2017			<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);2018		}20192020		collection.log(ERC20Events::Transfer {2021			from: *owner.as_eth(),2022			to: *recipient.as_eth(),2023			value: value.into(),2024		})?;2025		Self::deposit_event(RawEvent::Transfer(2026			collection.id,2027			1,2028			owner.clone(),2029			recipient.clone(),2030			value,2031		));20322033		Ok(())2034	}20352036	fn transfer_refungible(2037		collection: &CollectionHandle<T>,2038		item_id: TokenId,2039		value: u128,2040		owner: T::CrossAccountId,2041		new_owner: T::CrossAccountId,2042	) -> DispatchResult {2043		let collection_id = collection.id;2044		let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id)2045			.ok_or(Error::<T>::TokenNotFound)?;20462047		let item = full_item2048			.owner2049			.iter()2050			.find(|i| i.owner == owner)2051			.ok_or(Error::<T>::TokenNotFound)?;2052		let amount = item.fraction;20532054		ensure!(amount >= value, Error::<T>::TokenValueTooLow);20552056		// update balance2057		let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2058			.checked_sub(value)2059			.ok_or(Error::<T>::NumOverflow)?;2060		<Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);20612062		let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2063			.checked_add(value)2064			.ok_or(Error::<T>::NumOverflow)?;2065		<Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);20662067		let old_owner = item.owner.clone();2068		let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);20692070		let mut new_full_item = full_item.clone();2071		// transfer2072		if amount == value && !new_owner_has_account {2073			// change owner2074			// new owner do not have account2075			new_full_item2076				.owner2077				.iter_mut()2078				.find(|i| i.owner == owner)2079				.expect("old owner does present in refungible")2080				.owner = new_owner.clone();2081			<ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);20822083			// update index collection2084			Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;2085		} else {2086			new_full_item2087				.owner2088				.iter_mut()2089				.find(|i| i.owner == owner)2090				.expect("old owner does present in refungible")2091				.fraction -= value;20922093			// separate amount2094			if new_owner_has_account {2095				// new owner has account2096				new_full_item2097					.owner2098					.iter_mut()2099					.find(|i| i.owner == new_owner)2100					.expect("new owner has account")2101					.fraction += value;2102			} else {2103				// new owner do not have account2104				new_full_item.owner.push(Ownership {2105					owner: new_owner.clone(),2106					fraction: value,2107				});2108				Self::add_token_index(collection_id, item_id, &new_owner)?;2109			}21102111			<ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2112		}21132114		Self::deposit_event(RawEvent::Transfer(2115			collection.id,2116			item_id,2117			owner,2118			new_owner,2119			amount,2120		));21212122		Ok(())2123	}21242125	fn transfer_nft(2126		collection: &CollectionHandle<T>,2127		item_id: TokenId,2128		sender: T::CrossAccountId,2129		new_owner: T::CrossAccountId,2130	) -> DispatchResult {2131		let collection_id = collection.id;2132		let mut item =2133			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;21342135		ensure!(sender == item.owner, Error::<T>::MustBeTokenOwner);21362137		// update balance2138		let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2139			.checked_sub(1)2140			.ok_or(Error::<T>::NumOverflow)?;2141		<Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);21422143		let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2144			.checked_add(1)2145			.ok_or(Error::<T>::NumOverflow)?;2146		<Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);21472148		// change owner2149		let old_owner = item.owner.clone();2150		item.owner = new_owner.clone();2151		<NftItemList<T>>::insert(collection_id, item_id, item);21522153		// update index collection2154		Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;21552156		collection.log(ERC721Events::Transfer {2157			from: *sender.as_eth(),2158			to: *new_owner.as_eth(),2159			token_id: item_id.into(),2160		})?;2161		Self::deposit_event(RawEvent::Transfer(2162			collection.id,2163			item_id,2164			sender,2165			new_owner,2166			1,2167		));21682169		Ok(())2170	}21712172	fn set_re_fungible_variable_data(2173		collection: &CollectionHandle<T>,2174		item_id: TokenId,2175		data: Vec<u8>,2176	) -> DispatchResult {2177		let collection_id = collection.id;2178		let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id)2179			.ok_or(Error::<T>::TokenNotFound)?;21802181		item.variable_data = data;21822183		<ReFungibleItemList<T>>::insert(collection_id, item_id, item);21842185		Ok(())2186	}21872188	fn set_nft_variable_data(2189		collection: &CollectionHandle<T>,2190		item_id: TokenId,2191		data: Vec<u8>,2192	) -> DispatchResult {2193		let collection_id = collection.id;2194		let mut item =2195			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;21962197		item.variable_data = data;21982199		<NftItemList<T>>::insert(collection_id, item_id, item);22002201		Ok(())2202	}22032204	#[allow(dead_code)]2205	fn init_collection(item: &Collection<T>) {2206		// check params2207		assert!(2208			item.decimal_points <= MAX_DECIMAL_POINTS,2209			"decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2210		);2211		assert!(2212			item.name.len() <= 64,2213			"Collection name can not be longer than 63 char"2214		);2215		assert!(2216			item.name.len() <= 256,2217			"Collection description can not be longer than 255 char"2218		);2219		assert!(2220			item.token_prefix.len() <= 16,2221			"Token prefix can not be longer than 15 char"2222		);22232224		// Generate next collection ID2225		let next_id = CreatedCollectionCount::get().checked_add(1).unwrap();22262227		CreatedCollectionCount::put(next_id);2228	}22292230	#[allow(dead_code)]2231	fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::CrossAccountId>) {2232		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();22332234		Self::add_token_index(collection_id, current_index, &item.owner).unwrap();22352236		<ItemListIndex>::insert(collection_id, current_index);22372238		// Update balance2239		let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2240			.checked_add(1)2241			.unwrap();2242		<Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2243	}22442245	#[allow(dead_code)]2246	fn init_fungible_token(2247		collection_id: CollectionId,2248		owner: &T::CrossAccountId,2249		item: &FungibleItemType,2250	) {2251		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();22522253		Self::add_token_index(collection_id, current_index, owner).unwrap();22542255		<ItemListIndex>::insert(collection_id, current_index);22562257		// Update balance2258		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2259			.checked_add(item.value)2260			.unwrap();2261		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2262	}22632264	#[allow(dead_code)]2265	fn init_refungible_token(2266		collection_id: CollectionId,2267		item: &ReFungibleItemType<T::CrossAccountId>,2268	) {2269		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();22702271		let value = item.owner.first().unwrap().fraction;2272		let owner = item.owner.first().unwrap().owner.clone();22732274		Self::add_token_index(collection_id, current_index, &owner).unwrap();22752276		<ItemListIndex>::insert(collection_id, current_index);22772278		// Update balance2279		let new_balance = <Balance<T>>::get(collection_id, &owner.as_sub())2280			.checked_add(value)2281			.unwrap();2282		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2283	}22842285	fn add_token_index(2286		collection_id: CollectionId,2287		item_index: TokenId,2288		owner: &T::CrossAccountId,2289	) -> DispatchResult {2290		// add to account limit2291		if <AccountItemCount<T>>::contains_key(owner.as_sub()) {2292			// bound Owned tokens by a single address2293			let count = <AccountItemCount<T>>::get(owner.as_sub());2294			ensure!(2295				count < ChainLimit::get().account_token_ownership_limit,2296				Error::<T>::AddressOwnershipLimitExceeded2297			);22982299			<AccountItemCount<T>>::insert(2300				owner.as_sub(),2301				count.checked_add(1).ok_or(Error::<T>::NumOverflow)?,2302			);2303		} else {2304			<AccountItemCount<T>>::insert(owner.as_sub(), 1);2305		}23062307		let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2308		if list_exists {2309			let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2310			let item_contains = list.contains(&item_index.clone());23112312			if !item_contains {2313				list.push(item_index);2314			}23152316			<AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2317		} else {2318			let itm = vec![item_index];2319			<AddressTokens<T>>::insert(collection_id, owner.as_sub(), itm);2320		}23212322		Ok(())2323	}23242325	fn remove_token_index(2326		collection_id: CollectionId,2327		item_index: TokenId,2328		owner: &T::CrossAccountId,2329	) -> DispatchResult {2330		// update counter2331		<AccountItemCount<T>>::insert(2332			owner.as_sub(),2333			<AccountItemCount<T>>::get(owner.as_sub())2334				.checked_sub(1)2335				.ok_or(Error::<T>::NumOverflow)?,2336		);23372338		let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2339		if list_exists {2340			let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2341			let item_contains = list.contains(&item_index.clone());23422343			if item_contains {2344				list.retain(|&item| item != item_index);2345				<AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2346			}2347		}23482349		Ok(())2350	}23512352	fn move_token_index(2353		collection_id: CollectionId,2354		item_index: TokenId,2355		old_owner: &T::CrossAccountId,2356		new_owner: &T::CrossAccountId,2357	) -> DispatchResult {2358		Self::remove_token_index(collection_id, item_index, old_owner)?;2359		Self::add_token_index(collection_id, item_index, new_owner)?;23602361		Ok(())2362	}2363}23642365sp_api::decl_runtime_apis! {2366	pub trait NftApi {2367		/// Used for ethereum integration2368		fn eth_contract_code(account: H160) -> Option<Vec<u8>>;2369	}2370}