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

difftreelog

source

pallets/nft/src/lib.rs71.5 KiBsourcehistory
1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]7#![cfg_attr(not(feature = "std"), no_std)]8#![allow(9	clippy::too_many_arguments,10	clippy::unnecessary_mut_passed,11	clippy::unused_unit12)]1314extern crate alloc;1516pub use serde::{Serialize, Deserialize};1718pub use frame_support::{19	construct_runtime, decl_event, decl_module, decl_storage, decl_error,20	dispatch::DispatchResult,21	ensure, fail, parameter_types,22	traits::{23		Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,24		Randomness, IsSubType, WithdrawReasons,25	},26	weights::{27		constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},28		DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,29		WeightToFeePolynomial, DispatchClass,30	},31	StorageValue, transactional,32};3334use frame_system::{self as system, ensure_signed};35use sp_core::H160;36use sp_std::vec;37use sp_runtime::{DispatchError, sp_std::prelude::Vec};38use core::ops::{Deref, DerefMut};39use nft_data_structs::{40	MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, MAX_REFUNGIBLE_PIECES,41	CUSTOM_DATA_LIMIT, COLLECTION_NUMBER_LIMIT, ACCOUNT_TOKEN_OWNERSHIP_LIMIT,42	VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, COLLECTION_ADMINS_LIMIT,43	OFFCHAIN_SCHEMA_LIMIT, AccessMode, Collection, CreateItemData, CollectionLimits, CollectionId,44	CollectionMode, TokenId, SchemaVersion, SponsorshipState, Ownership, NftItemType,45	FungibleItemType, ReFungibleItemType,46};4748#[cfg(test)]49mod mock;5051#[cfg(test)]52mod tests;5354mod default_weights;55mod eth;56mod sponsorship;57pub use sponsorship::NftSponsorshipHandler;58pub use eth::sponsoring::NftEthSponsorshipHandler;5960pub use eth::NftErcSupport;61pub use eth::account::*;62use eth::erc::{ERC20Events, ERC721Events};6364#[cfg(feature = "runtime-benchmarks")]65mod benchmarking;6667pub trait WeightInfo {68	fn create_collection() -> Weight;69	fn destroy_collection() -> Weight;70	fn add_to_white_list() -> Weight;71	fn remove_from_white_list() -> Weight;72	fn set_public_access_mode() -> Weight;73	fn set_mint_permission() -> Weight;74	fn change_collection_owner() -> Weight;75	fn add_collection_admin() -> Weight;76	fn remove_collection_admin() -> Weight;77	fn set_collection_sponsor() -> Weight;78	fn confirm_sponsorship() -> Weight;79	fn remove_collection_sponsor() -> Weight;80	fn create_item(s: usize) -> Weight;81	fn burn_item() -> Weight;82	fn transfer() -> Weight;83	fn approve() -> Weight;84	fn transfer_from() -> Weight;85	fn set_offchain_schema() -> Weight;86	fn set_const_on_chain_schema() -> Weight;87	fn set_variable_on_chain_schema() -> Weight;88	fn set_variable_meta_data() -> Weight;89	fn enable_contract_sponsoring() -> Weight;90	fn set_schema_version() -> Weight;91	fn set_contract_sponsoring_rate_limit() -> Weight;92	fn set_variable_meta_data_sponsoring_rate_limit() -> Weight;93	fn toggle_contract_white_list() -> Weight;94	fn add_to_contract_white_list() -> Weight;95	fn remove_from_contract_white_list() -> Weight;96	fn set_collection_limits() -> Weight;97}9899decl_error! {100	/// Error for non-fungible-token module.101	pub enum Error for Module<T: Config> {102		/// Total collections bound exceeded.103		TotalCollectionsLimitExceeded,104		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.105		CollectionDecimalPointLimitExceeded,106		/// Collection name can not be longer than 63 char.107		CollectionNameLimitExceeded,108		/// Collection description can not be longer than 255 char.109		CollectionDescriptionLimitExceeded,110		/// Token prefix can not be longer than 15 char.111		CollectionTokenPrefixLimitExceeded,112		/// This collection does not exist.113		CollectionNotFound,114		/// Item not exists.115		TokenNotFound,116		/// Admin not found117		AdminNotFound,118		/// Arithmetic calculation overflow.119		NumOverflow,120		/// Account already has admin role.121		AlreadyAdmin,122		/// You do not own this collection.123		NoPermission,124		/// This address is not set as sponsor, use setCollectionSponsor first.125		ConfirmUnsetSponsorFail,126		/// Collection is not in mint mode.127		PublicMintingNotAllowed,128		/// Sender parameter and item owner must be equal.129		MustBeTokenOwner,130		/// Item balance not enough.131		TokenValueTooLow,132		/// Size of item is too large.133		NftSizeLimitExceeded,134		/// No approve found135		ApproveNotFound,136		/// Requested value more than approved.137		TokenValueNotEnough,138		/// Only approved addresses can call this method.139		ApproveRequired,140		/// Address is not in white list.141		AddresNotInWhiteList,142		/// Number of collection admins bound exceeded.143		CollectionAdminsLimitExceeded,144		/// Owned tokens by a single address bound exceeded.145		AddressOwnershipLimitExceeded,146		/// Length of items properties must be greater than 0.147		EmptyArgument,148		/// const_data exceeded data limit.149		TokenConstDataLimitExceeded,150		/// variable_data exceeded data limit.151		TokenVariableDataLimitExceeded,152		/// Not NFT item data used to mint in NFT collection.153		NotNftDataUsedToMintNftCollectionToken,154		/// Not Fungible item data used to mint in Fungible collection.155		NotFungibleDataUsedToMintFungibleCollectionToken,156		/// Not Re Fungible item data used to mint in Re Fungible collection.157		NotReFungibleDataUsedToMintReFungibleCollectionToken,158		/// Unexpected collection type.159		UnexpectedCollectionType,160		/// Can't store metadata in fungible tokens.161		CantStoreMetadataInFungibleTokens,162		/// Collection token limit exceeded163		CollectionTokenLimitExceeded,164		/// Account token limit exceeded per collection165		AccountTokenLimitExceeded,166		/// Collection limit bounds per collection exceeded167		CollectionLimitBoundsExceeded,168		/// Tried to enable permissions which are only permitted to be disabled169		OwnerPermissionsCantBeReverted,170		/// Schema data size limit bound exceeded171		SchemaDataLimitExceeded,172		/// Maximum refungibility exceeded173		WrongRefungiblePieces,174		/// createRefungible should be called with one owner175		BadCreateRefungibleCall,176		/// Gas limit exceeded177		OutOfGas,178		/// Collection settings not allowing items transferring179		TransferNotAllowed,180	}181}182183#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]184pub struct CollectionHandle<T: Config> {185	pub id: CollectionId,186	collection: Collection<T>,187	recorder: pallet_evm_coder_substrate::SubstrateRecorder<T>,188}189impl<T: Config> CollectionHandle<T> {190	pub fn get_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {191		<CollectionById<T>>::get(id).map(|collection| Self {192			id,193			collection,194			recorder: pallet_evm_coder_substrate::SubstrateRecorder::new(195				eth::collection_id_to_address(id),196				gas_limit,197			),198		})199	}200	pub fn get(id: CollectionId) -> Option<Self> {201		Self::get_with_gas_limit(id, u64::MAX)202	}203	pub fn log(&self, log: impl evm_coder::ToLog) -> DispatchResult {204		self.recorder.log_sub(log)205	}206	#[allow(dead_code)]207	fn consume_gas(&self, gas: u64) -> DispatchResult {208		self.recorder.consume_gas_sub(gas)209	}210	fn consume_sload(&self) -> DispatchResult {211		self.recorder.consume_sload_sub()212	}213	fn consume_sstore(&self) -> DispatchResult {214		self.recorder.consume_sstore_sub()215	}216	pub fn submit_logs(self) -> DispatchResult {217		self.recorder.submit_logs()218	}219	pub fn save(self) -> DispatchResult {220		self.recorder.submit_logs()?;221		<CollectionById<T>>::insert(self.id, self.collection);222		Ok(())223	}224}225impl<T: Config> Deref for CollectionHandle<T> {226	type Target = Collection<T>;227228	fn deref(&self) -> &Self::Target {229		&self.collection230	}231}232233impl<T: Config> DerefMut for CollectionHandle<T> {234	fn deref_mut(&mut self) -> &mut Self::Target {235		&mut self.collection236	}237}238239pub trait Config: system::Config + pallet_evm_coder_substrate::Config + Sized {240	type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;241242	/// Weight information for extrinsics in this pallet.243	type WeightInfo: WeightInfo;244245	type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;246	type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;247248	type CrossAccountId: CrossAccountId<Self::AccountId>;249	type Currency: Currency<Self::AccountId>;250	type CollectionCreationPrice: Get<251		<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,252	>;253	type TreasuryAccountId: Get<Self::AccountId>;254}255256// # Used definitions257//258// ## User control levels259//260// chain-controlled - key is uncontrolled by user261//                    i.e autoincrementing index262//                    can use non-cryptographic hash263// real - key is controlled by user264//        but it is hard to generate enough colliding values, i.e owner of signed txs265//        can use non-cryptographic hash266// controlled - key is completly controlled by users267//              i.e maps with mutable keys268//              should use cryptographic hash269//270// ## User control level downgrade reasons271//272// ?1 - chain-controlled -> controlled273//      collections/tokens can be destroyed, resulting in massive holes274// ?2 - chain-controlled -> controlled275//      same as ?1, but can be only added, resulting in easier exploitation276// ?3 - real -> controlled277//      no confirmation required, so addresses can be easily generated278decl_storage! {279	trait Store for Module<T: Config> as Nft {280281		//#region Private members282		/// Id of next collection283		CreatedCollectionCount: u32;284		/// Used for migrations285		ChainVersion: u64;286		/// Id of last collection token287		/// Collection id (controlled?1)288		ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;289		//#endregion290291		//#region Bound counters292		/// Amount of collections destroyed, used for total amount tracking with293		/// CreatedCollectionCount294		DestroyedCollectionCount: u32;295		/// Total amount of account owned tokens (NFTs + RFTs + unique fungibles)296		/// Account id (real)297		pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;298		//#endregion299300		//#region Basic collections301		/// Collection info302		/// Collection id (controlled?1)303		pub CollectionById get(fn collection_id) config(): map hasher(blake2_128_concat) CollectionId => Option<Collection<T>> = None;304		/// List of collection admins305		/// Collection id (controlled?2)306		pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec<T::CrossAccountId>;307		/// Whitelisted collection users308		/// Collection id (controlled?2), user id (controlled?3)309		pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool;310		//#endregion311312		/// How many of collection items user have313		/// Collection id (controlled?2), account id (real)314		pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128;315316		/// Amount of items which spender can transfer out of owners account (via transferFrom)317		/// Collection id (controlled?2), (token id (controlled ?2) + owner account id (real) + spender account id (controlled?3))318		/// TODO: Off chain worker should remove from this map when token gets removed319		pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128;320321		//#region Item collections322		/// Collection id (controlled?2), token id (controlled?1)323		pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<NftItemType<T::CrossAccountId>>;324		/// Collection id (controlled?2), owner (controlled?2)325		pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType;326		/// Collection id (controlled?2), token id (controlled?1)327		pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<ReFungibleItemType<T::CrossAccountId>>;328		//#endregion329330		//#region Index list331		/// Collection id (controlled?2), tokens owner (controlled?2)332		pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;333		//#endregion334335		//#region Tokens transfer rate limit baskets336		/// (Collection id (controlled?2), who created (real))337		/// TODO: Off chain worker should remove from this map when collection gets removed338		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;339		/// Collection id (controlled?2), token id (controlled?2)340		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;341		/// Collection id (controlled?2), owning user (real)342		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;343		/// Collection id (controlled?2), token id (controlled?2)344		pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;345		//#endregion346347		/// Variable metadata sponsoring348		/// Collection id (controlled?2), token id (controlled?2)349		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;350	}351	add_extra_genesis {352		build(|config: &GenesisConfig<T>| {353			// Modification of storage354			for (_num, _c) in &config.collection_id {355				<Module<T>>::init_collection(_c);356			}357358			for (_num, _c, _i) in &config.nft_item_id {359				<Module<T>>::init_nft_token(*_c, _i);360			}361362			for (collection_id, account_id, fungible_item) in &config.fungible_item_id {363				<Module<T>>::init_fungible_token(*collection_id, &T::CrossAccountId::from_sub(account_id.clone()), fungible_item);364			}365366			for (_num, _c, _i) in &config.refungible_item_id {367				<Module<T>>::init_refungible_token(*_c, _i);368			}369		})370	}371}372373decl_event!(374	pub enum Event<T>375	where376		AccountId = <T as frame_system::Config>::AccountId,377		CrossAccountId = <T as Config>::CrossAccountId,378	{379		/// New collection was created380		///381		/// # Arguments382		///383		/// * collection_id: Globally unique identifier of newly created collection.384		///385		/// * mode: [CollectionMode] converted into u8.386		///387		/// * account_id: Collection owner.388		CollectionCreated(CollectionId, u8, AccountId),389390		/// New item was created.391		///392		/// # Arguments393		///394		/// * collection_id: Id of the collection where item was created.395		///396		/// * item_id: Id of an item. Unique within the collection.397		///398		/// * recipient: Owner of newly created item399		ItemCreated(CollectionId, TokenId, CrossAccountId),400401		/// Collection item was burned.402		///403		/// # Arguments404		///405		/// collection_id.406		///407		/// item_id: Identifier of burned NFT.408		ItemDestroyed(CollectionId, TokenId),409410		/// Item was transferred411		///412		/// * collection_id: Id of collection to which item is belong413		///414		/// * item_id: Id of an item415		///416		/// * sender: Original owner of item417		///418		/// * recipient: New owner of item419		///420		/// * amount: Always 1 for NFT421		Transfer(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),422423		/// * collection_id424		///425		/// * item_id426		///427		/// * sender428		///429		/// * spender430		///431		/// * amount432		Approved(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),433	}434);435436decl_module! {437	pub struct Module<T: Config> for enum Call438	where439		origin: T::Origin440	{441		fn deposit_event() = default;442		const CollectionAdminsLimit: u64 = COLLECTION_ADMINS_LIMIT;443		type Error = Error<T>;444445		fn on_initialize(_now: T::BlockNumber) -> Weight {446			0447		}448449		/// 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.450		///451		/// # Permissions452		///453		/// * Anyone.454		///455		/// # Arguments456		///457		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.458		///459		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.460		///461		/// * token_prefix: UTF-8 string with token prefix.462		///463		/// * mode: [CollectionMode] collection type and type dependent data.464		// returns collection ID465		#[weight = <T as Config>::WeightInfo::create_collection()]466		#[transactional]467		pub fn create_collection(origin,468								 collection_name: Vec<u16>,469								 collection_description: Vec<u16>,470								 token_prefix: Vec<u8>,471								 mode: CollectionMode) -> DispatchResult {472473			// Anyone can create a collection474			let who = ensure_signed(origin)?;475476			// Take a (non-refundable) deposit of collection creation477			let mut imbalance = <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();478			imbalance.subsume(<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(479				&T::TreasuryAccountId::get(),480				T::CollectionCreationPrice::get(),481			));482			<T as Config>::Currency::settle(483				&who,484				imbalance,485				WithdrawReasons::TRANSFER,486				ExistenceRequirement::KeepAlive,487			).map_err(|_| Error::<T>::NoPermission)?;488489			let decimal_points = match mode {490				CollectionMode::Fungible(points) => points,491				_ => 0492			};493494			let created_count = CreatedCollectionCount::get();495			let destroyed_count = DestroyedCollectionCount::get();496497			// bound Total number of collections498			ensure!(created_count - destroyed_count < COLLECTION_NUMBER_LIMIT, Error::<T>::TotalCollectionsLimitExceeded);499500			// check params501			ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);502			ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);503			ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);504			ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);505506			// Generate next collection ID507			let next_id = created_count508				.checked_add(1)509				.ok_or(Error::<T>::NumOverflow)?;510511			CreatedCollectionCount::put(next_id);512513			let limits = CollectionLimits {514				sponsored_data_size: CUSTOM_DATA_LIMIT,515				..Default::default()516			};517518			// Create new collection519			let new_collection = Collection {520				owner: who.clone(),521				name: collection_name,522				mode: mode.clone(),523				mint_mode: false,524				access: AccessMode::Normal,525				description: collection_description,526				decimal_points,527				token_prefix,528				offchain_schema: Vec::new(),529				schema_version: SchemaVersion::ImageURL,530				sponsorship: SponsorshipState::Disabled,531				variable_on_chain_schema: Vec::new(),532				const_on_chain_schema: Vec::new(),533				limits,534				transfers_enabled: true,535			};536537			// Add new collection to map538			<CollectionById<T>>::insert(next_id, new_collection);539540			// call event541			Self::deposit_event(RawEvent::CollectionCreated(next_id, mode.id(), who));542543			Ok(())544		}545546		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.547		///548		/// # Permissions549		///550		/// * Collection Owner.551		///552		/// # Arguments553		///554		/// * collection_id: collection to destroy.555		#[weight = <T as Config>::WeightInfo::destroy_collection()]556		#[transactional]557		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {558559			let sender = ensure_signed(origin)?;560			let collection = Self::get_collection(collection_id)?;561			Self::check_owner_permissions(&collection, &sender)?;562			if !collection.limits.owner_can_destroy {563				fail!(Error::<T>::NoPermission);564			}565566			<AddressTokens<T>>::remove_prefix(collection_id, None);567			<Allowances<T>>::remove_prefix(collection_id, None);568			<Balance<T>>::remove_prefix(collection_id, None);569			<ItemListIndex>::remove(collection_id);570			<AdminList<T>>::remove(collection_id);571			<CollectionById<T>>::remove(collection_id);572			<WhiteList<T>>::remove_prefix(collection_id, None);573574			<NftItemList<T>>::remove_prefix(collection_id, None);575			<FungibleItemList<T>>::remove_prefix(collection_id, None);576			<ReFungibleItemList<T>>::remove_prefix(collection_id, None);577578			<NftTransferBasket<T>>::remove_prefix(collection_id, None);579			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);580			<ReFungibleTransferBasket<T>>::remove_prefix(collection_id, None);581582			<VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);583584			DestroyedCollectionCount::put(DestroyedCollectionCount::get()585				.checked_add(1)586				.ok_or(Error::<T>::NumOverflow)?);587588			Ok(())589		}590591		/// Add an address to white list.592		///593		/// # Permissions594		///595		/// * Collection Owner596		/// * Collection Admin597		///598		/// # Arguments599		///600		/// * collection_id.601		///602		/// * address.603		#[weight = <T as Config>::WeightInfo::add_to_white_list()]604		#[transactional]605		pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{606607			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);608			let collection = Self::get_collection(collection_id)?;609610			Self::toggle_white_list_internal(611				&sender,612				&collection,613				&address,614				true,615			)?;616617			Ok(())618		}619620		/// Remove an address from white list.621		///622		/// # Permissions623		///624		/// * Collection Owner625		/// * Collection Admin626		///627		/// # Arguments628		///629		/// * collection_id.630		///631		/// * address.632		#[weight = <T as Config>::WeightInfo::remove_from_white_list()]633		#[transactional]634		pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{635636			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);637			let collection = Self::get_collection(collection_id)?;638639			Self::toggle_white_list_internal(640				&sender,641				&collection,642				&address,643				false,644			)?;645646			Ok(())647		}648649		/// Toggle between normal and white list access for the methods with access for `Anyone`.650		///651		/// # Permissions652		///653		/// * Collection Owner.654		///655		/// # Arguments656		///657		/// * collection_id.658		///659		/// * mode: [AccessMode]660		#[weight = <T as Config>::WeightInfo::set_public_access_mode()]661		#[transactional]662		pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult663		{664			let sender = ensure_signed(origin)?;665666			let mut target_collection = Self::get_collection(collection_id)?;667			Self::check_owner_permissions(&target_collection, &sender)?;668			target_collection.access = mode;669			target_collection.save()670		}671672		/// Allows Anyone to create tokens if:673		/// * White List is enabled, and674		/// * Address is added to white list, and675		/// * This method was called with True parameter676		///677		/// # Permissions678		/// * Collection Owner679		///680		/// # Arguments681		///682		/// * collection_id.683		///684		/// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.685		#[weight = <T as Config>::WeightInfo::set_mint_permission()]686		#[transactional]687		pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult688		{689			let sender = ensure_signed(origin)?;690691			let mut target_collection = Self::get_collection(collection_id)?;692			Self::check_owner_permissions(&target_collection, &sender)?;693			target_collection.mint_mode = mint_permission;694			target_collection.save()695		}696697		/// Change the owner of the collection.698		///699		/// # Permissions700		///701		/// * Collection Owner.702		///703		/// # Arguments704		///705		/// * collection_id.706		///707		/// * new_owner.708		#[weight = <T as Config>::WeightInfo::change_collection_owner()]709		#[transactional]710		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {711712			let sender = ensure_signed(origin)?;713			let mut target_collection = Self::get_collection(collection_id)?;714			Self::check_owner_permissions(&target_collection, &sender)?;715			target_collection.owner = new_owner;716			target_collection.save()717		}718719		/// Adds an admin of the Collection.720		/// 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.721		///722		/// # Permissions723		///724		/// * Collection Owner.725		/// * Collection Admin.726		///727		/// # Arguments728		///729		/// * collection_id: ID of the Collection to add admin for.730		///731		/// * new_admin_id: Address of new admin to add.732		#[weight = <T as Config>::WeightInfo::add_collection_admin()]733		#[transactional]734		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {735			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);736			let collection = Self::get_collection(collection_id)?;737			Self::check_owner_or_admin_permissions(&collection, &sender)?;738			let mut admin_arr = <AdminList<T>>::get(collection_id);739740			match admin_arr.binary_search(&new_admin_id) {741				Ok(_) => {},742				Err(idx) => {743					ensure!(admin_arr.len() < COLLECTION_ADMINS_LIMIT as usize, Error::<T>::CollectionAdminsLimitExceeded);744					admin_arr.insert(idx, new_admin_id);745					<AdminList<T>>::insert(collection_id, admin_arr);746				}747			}748			Ok(())749		}750751		/// 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.752		///753		/// # Permissions754		///755		/// * Collection Owner.756		/// * Collection Admin.757		///758		/// # Arguments759		///760		/// * collection_id: ID of the Collection to remove admin for.761		///762		/// * account_id: Address of admin to remove.763		#[weight = <T as Config>::WeightInfo::remove_collection_admin()]764		#[transactional]765		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {766			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);767			let collection = Self::get_collection(collection_id)?;768			Self::check_owner_or_admin_permissions(&collection, &sender)?;769			let mut admin_arr = <AdminList<T>>::get(collection_id);770771			if let Ok(idx) = admin_arr.binary_search(&account_id) {772				admin_arr.remove(idx);773				<AdminList<T>>::insert(collection_id, admin_arr);774			}775			Ok(())776		}777778		/// # Permissions779		///780		/// * Collection Owner781		///782		/// # Arguments783		///784		/// * collection_id.785		///786		/// * new_sponsor.787		#[weight = <T as Config>::WeightInfo::set_collection_sponsor()]788		#[transactional]789		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {790			let sender = ensure_signed(origin)?;791			let mut target_collection = Self::get_collection(collection_id)?;792			Self::check_owner_permissions(&target_collection, &sender)?;793794			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);795			target_collection.save()796		}797798		/// # Permissions799		///800		/// * Sponsor.801		///802		/// # Arguments803		///804		/// * collection_id.805		#[weight = <T as Config>::WeightInfo::confirm_sponsorship()]806		#[transactional]807		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {808			let sender = ensure_signed(origin)?;809810			let mut target_collection = Self::get_collection(collection_id)?;811			ensure!(812				target_collection.sponsorship.pending_sponsor() == Some(&sender),813				Error::<T>::ConfirmUnsetSponsorFail814			);815816			target_collection.sponsorship = SponsorshipState::Confirmed(sender);817			target_collection.save()818		}819820		/// Switch back to pay-per-own-transaction model.821		///822		/// # Permissions823		///824		/// * Collection owner.825		///826		/// # Arguments827		///828		/// * collection_id.829		#[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]830		#[transactional]831		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {832			let sender = ensure_signed(origin)?;833834			let mut target_collection = Self::get_collection(collection_id)?;835			Self::check_owner_permissions(&target_collection, &sender)?;836837			target_collection.sponsorship = SponsorshipState::Disabled;838			target_collection.save()839		}840841		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.842		///843		/// # Permissions844		///845		/// * Collection Owner.846		/// * Collection Admin.847		/// * Anyone if848		///     * White List is enabled, and849		///     * Address is added to white list, and850		///     * MintPermission is enabled (see SetMintPermission method)851		///852		/// # Arguments853		///854		/// * collection_id: ID of the collection.855		///856		/// * owner: Address, initial owner of the NFT.857		///858		/// * data: Token data to store on chain.859		// #[weight =860		// (130_000_000 as Weight)861		// .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))862		// .saturating_add(RocksDbWeight::get().reads(10 as Weight))863		// .saturating_add(RocksDbWeight::get().writes(8 as Weight))]864865		#[weight = <T as Config>::WeightInfo::create_item(data.data_size())]866		#[transactional]867		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResult {868			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);869			let collection = Self::get_collection(collection_id)?;870871			Self::create_item_internal(&sender, &collection, &owner, data)?;872873			collection.submit_logs()874		}875876		/// This method creates multiple items in a collection created with CreateCollection method.877		///878		/// # Permissions879		///880		/// * Collection Owner.881		/// * Collection Admin.882		/// * Anyone if883		///     * White List is enabled, and884		///     * Address is added to white list, and885		///     * MintPermission is enabled (see SetMintPermission method)886		///887		/// # Arguments888		///889		/// * collection_id: ID of the collection.890		///891		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].892		///893		/// * owner: Address, initial owner of the NFT.894		#[weight = <T as Config>::WeightInfo::create_item(items_data.iter()895							   .map(|data| { data.data_size() })896							   .sum())]897		#[transactional]898		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResult {899900			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);901			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);902			let collection = Self::get_collection(collection_id)?;903904			Self::create_multiple_items_internal(&sender, &collection, &owner, items_data)?;905906			collection.submit_logs()907		}908909		// TODO! transaction weight910911		/// Set transfers_enabled value for particular collection912		///913		/// # Permissions914		///915		/// * Collection Owner.916		///917		/// # Arguments918		///919		/// * collection_id: ID of the collection.920		///921		/// * value: New flag value.922		#[weight = <T as Config>::WeightInfo::burn_item()]923		#[transactional]924		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {925926			let sender = ensure_signed(origin)?;927			let mut target_collection = Self::get_collection(collection_id)?;928929			Self::check_owner_permissions(&target_collection, &sender)?;930931			target_collection.transfers_enabled = value;932			target_collection.save()933		}934935		/// Destroys a concrete instance of NFT.936		///937		/// # Permissions938		///939		/// * Collection Owner.940		/// * Collection Admin.941		/// * Current NFT Owner.942		///943		/// # Arguments944		///945		/// * collection_id: ID of the collection.946		///947		/// * item_id: ID of NFT to burn.948		#[weight = <T as Config>::WeightInfo::burn_item()]949		#[transactional]950		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {951952			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);953			let target_collection = Self::get_collection(collection_id)?;954955			Self::burn_item_internal(&sender, &target_collection, item_id, value)?;956957			target_collection.submit_logs()958		}959960		/// Change ownership of the token.961		///962		/// # Permissions963		///964		/// * Collection Owner965		/// * Collection Admin966		/// * Current NFT owner967		///968		/// # Arguments969		///970		/// * recipient: Address of token recipient.971		///972		/// * collection_id.973		///974		/// * item_id: ID of the item975		///     * Non-Fungible Mode: Required.976		///     * Fungible Mode: Ignored.977		///     * Re-Fungible Mode: Required.978		///979		/// * value: Amount to transfer.980		///     * Non-Fungible Mode: Ignored981		///     * Fungible Mode: Must specify transferred amount982		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)983		#[weight = <T as Config>::WeightInfo::transfer()]984		#[transactional]985		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {986			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);987			let collection = Self::get_collection(collection_id)?;988989			Self::transfer_internal(&sender, &recipient, &collection, item_id, value)?;990991			collection.submit_logs()992		}993994		/// Set, change, or remove approved address to transfer the ownership of the NFT.995		///996		/// # Permissions997		///998		/// * Collection Owner999		/// * Collection Admin1000		/// * Current NFT owner1001		///1002		/// # Arguments1003		///1004		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1005		///1006		/// * collection_id.1007		///1008		/// * item_id: ID of the item.1009		#[weight = <T as Config>::WeightInfo::approve()]1010		#[transactional]1011		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {1012			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1013			let collection = Self::get_collection(collection_id)?;10141015			Self::approve_internal(&sender, &spender, &collection, item_id, amount)?;10161017			collection.submit_logs()1018		}10191020		/// 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.1021		///1022		/// # Permissions1023		/// * Collection Owner1024		/// * Collection Admin1025		/// * Current NFT owner1026		/// * Address approved by current NFT owner1027		///1028		/// # Arguments1029		///1030		/// * from: Address that owns token.1031		///1032		/// * recipient: Address of token recipient.1033		///1034		/// * collection_id.1035		///1036		/// * item_id: ID of the item.1037		///1038		/// * value: Amount to transfer.1039		#[weight = <T as Config>::WeightInfo::transfer_from()]1040		#[transactional]1041		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {1042			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1043			let collection = Self::get_collection(collection_id)?;10441045			Self::transfer_from_internal(&sender, &from, &recipient, &collection, item_id, value)?;10461047			collection.submit_logs()1048		}1049		// #[weight = 0]1050		//     // let no_perm_mes = "You do not have permissions to modify this collection";1051		//     // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1052		//     // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1053		//     // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);10541055		//     // // on_nft_received  call10561057		//     // Self::transfer(origin, collection_id, item_id, new_owner)?;10581059		//     Ok(())1060		// }10611062		/// Set off-chain data schema.1063		///1064		/// # Permissions1065		///1066		/// * Collection Owner1067		/// * Collection Admin1068		///1069		/// # Arguments1070		///1071		/// * collection_id.1072		///1073		/// * schema: String representing the offchain data schema.1074		#[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1075		#[transactional]1076		pub fn set_variable_meta_data (1077			origin,1078			collection_id: CollectionId,1079			item_id: TokenId,1080			data: Vec<u8>1081		) -> DispatchResult {1082			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);10831084			let collection = Self::get_collection(collection_id)?;10851086			Self::set_variable_meta_data_internal(&sender, &collection, item_id, data)?;10871088			Ok(())1089		}10901091		/// Set schema standard1092		/// ImageURL1093		/// Unique1094		///1095		/// # Permissions1096		///1097		/// * Collection Owner1098		/// * Collection Admin1099		///1100		/// # Arguments1101		///1102		/// * collection_id.1103		///1104		/// * schema: SchemaVersion: enum1105		#[weight = <T as Config>::WeightInfo::set_schema_version()]1106		#[transactional]1107		pub fn set_schema_version(1108			origin,1109			collection_id: CollectionId,1110			version: SchemaVersion1111		) -> DispatchResult {1112			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1113			let mut target_collection = Self::get_collection(collection_id)?;1114			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;1115			target_collection.schema_version = version;1116			target_collection.save()1117		}11181119		/// Set off-chain data schema.1120		///1121		/// # Permissions1122		///1123		/// * Collection Owner1124		/// * Collection Admin1125		///1126		/// # Arguments1127		///1128		/// * collection_id.1129		///1130		/// * schema: String representing the offchain data schema.1131		#[weight = <T as Config>::WeightInfo::set_offchain_schema()]1132		#[transactional]1133		pub fn set_offchain_schema(1134			origin,1135			collection_id: CollectionId,1136			schema: Vec<u8>1137		) -> DispatchResult {1138			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1139			let mut target_collection = Self::get_collection(collection_id)?;1140			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11411142			// check schema limit1143			ensure!(schema.len() as u32 <= OFFCHAIN_SCHEMA_LIMIT, "");11441145			target_collection.offchain_schema = schema;1146			target_collection.save()1147		}11481149		/// Set const on-chain data schema.1150		///1151		/// # Permissions1152		///1153		/// * Collection Owner1154		/// * Collection Admin1155		///1156		/// # Arguments1157		///1158		/// * collection_id.1159		///1160		/// * schema: String representing the const on-chain data schema.1161		#[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1162		#[transactional]1163		pub fn set_const_on_chain_schema (1164			origin,1165			collection_id: CollectionId,1166			schema: Vec<u8>1167		) -> DispatchResult {1168			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1169			let mut target_collection = Self::get_collection(collection_id)?;1170			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11711172			// check schema limit1173			ensure!(schema.len() as u32 <= CONST_ON_CHAIN_SCHEMA_LIMIT, "");11741175			target_collection.const_on_chain_schema = schema;1176			target_collection.save()1177		}11781179		/// Set variable on-chain data schema.1180		///1181		/// # Permissions1182		///1183		/// * Collection Owner1184		/// * Collection Admin1185		///1186		/// # Arguments1187		///1188		/// * collection_id.1189		///1190		/// * schema: String representing the variable on-chain data schema.1191		#[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1192		#[transactional]1193		pub fn set_variable_on_chain_schema (1194			origin,1195			collection_id: CollectionId,1196			schema: Vec<u8>1197		) -> DispatchResult {1198			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1199			let mut target_collection = Self::get_collection(collection_id)?;1200			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;12011202			// check schema limit1203			ensure!(schema.len() as u32 <= VARIABLE_ON_CHAIN_SCHEMA_LIMIT, "");12041205			target_collection.variable_on_chain_schema = schema;1206			target_collection.save()1207		}12081209		#[weight = <T as Config>::WeightInfo::set_collection_limits()]1210		#[transactional]1211		pub fn set_collection_limits(1212			origin,1213			collection_id: u32,1214			new_limits: CollectionLimits<T::BlockNumber>,1215		) -> DispatchResult {1216			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1217			let mut target_collection = Self::get_collection(collection_id)?;1218			Self::check_owner_permissions(&target_collection, sender.as_sub())?;1219			let old_limits = &target_collection.limits;12201221			// collection bounds1222			ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1223				new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP &&1224				new_limits.sponsored_data_size <= CUSTOM_DATA_LIMIT,1225				Error::<T>::CollectionLimitBoundsExceeded);12261227			// token_limit   check  prev1228			ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1229			ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);12301231			ensure!(1232				(old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1233				(old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1234				Error::<T>::OwnerPermissionsCantBeReverted,1235			);12361237			target_collection.limits = new_limits;12381239			target_collection.save()1240		}1241	}1242}12431244impl<T: Config> Module<T> {1245	pub fn create_item_internal(1246		sender: &T::CrossAccountId,1247		collection: &CollectionHandle<T>,1248		owner: &T::CrossAccountId,1249		data: CreateItemData,1250	) -> DispatchResult {1251		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		// Limits check1266		Self::is_correct_transfer(target_collection, recipient)?;12671268		// Transfer permissions check1269		ensure!(1270			Self::is_item_owner(sender, target_collection, item_id)?1271				|| Self::is_owner_or_admin_permissions(target_collection, sender)?,1272			Error::<T>::NoPermission1273		);12741275		if target_collection.access == AccessMode::WhiteList {1276			Self::check_white_list(target_collection, sender)?;1277			Self::check_white_list(target_collection, recipient)?;1278		}12791280		match target_collection.mode {1281			CollectionMode::NFT => Self::transfer_nft(1282				target_collection,1283				item_id,1284				sender.clone(),1285				recipient.clone(),1286			)?,1287			CollectionMode::Fungible(_) => {1288				Self::transfer_fungible(target_collection, value, sender, recipient)?1289			}1290			CollectionMode::ReFungible => Self::transfer_refungible(1291				target_collection,1292				item_id,1293				value,1294				sender.clone(),1295				recipient.clone(),1296			)?,1297			_ => (),1298		};12991300		Self::deposit_event(RawEvent::Transfer(1301			target_collection.id,1302			item_id,1303			sender.clone(),1304			recipient.clone(),1305			value,1306		));13071308		Ok(())1309	}13101311	pub fn approve_internal(1312		sender: &T::CrossAccountId,1313		spender: &T::CrossAccountId,1314		collection: &CollectionHandle<T>,1315		item_id: TokenId,1316		amount: u128,1317	) -> DispatchResult {1318		Self::token_exists(collection, item_id)?;13191320		// Transfer permissions check1321		let bypasses_limits = collection.limits.owner_can_transfer1322			&& Self::is_owner_or_admin_permissions(collection, sender)?;13231324		let allowance_limit = if bypasses_limits {1325			None1326		} else if let Some(amount) = Self::owned_amount(sender, collection, item_id)? {1327			Some(amount)1328		} else {1329			fail!(Error::<T>::NoPermission);1330		};13311332		if collection.access == AccessMode::WhiteList {1333			Self::check_white_list(collection, sender)?;1334			Self::check_white_list(collection, spender)?;1335		}13361337		collection.consume_sload()?;1338		let allowance: u128 = amount1339			.checked_add(<Allowances<T>>::get(1340				collection.id,1341				(item_id, sender.as_sub(), spender.as_sub()),1342			))1343			.ok_or(Error::<T>::NumOverflow)?;1344		if let Some(limit) = allowance_limit {1345			ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1346		}1347		collection.consume_sstore()?;1348		<Allowances<T>>::insert(1349			collection.id,1350			(item_id, sender.as_sub(), spender.as_sub()),1351			allowance,1352		);13531354		if matches!(collection.mode, CollectionMode::NFT) {1355			// TODO: NFT: only one owner may exist for token in ERC7211356			collection.log(ERC721Events::Approval {1357				owner: *sender.as_eth(),1358				approved: *spender.as_eth(),1359				token_id: item_id.into(),1360			})?;1361		}13621363		if matches!(collection.mode, CollectionMode::Fungible(_)) {1364			// TODO: NFT: only one owner may exist for token in ERC201365			collection.log(ERC20Events::Approval {1366				owner: *sender.as_eth(),1367				spender: *spender.as_eth(),1368				value: allowance.into(),1369			})?;1370		}13711372		Self::deposit_event(RawEvent::Approved(1373			collection.id,1374			item_id,1375			sender.clone(),1376			spender.clone(),1377			allowance,1378		));1379		Ok(())1380	}13811382	pub fn transfer_from_internal(1383		sender: &T::CrossAccountId,1384		from: &T::CrossAccountId,1385		recipient: &T::CrossAccountId,1386		collection: &CollectionHandle<T>,1387		item_id: TokenId,1388		amount: u128,1389	) -> DispatchResult {1390		// Check approval1391		collection.consume_sload()?;1392		let approval: u128 =1393			<Allowances<T>>::get(collection.id, (item_id, from.as_sub(), sender.as_sub()));13941395		// Limits check1396		Self::is_correct_transfer(collection, recipient)?;13971398		// Transfer permissions check1399		ensure!(1400			approval >= amount1401				|| (collection.limits.owner_can_transfer1402					&& Self::is_owner_or_admin_permissions(collection, sender)?),1403			Error::<T>::NoPermission1404		);14051406		if collection.access == AccessMode::WhiteList {1407			Self::check_white_list(collection, sender)?;1408			Self::check_white_list(collection, recipient)?;1409		}14101411		// Reduce approval by transferred amount or remove if remaining approval drops to 01412		let allowance = approval.saturating_sub(amount);1413		collection.consume_sstore()?;1414		if allowance > 0 {1415			<Allowances<T>>::insert(1416				collection.id,1417				(item_id, from.as_sub(), sender.as_sub()),1418				allowance,1419			);1420		} else {1421			<Allowances<T>>::remove(collection.id, (item_id, from.as_sub(), sender.as_sub()));1422		}14231424		match collection.mode {1425			CollectionMode::NFT => {1426				Self::transfer_nft(collection, item_id, from.clone(), recipient.clone())?1427			}1428			CollectionMode::Fungible(_) => {1429				Self::transfer_fungible(collection, amount, from, recipient)?1430			}1431			CollectionMode::ReFungible => Self::transfer_refungible(1432				collection,1433				item_id,1434				amount,1435				from.clone(),1436				recipient.clone(),1437			)?,1438			_ => (),1439		};14401441		if matches!(collection.mode, CollectionMode::Fungible(_)) {1442			collection.log(ERC20Events::Approval {1443				owner: *from.as_eth(),1444				spender: *sender.as_eth(),1445				value: allowance.into(),1446			})?;1447		}14481449		Ok(())1450	}14511452	pub fn set_variable_meta_data_internal(1453		sender: &T::CrossAccountId,1454		collection: &CollectionHandle<T>,1455		item_id: TokenId,1456		data: Vec<u8>,1457	) -> DispatchResult {1458		Self::token_exists(collection, item_id)?;14591460		ensure!(1461			CUSTOM_DATA_LIMIT >= data.len() as u32,1462			Error::<T>::TokenVariableDataLimitExceeded1463		);14641465		// Modify permissions check1466		ensure!(1467			Self::is_item_owner(sender, collection, item_id)?1468				|| Self::is_owner_or_admin_permissions(collection, sender)?,1469			Error::<T>::NoPermission1470		);14711472		match collection.mode {1473			CollectionMode::NFT => Self::set_nft_variable_data(collection, item_id, data)?,1474			CollectionMode::ReFungible => {1475				Self::set_re_fungible_variable_data(collection, item_id, data)?1476			}1477			CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1478			_ => fail!(Error::<T>::UnexpectedCollectionType),1479		};14801481		Ok(())1482	}14831484	pub fn create_multiple_items_internal(1485		sender: &T::CrossAccountId,1486		collection: &CollectionHandle<T>,1487		owner: &T::CrossAccountId,1488		items_data: Vec<CreateItemData>,1489	) -> DispatchResult {1490		Self::can_create_items_in_collection(collection, sender, owner, items_data.len() as u32)?;14911492		for data in &items_data {1493			Self::validate_create_item_args(collection, data)?;1494		}1495		for data in &items_data {1496			Self::create_item_no_validation(collection, owner, data.clone())?;1497		}14981499		Ok(())1500	}15011502	pub fn burn_item_internal(1503		sender: &T::CrossAccountId,1504		collection: &CollectionHandle<T>,1505		item_id: TokenId,1506		value: u128,1507	) -> DispatchResult {1508		ensure!(1509			Self::is_item_owner(sender, collection, item_id)?1510				|| (collection.limits.owner_can_transfer1511					&& Self::is_owner_or_admin_permissions(collection, sender)?),1512			Error::<T>::NoPermission1513		);15141515		if collection.access == AccessMode::WhiteList {1516			Self::check_white_list(collection, sender)?;1517		}15181519		match collection.mode {1520			CollectionMode::NFT => Self::burn_nft_item(collection, item_id)?,1521			CollectionMode::Fungible(_) => Self::burn_fungible_item(sender, collection, value)?,1522			CollectionMode::ReFungible => Self::burn_refungible_item(collection, item_id, sender)?,1523			_ => (),1524		};15251526		Ok(())1527	}15281529	pub fn toggle_white_list_internal(1530		sender: &T::CrossAccountId,1531		collection: &CollectionHandle<T>,1532		address: &T::CrossAccountId,1533		whitelisted: bool,1534	) -> DispatchResult {1535		Self::check_owner_or_admin_permissions(collection, sender)?;15361537		if whitelisted {1538			<WhiteList<T>>::insert(collection.id, address.as_sub(), true);1539		} else {1540			<WhiteList<T>>::remove(collection.id, address.as_sub());1541		}15421543		Ok(())1544	}15451546	fn is_correct_transfer(1547		collection: &CollectionHandle<T>,1548		recipient: &T::CrossAccountId,1549	) -> DispatchResult {1550		let collection_id = collection.id;15511552		// check token limit and account token limit1553		collection.consume_sload()?;1554		let account_items: u32 =1555			<AddressTokens<T>>::get(collection_id, recipient.as_sub()).len() as u32;1556		ensure!(1557			collection.limits.account_token_ownership_limit > account_items,1558			Error::<T>::AccountTokenLimitExceeded1559		);15601561		// preliminary transfer check1562		ensure!(collection.transfers_enabled, Error::<T>::TransferNotAllowed);15631564		Ok(())1565	}15661567	fn can_create_items_in_collection(1568		collection: &CollectionHandle<T>,1569		sender: &T::CrossAccountId,1570		owner: &T::CrossAccountId,1571		amount: u32,1572	) -> DispatchResult {1573		let collection_id = collection.id;15741575		// check token limit and account token limit1576		let total_items: u32 = ItemListIndex::get(collection_id)1577			.checked_add(amount)1578			.ok_or(Error::<T>::CollectionTokenLimitExceeded)?;1579		let account_items: u32 = (<AddressTokens<T>>::get(collection_id, owner.as_sub()).len()1580			as u32)1581			.checked_add(amount)1582			.ok_or(Error::<T>::AccountTokenLimitExceeded)?;1583		ensure!(1584			collection.limits.token_limit >= total_items,1585			Error::<T>::CollectionTokenLimitExceeded1586		);1587		ensure!(1588			collection.limits.account_token_ownership_limit >= account_items,1589			Error::<T>::AccountTokenLimitExceeded1590		);15911592		if !Self::is_owner_or_admin_permissions(collection, sender)? {1593			ensure!(collection.mint_mode, Error::<T>::PublicMintingNotAllowed);1594			Self::check_white_list(collection, owner)?;1595			Self::check_white_list(collection, sender)?;1596		}15971598		Ok(())1599	}16001601	fn validate_create_item_args(1602		target_collection: &CollectionHandle<T>,1603		data: &CreateItemData,1604	) -> DispatchResult {1605		match target_collection.mode {1606			CollectionMode::NFT => {1607				if !matches!(data, CreateItemData::NFT(_)) {1608					fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1609				}1610			}1611			CollectionMode::Fungible(_) => {1612				if !matches!(data, CreateItemData::Fungible(_)) {1613					fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1614				}1615			}1616			CollectionMode::ReFungible => {1617				if let CreateItemData::ReFungible(data) = data {1618					// Check refungibility limits1619					ensure!(1620						data.pieces <= MAX_REFUNGIBLE_PIECES,1621						Error::<T>::WrongRefungiblePieces1622					);1623					ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1624				} else {1625					fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1626				}1627			}1628			_ => {1629				fail!(Error::<T>::UnexpectedCollectionType);1630			}1631		};16321633		Ok(())1634	}16351636	fn create_item_no_validation(1637		collection: &CollectionHandle<T>,1638		owner: &T::CrossAccountId,1639		data: CreateItemData,1640	) -> DispatchResult {1641		match data {1642			CreateItemData::NFT(data) => {1643				let item = NftItemType {1644					owner: owner.clone(),1645					const_data: data.const_data.into_inner(),1646					variable_data: data.variable_data.into_inner(),1647				};16481649				Self::add_nft_item(collection, item)?;1650			}1651			CreateItemData::Fungible(data) => {1652				Self::add_fungible_item(collection, owner, data.value)?;1653			}1654			CreateItemData::ReFungible(data) => {1655				let owner_list = vec![Ownership {1656					owner: owner.clone(),1657					fraction: data.pieces,1658				}];16591660				let item = ReFungibleItemType {1661					owner: owner_list,1662					const_data: data.const_data.into_inner(),1663					variable_data: data.variable_data.into_inner(),1664				};16651666				Self::add_refungible_item(collection, item)?;1667			}1668		};16691670		Ok(())1671	}16721673	fn add_fungible_item(1674		collection: &CollectionHandle<T>,1675		owner: &T::CrossAccountId,1676		value: u128,1677	) -> DispatchResult {1678		let collection_id = collection.id;16791680		// Does new owner already have an account?1681		collection.consume_sload()?;1682		let balance: u128 = <FungibleItemList<T>>::get(collection_id, owner.as_sub()).value;16831684		// Mint1685		let item = FungibleItemType {1686			value: balance.checked_add(value).ok_or(Error::<T>::NumOverflow)?,1687		};1688		collection.consume_sstore()?;1689		<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), item);16901691		// Update balance1692		collection.consume_sload()?;1693		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1694			.checked_add(value)1695			.ok_or(Error::<T>::NumOverflow)?;1696		collection.consume_sstore()?;1697		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);16981699		collection.log(ERC20Events::Transfer {1700			from: H160::default(),1701			to: *owner.as_eth(),1702			value: value.into(),1703		})?;1704		Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));1705		Ok(())1706	}17071708	fn add_refungible_item(1709		collection: &CollectionHandle<T>,1710		item: ReFungibleItemType<T::CrossAccountId>,1711	) -> DispatchResult {1712		let collection_id = collection.id;17131714		let current_index = <ItemListIndex>::get(collection_id)1715			.checked_add(1)1716			.ok_or(Error::<T>::NumOverflow)?;1717		let itemcopy = item.clone();17181719		ensure!(item.owner.len() == 1, Error::<T>::BadCreateRefungibleCall,);1720		let item_owner = item.owner.first().expect("only one owner is defined");17211722		let value = item_owner.fraction;1723		let owner = item_owner.owner.clone();17241725		Self::add_token_index(collection, current_index, &owner)?;17261727		<ItemListIndex>::insert(collection_id, current_index);1728		<ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);17291730		// Update balance1731		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1732			.checked_add(value)1733			.ok_or(Error::<T>::NumOverflow)?;1734		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);17351736		Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));1737		Ok(())1738	}17391740	fn add_nft_item(1741		collection: &CollectionHandle<T>,1742		item: NftItemType<T::CrossAccountId>,1743	) -> DispatchResult {1744		let collection_id = collection.id;17451746		let current_index = <ItemListIndex>::get(collection_id)1747			.checked_add(1)1748			.ok_or(Error::<T>::NumOverflow)?;17491750		let item_owner = item.owner.clone();1751		Self::add_token_index(collection, current_index, &item.owner)?;17521753		<ItemListIndex>::insert(collection_id, current_index);1754		<NftItemList<T>>::insert(collection_id, current_index, item);17551756		// Update balance1757		let new_balance = <Balance<T>>::get(collection_id, item_owner.as_sub())1758			.checked_add(1)1759			.ok_or(Error::<T>::NumOverflow)?;1760		<Balance<T>>::insert(collection_id, item_owner.as_sub(), new_balance);17611762		collection.log(ERC721Events::Transfer {1763			from: H160::default(),1764			to: *item_owner.as_eth(),1765			token_id: current_index.into(),1766		})?;1767		Self::deposit_event(RawEvent::ItemCreated(1768			collection_id,1769			current_index,1770			item_owner,1771		));1772		Ok(())1773	}17741775	fn burn_refungible_item(1776		collection: &CollectionHandle<T>,1777		item_id: TokenId,1778		owner: &T::CrossAccountId,1779	) -> DispatchResult {1780		let collection_id = collection.id;17811782		let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id)1783			.ok_or(Error::<T>::TokenNotFound)?;1784		let rft_balance = token1785			.owner1786			.iter()1787			.find(|&i| i.owner == *owner)1788			.ok_or(Error::<T>::TokenNotFound)?;1789		Self::remove_token_index(collection, item_id, owner)?;17901791		// update balance1792		let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.as_sub())1793			.checked_sub(rft_balance.fraction)1794			.ok_or(Error::<T>::NumOverflow)?;1795		<Balance<T>>::insert(collection_id, rft_balance.owner.as_sub(), new_balance);17961797		// Re-create owners list with sender removed1798		let index = token1799			.owner1800			.iter()1801			.position(|i| i.owner == *owner)1802			.expect("owned item is exists");1803		token.owner.remove(index);1804		let owner_count = token.owner.len();18051806		// Burn the token completely if this was the last (only) owner1807		if owner_count == 0 {1808			<ReFungibleItemList<T>>::remove(collection_id, item_id);1809			<VariableMetaDataBasket<T>>::remove(collection_id, item_id);1810		} else {1811			<ReFungibleItemList<T>>::insert(collection_id, item_id, token);1812		}18131814		Ok(())1815	}18161817	fn burn_nft_item(collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1818		let collection_id = collection.id;18191820		let item =1821			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;1822		Self::remove_token_index(collection, item_id, &item.owner)?;18231824		// update balance1825		let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())1826			.checked_sub(1)1827			.ok_or(Error::<T>::NumOverflow)?;1828		<Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);1829		<NftItemList<T>>::remove(collection_id, item_id);1830		<VariableMetaDataBasket<T>>::remove(collection_id, item_id);18311832		Self::deposit_event(RawEvent::ItemDestroyed(collection.id, item_id));1833		Ok(())1834	}18351836	fn burn_fungible_item(1837		owner: &T::CrossAccountId,1838		collection: &CollectionHandle<T>,1839		value: u128,1840	) -> DispatchResult {1841		let collection_id = collection.id;18421843		let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());1844		ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);18451846		// update balance1847		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1848			.checked_sub(value)1849			.ok_or(Error::<T>::NumOverflow)?;1850		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);18511852		if balance.value - value > 0 {1853			balance.value -= value;1854			<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);1855		} else {1856			<FungibleItemList<T>>::remove(collection_id, owner.as_sub());1857		}18581859		collection.log(ERC20Events::Transfer {1860			from: *owner.as_eth(),1861			to: H160::default(),1862			value: value.into(),1863		})?;1864		Ok(())1865	}18661867	pub fn get_collection(1868		collection_id: CollectionId,1869	) -> Result<CollectionHandle<T>, sp_runtime::DispatchError> {1870		Ok(<CollectionHandle<T>>::get(collection_id).ok_or(Error::<T>::CollectionNotFound)?)1871	}18721873	fn check_owner_permissions(1874		target_collection: &CollectionHandle<T>,1875		subject: &T::AccountId,1876	) -> DispatchResult {1877		ensure!(1878			*subject == target_collection.owner,1879			Error::<T>::NoPermission1880		);18811882		Ok(())1883	}18841885	fn is_owner_or_admin_permissions(1886		collection: &CollectionHandle<T>,1887		subject: &T::CrossAccountId,1888	) -> Result<bool, DispatchError> {1889		collection.consume_sload()?;1890		Ok(*subject.as_sub() == collection.owner1891			|| <AdminList<T>>::get(collection.id).contains(subject))1892	}18931894	fn check_owner_or_admin_permissions(1895		collection: &CollectionHandle<T>,1896		subject: &T::CrossAccountId,1897	) -> DispatchResult {1898		ensure!(1899			Self::is_owner_or_admin_permissions(collection, subject)?,1900			Error::<T>::NoPermission1901		);19021903		Ok(())1904	}19051906	fn owned_amount(1907		subject: &T::CrossAccountId,1908		collection: &CollectionHandle<T>,1909		item_id: TokenId,1910	) -> Result<Option<u128>, DispatchError> {1911		collection.consume_sload()?;1912		Ok(Self::owned_amount_unchecked(subject, collection, item_id))1913	}19141915	fn owned_amount_unchecked(1916		subject: &T::CrossAccountId,1917		target_collection: &CollectionHandle<T>,1918		item_id: TokenId,1919	) -> Option<u128> {1920		let collection_id = target_collection.id;19211922		match target_collection.mode {1923			CollectionMode::NFT => {1924				(<NftItemList<T>>::get(collection_id, item_id)?.owner == *subject).then(|| 1)1925			}1926			CollectionMode::Fungible(_) => {1927				Some(<FungibleItemList<T>>::get(collection_id, &subject.as_sub()).value)1928			}1929			CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)?1930				.owner1931				.iter()1932				.find(|i| i.owner == *subject)1933				.map(|i| i.fraction),1934			CollectionMode::Invalid => None,1935		}1936	}19371938	fn is_item_owner(1939		subject: &T::CrossAccountId,1940		target_collection: &CollectionHandle<T>,1941		item_id: TokenId,1942	) -> Result<bool, DispatchError> {1943		Ok(match target_collection.mode {1944			CollectionMode::Fungible(_) => true,1945			_ => Self::owned_amount(subject, target_collection, item_id)?.is_some(),1946		})1947	}19481949	fn check_white_list(1950		collection: &CollectionHandle<T>,1951		address: &T::CrossAccountId,1952	) -> DispatchResult {1953		collection.consume_sload()?;1954		ensure!(1955			<WhiteList<T>>::contains_key(collection.id, address.as_sub()),1956			Error::<T>::AddresNotInWhiteList,1957		);1958		Ok(())1959	}19601961	/// Check if token exists. In case of Fungible, check if there is an entry for1962	/// the owner in fungible balances double map1963	fn token_exists(target_collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1964		let collection_id = target_collection.id;1965		let exists = match target_collection.mode {1966			CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),1967			CollectionMode::Fungible(_) => true,1968			CollectionMode::ReFungible => {1969				<ReFungibleItemList<T>>::contains_key(collection_id, item_id)1970			}1971			_ => false,1972		};19731974		ensure!(exists, Error::<T>::TokenNotFound);1975		Ok(())1976	}19771978	fn transfer_fungible(1979		collection: &CollectionHandle<T>,1980		value: u128,1981		owner: &T::CrossAccountId,1982		recipient: &T::CrossAccountId,1983	) -> DispatchResult {1984		let collection_id = collection.id;19851986		collection.consume_sload()?;1987		collection.consume_sload()?;1988		let mut recipient_balance = <FungibleItemList<T>>::get(collection_id, recipient.as_sub());1989		let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());19901991		recipient_balance.value = recipient_balance1992			.value1993			.checked_add(value)1994			.ok_or(Error::<T>::NumOverflow)?;1995		balance.value = balance1996			.value1997			.checked_sub(value)1998			.ok_or(Error::<T>::TokenValueTooLow)?;19992000		// update balanceOf2001		collection.consume_sstore()?;2002		collection.consume_sstore()?;2003		if balance.value != 0 {2004			<Balance<T>>::insert(collection_id, owner.as_sub(), balance.value);2005		} else {2006			<Balance<T>>::remove(collection_id, owner.as_sub());2007		}2008		<Balance<T>>::insert(collection_id, recipient.as_sub(), recipient_balance.value);20092010		// Reduce or remove sender2011		collection.consume_sstore()?;2012		collection.consume_sstore()?;2013		if balance.value != 0 {2014			<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);2015		} else {2016			<FungibleItemList<T>>::remove(collection_id, owner.as_sub());2017		}2018		<FungibleItemList<T>>::insert(collection_id, recipient.as_sub(), recipient_balance);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		collection.consume_sload()?;2045		let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id)2046			.ok_or(Error::<T>::TokenNotFound)?;20472048		let item = full_item2049			.owner2050			.iter()2051			.find(|i| i.owner == owner)2052			.ok_or(Error::<T>::TokenNotFound)?;2053		let amount = item.fraction;20542055		ensure!(amount >= value, Error::<T>::TokenValueTooLow);20562057		collection.consume_sload()?;2058		// update balance2059		let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2060			.checked_sub(value)2061			.ok_or(Error::<T>::NumOverflow)?;2062		collection.consume_sstore()?;2063		<Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);20642065		collection.consume_sload()?;2066		let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2067			.checked_add(value)2068			.ok_or(Error::<T>::NumOverflow)?;2069		collection.consume_sstore()?;2070		<Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);20712072		let old_owner = item.owner.clone();2073		let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);20742075		let mut new_full_item = full_item.clone();2076		// transfer2077		if amount == value && !new_owner_has_account {2078			// change owner2079			// new owner do not have account2080			new_full_item2081				.owner2082				.iter_mut()2083				.find(|i| i.owner == owner)2084				.expect("old owner does present in refungible")2085				.owner = new_owner.clone();2086			collection.consume_sstore()?;2087			<ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);20882089			// update index collection2090			Self::move_token_index(collection, item_id, &old_owner, &new_owner)?;2091		} else {2092			new_full_item2093				.owner2094				.iter_mut()2095				.find(|i| i.owner == owner)2096				.expect("old owner does present in refungible")2097				.fraction -= value;20982099			// separate amount2100			if new_owner_has_account {2101				// new owner has account2102				new_full_item2103					.owner2104					.iter_mut()2105					.find(|i| i.owner == new_owner)2106					.expect("new owner has account")2107					.fraction += value;2108			} else {2109				// new owner do not have account2110				new_full_item.owner.push(Ownership {2111					owner: new_owner.clone(),2112					fraction: value,2113				});2114				Self::add_token_index(collection, item_id, &new_owner)?;2115			}21162117			collection.consume_sstore()?;2118			<ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2119		}21202121		Self::deposit_event(RawEvent::Transfer(2122			collection.id,2123			item_id,2124			owner,2125			new_owner,2126			amount,2127		));21282129		Ok(())2130	}21312132	fn transfer_nft(2133		collection: &CollectionHandle<T>,2134		item_id: TokenId,2135		sender: T::CrossAccountId,2136		new_owner: T::CrossAccountId,2137	) -> DispatchResult {2138		let collection_id = collection.id;2139		collection.consume_sload()?;2140		let mut item =2141			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;21422143		ensure!(sender == item.owner, Error::<T>::MustBeTokenOwner);21442145		collection.consume_sload()?;2146		// update balance2147		let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2148			.checked_sub(1)2149			.ok_or(Error::<T>::NumOverflow)?;2150		collection.consume_sstore()?;2151		<Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);21522153		collection.consume_sload()?;2154		let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2155			.checked_add(1)2156			.ok_or(Error::<T>::NumOverflow)?;2157		collection.consume_sstore()?;2158		<Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);21592160		// change owner2161		let old_owner = item.owner.clone();2162		item.owner = new_owner.clone();2163		collection.consume_sstore()?;2164		<NftItemList<T>>::insert(collection_id, item_id, item);21652166		// update index collection2167		Self::move_token_index(collection, item_id, &old_owner, &new_owner)?;21682169		collection.log(ERC721Events::Transfer {2170			from: *sender.as_eth(),2171			to: *new_owner.as_eth(),2172			token_id: item_id.into(),2173		})?;2174		Self::deposit_event(RawEvent::Transfer(2175			collection.id,2176			item_id,2177			sender,2178			new_owner,2179			1,2180		));21812182		Ok(())2183	}21842185	fn set_re_fungible_variable_data(2186		collection: &CollectionHandle<T>,2187		item_id: TokenId,2188		data: Vec<u8>,2189	) -> DispatchResult {2190		let collection_id = collection.id;2191		let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id)2192			.ok_or(Error::<T>::TokenNotFound)?;21932194		item.variable_data = data;21952196		<ReFungibleItemList<T>>::insert(collection_id, item_id, item);21972198		Ok(())2199	}22002201	fn set_nft_variable_data(2202		collection: &CollectionHandle<T>,2203		item_id: TokenId,2204		data: Vec<u8>,2205	) -> DispatchResult {2206		let collection_id = collection.id;2207		let mut item =2208			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;22092210		item.variable_data = data;22112212		<NftItemList<T>>::insert(collection_id, item_id, item);22132214		Ok(())2215	}22162217	#[allow(dead_code)]2218	fn init_collection(item: &Collection<T>) {2219		// check params2220		assert!(2221			item.decimal_points <= MAX_DECIMAL_POINTS,2222			"decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2223		);2224		assert!(2225			item.name.len() <= 64,2226			"Collection name can not be longer than 63 char"2227		);2228		assert!(2229			item.name.len() <= 256,2230			"Collection description can not be longer than 255 char"2231		);2232		assert!(2233			item.token_prefix.len() <= 16,2234			"Token prefix can not be longer than 15 char"2235		);22362237		// Generate next collection ID2238		let next_id = CreatedCollectionCount::get().checked_add(1).unwrap();22392240		CreatedCollectionCount::put(next_id);2241	}22422243	#[allow(dead_code)]2244	fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::CrossAccountId>) {2245		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();22462247		Self::add_token_index(2248			&CollectionHandle::get(collection_id).unwrap(),2249			current_index,2250			&item.owner,2251		)2252		.unwrap();22532254		<ItemListIndex>::insert(collection_id, current_index);22552256		// Update balance2257		let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2258			.checked_add(1)2259			.unwrap();2260		<Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2261	}22622263	#[allow(dead_code)]2264	fn init_fungible_token(2265		collection_id: CollectionId,2266		owner: &T::CrossAccountId,2267		item: &FungibleItemType,2268	) {2269		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();22702271		Self::add_token_index(2272			&CollectionHandle::get(collection_id).unwrap(),2273			current_index,2274			owner,2275		)2276		.unwrap();22772278		<ItemListIndex>::insert(collection_id, current_index);22792280		// Update balance2281		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2282			.checked_add(item.value)2283			.unwrap();2284		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2285	}22862287	#[allow(dead_code)]2288	fn init_refungible_token(2289		collection_id: CollectionId,2290		item: &ReFungibleItemType<T::CrossAccountId>,2291	) {2292		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();22932294		let value = item.owner.first().unwrap().fraction;2295		let owner = item.owner.first().unwrap().owner.clone();22962297		Self::add_token_index(2298			&CollectionHandle::get(collection_id).unwrap(),2299			current_index,2300			&owner,2301		)2302		.unwrap();23032304		<ItemListIndex>::insert(collection_id, current_index);23052306		// Update balance2307		let new_balance = <Balance<T>>::get(collection_id, &owner.as_sub())2308			.checked_add(value)2309			.unwrap();2310		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2311	}23122313	fn add_token_index(2314		collection: &CollectionHandle<T>,2315		item_index: TokenId,2316		owner: &T::CrossAccountId,2317	) -> DispatchResult {2318		// add to account limit2319		collection.consume_sload()?;2320		if <AccountItemCount<T>>::contains_key(owner.as_sub()) {2321			// bound Owned tokens by a single address2322			collection.consume_sload()?;2323			let count = <AccountItemCount<T>>::get(owner.as_sub());2324			ensure!(2325				count < ACCOUNT_TOKEN_OWNERSHIP_LIMIT,2326				Error::<T>::AddressOwnershipLimitExceeded2327			);23282329			collection.consume_sstore()?;2330			<AccountItemCount<T>>::insert(2331				owner.as_sub(),2332				count.checked_add(1).ok_or(Error::<T>::NumOverflow)?,2333			);2334		} else {2335			collection.consume_sstore()?;2336			<AccountItemCount<T>>::insert(owner.as_sub(), 1);2337		}23382339		collection.consume_sload()?;2340		let list_exists = <AddressTokens<T>>::contains_key(collection.id, owner.as_sub());2341		if list_exists {2342			collection.consume_sload()?;2343			let mut list = <AddressTokens<T>>::get(collection.id, owner.as_sub());2344			let item_contains = list.contains(&item_index.clone());23452346			if !item_contains {2347				list.push(item_index);2348			}23492350			collection.consume_sstore()?;2351			<AddressTokens<T>>::insert(collection.id, owner.as_sub(), list);2352		} else {2353			let itm = vec![item_index];2354			collection.consume_sstore()?;2355			<AddressTokens<T>>::insert(collection.id, owner.as_sub(), itm);2356		}23572358		Ok(())2359	}23602361	fn remove_token_index(2362		collection: &CollectionHandle<T>,2363		item_index: TokenId,2364		owner: &T::CrossAccountId,2365	) -> DispatchResult {2366		// update counter2367		collection.consume_sload()?;2368		collection.consume_sstore()?;2369		<AccountItemCount<T>>::insert(2370			owner.as_sub(),2371			<AccountItemCount<T>>::get(owner.as_sub())2372				.checked_sub(1)2373				.ok_or(Error::<T>::NumOverflow)?,2374		);23752376		collection.consume_sload()?;2377		let list_exists = <AddressTokens<T>>::contains_key(collection.id, owner.as_sub());2378		if list_exists {2379			collection.consume_sload()?;2380			let mut list = <AddressTokens<T>>::get(collection.id, owner.as_sub());2381			let item_contains = list.contains(&item_index.clone());23822383			if item_contains {2384				list.retain(|&item| item != item_index);2385				collection.consume_sstore()?;2386				<AddressTokens<T>>::insert(collection.id, owner.as_sub(), list);2387			}2388		}23892390		Ok(())2391	}23922393	fn move_token_index(2394		collection: &CollectionHandle<T>,2395		item_index: TokenId,2396		old_owner: &T::CrossAccountId,2397		new_owner: &T::CrossAccountId,2398	) -> DispatchResult {2399		Self::remove_token_index(collection, item_index, old_owner)?;2400		Self::add_token_index(collection, item_index, new_owner)?;24012402		Ok(())2403	}2404}24052406sp_api::decl_runtime_apis! {2407	pub trait NftApi {2408		/// Used for ethereum integration2409		fn eth_contract_code(account: H160) -> Option<Vec<u8>>;2410	}2411}