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

difftreelog

source

pallets/nft/src/lib.rs69.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::sp_std::prelude::Vec;38use core::ops::{Deref, DerefMut};39use nft_data_structs::{40	MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, MAX_REFUNGIBLE_PIECES,41	CUSTOM_DATA_LIMIT, COLLECTION_NUMBER_LIMIT, ACCOUNT_TOKEN_OWNERSHIP_LIMIT,42	VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, COLLECTION_ADMINS_LIMIT,43	OFFCHAIN_SCHEMA_LIMIT, AccessMode, Collection, CreateItemData, CollectionLimits, CollectionId,44	CollectionMode, TokenId, SchemaVersion, SponsorshipState, Ownership, NftItemType,45	FungibleItemType, ReFungibleItemType,46};4748#[cfg(test)]49mod mock;5051#[cfg(test)]52mod tests;5354mod default_weights;55mod eth;56mod sponsorship;57pub use sponsorship::NftSponsorshipHandler;58pub use eth::sponsoring::NftEthSponsorshipHandler;5960pub use eth::NftErcSupport;61pub use eth::account::*;62use eth::erc::{ERC20Events, ERC721Events};6364#[cfg(feature = "runtime-benchmarks")]65mod benchmarking;6667pub trait WeightInfo {68	fn create_collection() -> Weight;69	fn destroy_collection() -> Weight;70	fn add_to_white_list() -> Weight;71	fn remove_from_white_list() -> Weight;72	fn set_public_access_mode() -> Weight;73	fn set_mint_permission() -> Weight;74	fn change_collection_owner() -> Weight;75	fn add_collection_admin() -> Weight;76	fn remove_collection_admin() -> Weight;77	fn set_collection_sponsor() -> Weight;78	fn confirm_sponsorship() -> Weight;79	fn remove_collection_sponsor() -> Weight;80	fn create_item(s: usize) -> Weight;81	fn burn_item() -> Weight;82	fn transfer() -> Weight;83	fn approve() -> Weight;84	fn transfer_from() -> Weight;85	fn set_offchain_schema() -> Weight;86	fn set_const_on_chain_schema() -> Weight;87	fn set_variable_on_chain_schema() -> Weight;88	fn set_variable_meta_data() -> Weight;89	fn enable_contract_sponsoring() -> Weight;90	fn set_schema_version() -> Weight;91	fn set_contract_sponsoring_rate_limit() -> Weight;92	fn set_variable_meta_data_sponsoring_rate_limit() -> Weight;93	fn toggle_contract_white_list() -> Weight;94	fn add_to_contract_white_list() -> Weight;95	fn remove_from_contract_white_list() -> Weight;96	fn set_collection_limits() -> Weight;97}9899decl_error! {100	/// Error for non-fungible-token module.101	pub enum Error for Module<T: Config> {102		/// Total collections bound exceeded.103		TotalCollectionsLimitExceeded,104		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.105		CollectionDecimalPointLimitExceeded,106		/// Collection name can not be longer than 63 char.107		CollectionNameLimitExceeded,108		/// Collection description can not be longer than 255 char.109		CollectionDescriptionLimitExceeded,110		/// Token prefix can not be longer than 15 char.111		CollectionTokenPrefixLimitExceeded,112		/// This collection does not exist.113		CollectionNotFound,114		/// Item not exists.115		TokenNotFound,116		/// Admin not found117		AdminNotFound,118		/// Arithmetic calculation overflow.119		NumOverflow,120		/// Account already has admin role.121		AlreadyAdmin,122		/// You do not own this collection.123		NoPermission,124		/// This address is not set as sponsor, use setCollectionSponsor first.125		ConfirmUnsetSponsorFail,126		/// Collection is not in mint mode.127		PublicMintingNotAllowed,128		/// Sender parameter and item owner must be equal.129		MustBeTokenOwner,130		/// Item balance not enough.131		TokenValueTooLow,132		/// Size of item is too large.133		NftSizeLimitExceeded,134		/// No approve found135		ApproveNotFound,136		/// Requested value more than approved.137		TokenValueNotEnough,138		/// Only approved addresses can call this method.139		ApproveRequired,140		/// Address is not in white list.141		AddresNotInWhiteList,142		/// Number of collection admins bound exceeded.143		CollectionAdminsLimitExceeded,144		/// Owned tokens by a single address bound exceeded.145		AddressOwnershipLimitExceeded,146		/// Length of items properties must be greater than 0.147		EmptyArgument,148		/// const_data exceeded data limit.149		TokenConstDataLimitExceeded,150		/// variable_data exceeded data limit.151		TokenVariableDataLimitExceeded,152		/// Not NFT item data used to mint in NFT collection.153		NotNftDataUsedToMintNftCollectionToken,154		/// Not Fungible item data used to mint in Fungible collection.155		NotFungibleDataUsedToMintFungibleCollectionToken,156		/// Not Re Fungible item data used to mint in Re Fungible collection.157		NotReFungibleDataUsedToMintReFungibleCollectionToken,158		/// Unexpected collection type.159		UnexpectedCollectionType,160		/// Can't store metadata in fungible tokens.161		CantStoreMetadataInFungibleTokens,162		/// Collection token limit exceeded163		CollectionTokenLimitExceeded,164		/// Account token limit exceeded per collection165		AccountTokenLimitExceeded,166		/// Collection limit bounds per collection exceeded167		CollectionLimitBoundsExceeded,168		/// Tried to enable permissions which are only permitted to be disabled169		OwnerPermissionsCantBeReverted,170		/// Schema data size limit bound exceeded171		SchemaDataLimitExceeded,172		/// Maximum refungibility exceeded173		WrongRefungiblePieces,174		/// createRefungible should be called with one owner175		BadCreateRefungibleCall,176		/// Gas limit exceeded177		OutOfGas,178		/// Collection settings not allowing items transferring179		TransferNotAllowed,180		/// Can't transfer tokens to ethereum zero address181		AddressIsZero,182	}183}184185#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]186pub struct CollectionHandle<T: Config> {187	pub id: CollectionId,188	collection: Collection<T>,189	recorder: pallet_evm_coder_substrate::SubstrateRecorder<T>,190}191impl<T: Config> CollectionHandle<T> {192	pub fn get_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {193		<CollectionById<T>>::get(id).map(|collection| Self {194			id,195			collection,196			recorder: pallet_evm_coder_substrate::SubstrateRecorder::new(197				eth::collection_id_to_address(id),198				gas_limit,199			),200		})201	}202	pub fn get(id: CollectionId) -> Option<Self> {203		Self::get_with_gas_limit(id, u64::MAX)204	}205	pub fn log(&self, log: impl evm_coder::ToLog) -> DispatchResult {206		self.recorder.log_sub(log)207	}208	fn consume_gas(&self, gas: u64) -> DispatchResult {209		self.recorder.consume_gas_sub(gas)210	}211	pub fn submit_logs(self) -> DispatchResult {212		self.recorder.submit_logs()213	}214	pub fn save(self) -> DispatchResult {215		self.recorder.submit_logs()?;216		<CollectionById<T>>::insert(self.id, self.collection);217		Ok(())218	}219}220impl<T: Config> Deref for CollectionHandle<T> {221	type Target = Collection<T>;222223	fn deref(&self) -> &Self::Target {224		&self.collection225	}226}227228impl<T: Config> DerefMut for CollectionHandle<T> {229	fn deref_mut(&mut self) -> &mut Self::Target {230		&mut self.collection231	}232}233234pub trait Config: system::Config + pallet_evm_coder_substrate::Config + Sized {235	type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;236237	/// Weight information for extrinsics in this pallet.238	type WeightInfo: WeightInfo;239240	type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;241	type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;242243	type CrossAccountId: CrossAccountId<Self::AccountId>;244	type Currency: Currency<Self::AccountId>;245	type CollectionCreationPrice: Get<246		<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,247	>;248	type TreasuryAccountId: Get<Self::AccountId>;249}250251// # Used definitions252//253// ## User control levels254//255// chain-controlled - key is uncontrolled by user256//                    i.e autoincrementing index257//                    can use non-cryptographic hash258// real - key is controlled by user259//        but it is hard to generate enough colliding values, i.e owner of signed txs260//        can use non-cryptographic hash261// controlled - key is completly controlled by users262//              i.e maps with mutable keys263//              should use cryptographic hash264//265// ## User control level downgrade reasons266//267// ?1 - chain-controlled -> controlled268//      collections/tokens can be destroyed, resulting in massive holes269// ?2 - chain-controlled -> controlled270//      same as ?1, but can be only added, resulting in easier exploitation271// ?3 - real -> controlled272//      no confirmation required, so addresses can be easily generated273decl_storage! {274	trait Store for Module<T: Config> as Nft {275276		//#region Private members277		/// Id of next collection278		CreatedCollectionCount: u32;279		/// Used for migrations280		ChainVersion: u64;281		/// Id of last collection token282		/// Collection id (controlled?1)283		ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;284		//#endregion285286		//#region Bound counters287		/// Amount of collections destroyed, used for total amount tracking with288		/// CreatedCollectionCount289		DestroyedCollectionCount: u32;290		/// Total amount of account owned tokens (NFTs + RFTs + unique fungibles)291		/// Account id (real)292		pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;293		//#endregion294295		//#region Basic collections296		/// Collection info297		/// Collection id (controlled?1)298		pub CollectionById get(fn collection_id) config(): map hasher(blake2_128_concat) CollectionId => Option<Collection<T>> = None;299		/// List of collection admins300		/// Collection id (controlled?2)301		pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec<T::CrossAccountId>;302		/// Whitelisted collection users303		/// Collection id (controlled?2), user id (controlled?3)304		pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool;305		//#endregion306307		/// How many of collection items user have308		/// Collection id (controlled?2), account id (real)309		pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128;310311		/// Amount of items which spender can transfer out of owners account (via transferFrom)312		/// Collection id (controlled?2), (token id (controlled ?2) + owner account id (real) + spender account id (controlled?3))313		/// TODO: Off chain worker should remove from this map when token gets removed314		pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128;315316		//#region Item collections317		/// Collection id (controlled?2), token id (controlled?1)318		pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<NftItemType<T::CrossAccountId>>;319		/// Collection id (controlled?2), owner (controlled?2)320		pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType;321		/// Collection id (controlled?2), token id (controlled?1)322		pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<ReFungibleItemType<T::CrossAccountId>>;323		//#endregion324325		//#region Index list326		/// Collection id (controlled?2), tokens owner (controlled?2)327		pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;328		//#endregion329330		//#region Tokens transfer rate limit baskets331		/// (Collection id (controlled?2), who created (real))332		/// TODO: Off chain worker should remove from this map when collection gets removed333		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;334		/// Collection id (controlled?2), token id (controlled?2)335		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;336		/// Collection id (controlled?2), owning user (real)337		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;338		/// Collection id (controlled?2), token id (controlled?2)339		pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;340		//#endregion341342		/// Variable metadata sponsoring343		/// Collection id (controlled?2), token id (controlled?2)344		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;345	}346	add_extra_genesis {347		build(|config: &GenesisConfig<T>| {348			// Modification of storage349			for (_num, _c) in &config.collection_id {350				<Module<T>>::init_collection(_c);351			}352353			for (_num, _c, _i) in &config.nft_item_id {354				<Module<T>>::init_nft_token(*_c, _i);355			}356357			for (collection_id, account_id, fungible_item) in &config.fungible_item_id {358				<Module<T>>::init_fungible_token(*collection_id, &T::CrossAccountId::from_sub(account_id.clone()), fungible_item);359			}360361			for (_num, _c, _i) in &config.refungible_item_id {362				<Module<T>>::init_refungible_token(*_c, _i);363			}364		})365	}366}367368decl_event!(369	pub enum Event<T>370	where371		AccountId = <T as frame_system::Config>::AccountId,372		CrossAccountId = <T as Config>::CrossAccountId,373	{374		/// New collection was created375		///376		/// # Arguments377		///378		/// * collection_id: Globally unique identifier of newly created collection.379		///380		/// * mode: [CollectionMode] converted into u8.381		///382		/// * account_id: Collection owner.383		CollectionCreated(CollectionId, u8, AccountId),384385		/// New item was created.386		///387		/// # Arguments388		///389		/// * collection_id: Id of the collection where item was created.390		///391		/// * item_id: Id of an item. Unique within the collection.392		///393		/// * recipient: Owner of newly created item394		ItemCreated(CollectionId, TokenId, CrossAccountId),395396		/// Collection item was burned.397		///398		/// # Arguments399		///400		/// collection_id.401		///402		/// item_id: Identifier of burned NFT.403		ItemDestroyed(CollectionId, TokenId),404405		/// Item was transferred406		///407		/// * collection_id: Id of collection to which item is belong408		///409		/// * item_id: Id of an item410		///411		/// * sender: Original owner of item412		///413		/// * recipient: New owner of item414		///415		/// * amount: Always 1 for NFT416		Transfer(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),417418		/// * collection_id419		///420		/// * item_id421		///422		/// * sender423		///424		/// * spender425		///426		/// * amount427		Approved(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),428	}429);430431decl_module! {432	pub struct Module<T: Config> for enum Call433	where434		origin: T::Origin435	{436		fn deposit_event() = default;437		const CollectionAdminsLimit: u64 = COLLECTION_ADMINS_LIMIT;438		type Error = Error<T>;439440		fn on_initialize(_now: T::BlockNumber) -> Weight {441			0442		}443444		/// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.445		///446		/// # Permissions447		///448		/// * Anyone.449		///450		/// # Arguments451		///452		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.453		///454		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.455		///456		/// * token_prefix: UTF-8 string with token prefix.457		///458		/// * mode: [CollectionMode] collection type and type dependent data.459		// returns collection ID460		#[weight = <T as Config>::WeightInfo::create_collection()]461		#[transactional]462		pub fn create_collection(origin,463								 collection_name: Vec<u16>,464								 collection_description: Vec<u16>,465								 token_prefix: Vec<u8>,466								 mode: CollectionMode) -> DispatchResult {467468			// Anyone can create a collection469			let who = ensure_signed(origin)?;470471			// Take a (non-refundable) deposit of collection creation472			let mut imbalance = <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();473			imbalance.subsume(<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(474				&T::TreasuryAccountId::get(),475				T::CollectionCreationPrice::get(),476			));477			<T as Config>::Currency::settle(478				&who,479				imbalance,480				WithdrawReasons::TRANSFER,481				ExistenceRequirement::KeepAlive,482			).map_err(|_| Error::<T>::NoPermission)?;483484			let decimal_points = match mode {485				CollectionMode::Fungible(points) => points,486				_ => 0487			};488489			let created_count = CreatedCollectionCount::get();490			let destroyed_count = DestroyedCollectionCount::get();491492			// bound Total number of collections493			ensure!(created_count - destroyed_count < COLLECTION_NUMBER_LIMIT, Error::<T>::TotalCollectionsLimitExceeded);494495			// check params496			ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);497			ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);498			ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);499			ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);500501			// Generate next collection ID502			let next_id = created_count503				.checked_add(1)504				.ok_or(Error::<T>::NumOverflow)?;505506			CreatedCollectionCount::put(next_id);507508			let limits = CollectionLimits {509				sponsored_data_size: CUSTOM_DATA_LIMIT,510				..Default::default()511			};512513			// Create new collection514			let new_collection = Collection {515				owner: who.clone(),516				name: collection_name,517				mode: mode.clone(),518				mint_mode: false,519				access: AccessMode::Normal,520				description: collection_description,521				decimal_points,522				token_prefix,523				offchain_schema: Vec::new(),524				schema_version: SchemaVersion::ImageURL,525				sponsorship: SponsorshipState::Disabled,526				variable_on_chain_schema: Vec::new(),527				const_on_chain_schema: Vec::new(),528				limits,529				transfers_enabled: true,530			};531532			// Add new collection to map533			<CollectionById<T>>::insert(next_id, new_collection);534535			// call event536			Self::deposit_event(RawEvent::CollectionCreated(next_id, mode.id(), who));537538			Ok(())539		}540541		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.542		///543		/// # Permissions544		///545		/// * Collection Owner.546		///547		/// # Arguments548		///549		/// * collection_id: collection to destroy.550		#[weight = <T as Config>::WeightInfo::destroy_collection()]551		#[transactional]552		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {553554			let sender = ensure_signed(origin)?;555			let collection = Self::get_collection(collection_id)?;556			Self::check_owner_permissions(&collection, &sender)?;557			if !collection.limits.owner_can_destroy {558				fail!(Error::<T>::NoPermission);559			}560561			<AddressTokens<T>>::remove_prefix(collection_id, None);562			<Allowances<T>>::remove_prefix(collection_id, None);563			<Balance<T>>::remove_prefix(collection_id, None);564			<ItemListIndex>::remove(collection_id);565			<AdminList<T>>::remove(collection_id);566			<CollectionById<T>>::remove(collection_id);567			<WhiteList<T>>::remove_prefix(collection_id, None);568569			<NftItemList<T>>::remove_prefix(collection_id, None);570			<FungibleItemList<T>>::remove_prefix(collection_id, None);571			<ReFungibleItemList<T>>::remove_prefix(collection_id, None);572573			<NftTransferBasket<T>>::remove_prefix(collection_id, None);574			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);575			<ReFungibleTransferBasket<T>>::remove_prefix(collection_id, None);576577			<VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);578579			DestroyedCollectionCount::put(DestroyedCollectionCount::get()580				.checked_add(1)581				.ok_or(Error::<T>::NumOverflow)?);582583			Ok(())584		}585586		/// Add an address to white list.587		///588		/// # Permissions589		///590		/// * Collection Owner591		/// * Collection Admin592		///593		/// # Arguments594		///595		/// * collection_id.596		///597		/// * address.598		#[weight = <T as Config>::WeightInfo::add_to_white_list()]599		#[transactional]600		pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{601602			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);603			let collection = Self::get_collection(collection_id)?;604605			Self::toggle_white_list_internal(606				&sender,607				&collection,608				&address,609				true,610			)?;611612			Ok(())613		}614615		/// Remove an address from white list.616		///617		/// # Permissions618		///619		/// * Collection Owner620		/// * Collection Admin621		///622		/// # Arguments623		///624		/// * collection_id.625		///626		/// * address.627		#[weight = <T as Config>::WeightInfo::remove_from_white_list()]628		#[transactional]629		pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{630631			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);632			let collection = Self::get_collection(collection_id)?;633634			Self::toggle_white_list_internal(635				&sender,636				&collection,637				&address,638				false,639			)?;640641			Ok(())642		}643644		/// Toggle between normal and white list access for the methods with access for `Anyone`.645		///646		/// # Permissions647		///648		/// * Collection Owner.649		///650		/// # Arguments651		///652		/// * collection_id.653		///654		/// * mode: [AccessMode]655		#[weight = <T as Config>::WeightInfo::set_public_access_mode()]656		#[transactional]657		pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult658		{659			let sender = ensure_signed(origin)?;660661			let mut target_collection = Self::get_collection(collection_id)?;662			Self::check_owner_permissions(&target_collection, &sender)?;663			target_collection.access = mode;664			target_collection.save()665		}666667		/// Allows Anyone to create tokens if:668		/// * White List is enabled, and669		/// * Address is added to white list, and670		/// * This method was called with True parameter671		///672		/// # Permissions673		/// * Collection Owner674		///675		/// # Arguments676		///677		/// * collection_id.678		///679		/// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.680		#[weight = <T as Config>::WeightInfo::set_mint_permission()]681		#[transactional]682		pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult683		{684			let sender = ensure_signed(origin)?;685686			let mut target_collection = Self::get_collection(collection_id)?;687			Self::check_owner_permissions(&target_collection, &sender)?;688			target_collection.mint_mode = mint_permission;689			target_collection.save()690		}691692		/// Change the owner of the collection.693		///694		/// # Permissions695		///696		/// * Collection Owner.697		///698		/// # Arguments699		///700		/// * collection_id.701		///702		/// * new_owner.703		#[weight = <T as Config>::WeightInfo::change_collection_owner()]704		#[transactional]705		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {706707			let sender = ensure_signed(origin)?;708			let mut target_collection = Self::get_collection(collection_id)?;709			Self::check_owner_permissions(&target_collection, &sender)?;710			target_collection.owner = new_owner;711			target_collection.save()712		}713714		/// Adds an admin of the Collection.715		/// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership.716		///717		/// # Permissions718		///719		/// * Collection Owner.720		/// * Collection Admin.721		///722		/// # Arguments723		///724		/// * collection_id: ID of the Collection to add admin for.725		///726		/// * new_admin_id: Address of new admin to add.727		#[weight = <T as Config>::WeightInfo::add_collection_admin()]728		#[transactional]729		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {730			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);731			let collection = Self::get_collection(collection_id)?;732			Self::check_owner_or_admin_permissions(&collection, &sender)?;733			let mut admin_arr = <AdminList<T>>::get(collection_id);734735			match admin_arr.binary_search(&new_admin_id) {736				Ok(_) => {},737				Err(idx) => {738					ensure!(admin_arr.len() < COLLECTION_ADMINS_LIMIT as usize, Error::<T>::CollectionAdminsLimitExceeded);739					admin_arr.insert(idx, new_admin_id);740					<AdminList<T>>::insert(collection_id, admin_arr);741				}742			}743			Ok(())744		}745746		/// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.747		///748		/// # Permissions749		///750		/// * Collection Owner.751		/// * Collection Admin.752		///753		/// # Arguments754		///755		/// * collection_id: ID of the Collection to remove admin for.756		///757		/// * account_id: Address of admin to remove.758		#[weight = <T as Config>::WeightInfo::remove_collection_admin()]759		#[transactional]760		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {761			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);762			let collection = Self::get_collection(collection_id)?;763			Self::check_owner_or_admin_permissions(&collection, &sender)?;764			let mut admin_arr = <AdminList<T>>::get(collection_id);765766			if let Ok(idx) = admin_arr.binary_search(&account_id) {767				admin_arr.remove(idx);768				<AdminList<T>>::insert(collection_id, admin_arr);769			}770			Ok(())771		}772773		/// # Permissions774		///775		/// * Collection Owner776		///777		/// # Arguments778		///779		/// * collection_id.780		///781		/// * new_sponsor.782		#[weight = <T as Config>::WeightInfo::set_collection_sponsor()]783		#[transactional]784		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {785			let sender = ensure_signed(origin)?;786			let mut target_collection = Self::get_collection(collection_id)?;787			Self::check_owner_permissions(&target_collection, &sender)?;788789			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);790			target_collection.save()791		}792793		/// # Permissions794		///795		/// * Sponsor.796		///797		/// # Arguments798		///799		/// * collection_id.800		#[weight = <T as Config>::WeightInfo::confirm_sponsorship()]801		#[transactional]802		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {803			let sender = ensure_signed(origin)?;804805			let mut target_collection = Self::get_collection(collection_id)?;806			ensure!(807				target_collection.sponsorship.pending_sponsor() == Some(&sender),808				Error::<T>::ConfirmUnsetSponsorFail809			);810811			target_collection.sponsorship = SponsorshipState::Confirmed(sender);812			target_collection.save()813		}814815		/// Switch back to pay-per-own-transaction model.816		///817		/// # Permissions818		///819		/// * Collection owner.820		///821		/// # Arguments822		///823		/// * collection_id.824		#[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]825		#[transactional]826		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {827			let sender = ensure_signed(origin)?;828829			let mut target_collection = Self::get_collection(collection_id)?;830			Self::check_owner_permissions(&target_collection, &sender)?;831832			target_collection.sponsorship = SponsorshipState::Disabled;833			target_collection.save()834		}835836		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.837		///838		/// # Permissions839		///840		/// * Collection Owner.841		/// * Collection Admin.842		/// * Anyone if843		///     * White List is enabled, and844		///     * Address is added to white list, and845		///     * MintPermission is enabled (see SetMintPermission method)846		///847		/// # Arguments848		///849		/// * collection_id: ID of the collection.850		///851		/// * owner: Address, initial owner of the NFT.852		///853		/// * data: Token data to store on chain.854		// #[weight =855		// (130_000_000 as Weight)856		// .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))857		// .saturating_add(RocksDbWeight::get().reads(10 as Weight))858		// .saturating_add(RocksDbWeight::get().writes(8 as Weight))]859860		#[weight = <T as Config>::WeightInfo::create_item(data.data_size())]861		#[transactional]862		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResult {863			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);864			let collection = Self::get_collection(collection_id)?;865866			Self::create_item_internal(&sender, &collection, &owner, data)?;867868			collection.submit_logs()869		}870871		/// This method creates multiple items in a collection created with CreateCollection method.872		///873		/// # Permissions874		///875		/// * Collection Owner.876		/// * Collection Admin.877		/// * Anyone if878		///     * White List is enabled, and879		///     * Address is added to white list, and880		///     * MintPermission is enabled (see SetMintPermission method)881		///882		/// # Arguments883		///884		/// * collection_id: ID of the collection.885		///886		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].887		///888		/// * owner: Address, initial owner of the NFT.889		#[weight = <T as Config>::WeightInfo::create_item(items_data.iter()890							   .map(|data| { data.data_size() })891							   .sum())]892		#[transactional]893		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResult {894895			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);896			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);897			let collection = Self::get_collection(collection_id)?;898899			Self::create_multiple_items_internal(&sender, &collection, &owner, items_data)?;900901			collection.submit_logs()902		}903904		// TODO! transaction weight905906		/// Set transfers_enabled value for particular collection907		///908		/// # Permissions909		///910		/// * Collection Owner.911		///912		/// # Arguments913		///914		/// * collection_id: ID of the collection.915		///916		/// * value: New flag value.917		#[weight = <T as Config>::WeightInfo::burn_item()]918		#[transactional]919		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {920921			let sender = ensure_signed(origin)?;922			let mut target_collection = Self::get_collection(collection_id)?;923924			Self::check_owner_permissions(&target_collection, &sender)?;925926			target_collection.transfers_enabled = value;927			target_collection.save()928		}929930		/// Destroys a concrete instance of NFT.931		///932		/// # Permissions933		///934		/// * Collection Owner.935		/// * Collection Admin.936		/// * Current NFT Owner.937		///938		/// # Arguments939		///940		/// * collection_id: ID of the collection.941		///942		/// * item_id: ID of NFT to burn.943		#[weight = <T as Config>::WeightInfo::burn_item()]944		#[transactional]945		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {946947			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);948			let target_collection = Self::get_collection(collection_id)?;949950			Self::burn_item_internal(&sender, &target_collection, item_id, value)?;951952			target_collection.submit_logs()953		}954955		/// Change ownership of the token.956		///957		/// # Permissions958		///959		/// * Collection Owner960		/// * Collection Admin961		/// * Current NFT owner962		///963		/// # Arguments964		///965		/// * recipient: Address of token recipient.966		///967		/// * collection_id.968		///969		/// * item_id: ID of the item970		///     * Non-Fungible Mode: Required.971		///     * Fungible Mode: Ignored.972		///     * Re-Fungible Mode: Required.973		///974		/// * value: Amount to transfer.975		///     * Non-Fungible Mode: Ignored976		///     * Fungible Mode: Must specify transferred amount977		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)978		#[weight = <T as Config>::WeightInfo::transfer()]979		#[transactional]980		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {981			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);982			let collection = Self::get_collection(collection_id)?;983984			Self::transfer_internal(&sender, &recipient, &collection, item_id, value)?;985986			collection.submit_logs()987		}988989		/// Set, change, or remove approved address to transfer the ownership of the NFT.990		///991		/// # Permissions992		///993		/// * Collection Owner994		/// * Collection Admin995		/// * Current NFT owner996		///997		/// # Arguments998		///999		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1000		///1001		/// * collection_id.1002		///1003		/// * item_id: ID of the item.1004		#[weight = <T as Config>::WeightInfo::approve()]1005		#[transactional]1006		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {1007			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1008			let collection = Self::get_collection(collection_id)?;10091010			Self::approve_internal(&sender, &spender, &collection, item_id, amount)?;10111012			collection.submit_logs()1013		}10141015		/// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.1016		///1017		/// # Permissions1018		/// * Collection Owner1019		/// * Collection Admin1020		/// * Current NFT owner1021		/// * Address approved by current NFT owner1022		///1023		/// # Arguments1024		///1025		/// * from: Address that owns token.1026		///1027		/// * recipient: Address of token recipient.1028		///1029		/// * collection_id.1030		///1031		/// * item_id: ID of the item.1032		///1033		/// * value: Amount to transfer.1034		#[weight = <T as Config>::WeightInfo::transfer_from()]1035		#[transactional]1036		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {1037			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1038			let collection = Self::get_collection(collection_id)?;10391040			Self::transfer_from_internal(&sender, &from, &recipient, &collection, item_id, value)?;10411042			collection.submit_logs()1043		}1044		// #[weight = 0]1045		//     // let no_perm_mes = "You do not have permissions to modify this collection";1046		//     // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1047		//     // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1048		//     // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);10491050		//     // // on_nft_received  call10511052		//     // Self::transfer(origin, collection_id, item_id, new_owner)?;10531054		//     Ok(())1055		// }10561057		/// Set off-chain data schema.1058		///1059		/// # Permissions1060		///1061		/// * Collection Owner1062		/// * Collection Admin1063		///1064		/// # Arguments1065		///1066		/// * collection_id.1067		///1068		/// * schema: String representing the offchain data schema.1069		#[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1070		#[transactional]1071		pub fn set_variable_meta_data (1072			origin,1073			collection_id: CollectionId,1074			item_id: TokenId,1075			data: Vec<u8>1076		) -> DispatchResult {1077			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);10781079			let collection = Self::get_collection(collection_id)?;10801081			Self::set_variable_meta_data_internal(&sender, &collection, item_id, data)?;10821083			Ok(())1084		}10851086		/// Set schema standard1087		/// ImageURL1088		/// Unique1089		///1090		/// # Permissions1091		///1092		/// * Collection Owner1093		/// * Collection Admin1094		///1095		/// # Arguments1096		///1097		/// * collection_id.1098		///1099		/// * schema: SchemaVersion: enum1100		#[weight = <T as Config>::WeightInfo::set_schema_version()]1101		#[transactional]1102		pub fn set_schema_version(1103			origin,1104			collection_id: CollectionId,1105			version: SchemaVersion1106		) -> DispatchResult {1107			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1108			let mut target_collection = Self::get_collection(collection_id)?;1109			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;1110			target_collection.schema_version = version;1111			target_collection.save()1112		}11131114		/// Set off-chain data schema.1115		///1116		/// # Permissions1117		///1118		/// * Collection Owner1119		/// * Collection Admin1120		///1121		/// # Arguments1122		///1123		/// * collection_id.1124		///1125		/// * schema: String representing the offchain data schema.1126		#[weight = <T as Config>::WeightInfo::set_offchain_schema()]1127		#[transactional]1128		pub fn set_offchain_schema(1129			origin,1130			collection_id: CollectionId,1131			schema: Vec<u8>1132		) -> DispatchResult {1133			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1134			let mut target_collection = Self::get_collection(collection_id)?;1135			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11361137			// check schema limit1138			ensure!(schema.len() as u32 <= OFFCHAIN_SCHEMA_LIMIT, "");11391140			target_collection.offchain_schema = schema;1141			target_collection.save()1142		}11431144		/// Set const on-chain data schema.1145		///1146		/// # Permissions1147		///1148		/// * Collection Owner1149		/// * Collection Admin1150		///1151		/// # Arguments1152		///1153		/// * collection_id.1154		///1155		/// * schema: String representing the const on-chain data schema.1156		#[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1157		#[transactional]1158		pub fn set_const_on_chain_schema (1159			origin,1160			collection_id: CollectionId,1161			schema: Vec<u8>1162		) -> DispatchResult {1163			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1164			let mut target_collection = Self::get_collection(collection_id)?;1165			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11661167			// check schema limit1168			ensure!(schema.len() as u32 <= CONST_ON_CHAIN_SCHEMA_LIMIT, "");11691170			target_collection.const_on_chain_schema = schema;1171			target_collection.save()1172		}11731174		/// Set variable on-chain data schema.1175		///1176		/// # Permissions1177		///1178		/// * Collection Owner1179		/// * Collection Admin1180		///1181		/// # Arguments1182		///1183		/// * collection_id.1184		///1185		/// * schema: String representing the variable on-chain data schema.1186		#[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1187		#[transactional]1188		pub fn set_variable_on_chain_schema (1189			origin,1190			collection_id: CollectionId,1191			schema: Vec<u8>1192		) -> DispatchResult {1193			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1194			let mut target_collection = Self::get_collection(collection_id)?;1195			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11961197			// check schema limit1198			ensure!(schema.len() as u32 <= VARIABLE_ON_CHAIN_SCHEMA_LIMIT, "");11991200			target_collection.variable_on_chain_schema = schema;1201			target_collection.save()1202		}12031204		#[weight = <T as Config>::WeightInfo::set_collection_limits()]1205		#[transactional]1206		pub fn set_collection_limits(1207			origin,1208			collection_id: u32,1209			new_limits: CollectionLimits<T::BlockNumber>,1210		) -> DispatchResult {1211			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1212			let mut target_collection = Self::get_collection(collection_id)?;1213			Self::check_owner_permissions(&target_collection, sender.as_sub())?;1214			let old_limits = &target_collection.limits;12151216			// collection bounds1217			ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1218				new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP &&1219				new_limits.sponsored_data_size <= CUSTOM_DATA_LIMIT,1220				Error::<T>::CollectionLimitBoundsExceeded);12211222			// token_limit   check  prev1223			ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1224			ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);12251226			ensure!(1227				(old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1228				(old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1229				Error::<T>::OwnerPermissionsCantBeReverted,1230			);12311232			target_collection.limits = new_limits;12331234			target_collection.save()1235		}1236	}1237}12381239impl<T: Config> Module<T> {1240	pub fn create_item_internal(1241		sender: &T::CrossAccountId,1242		collection: &CollectionHandle<T>,1243		owner: &T::CrossAccountId,1244		data: CreateItemData,1245	) -> DispatchResult {1246		ensure!(1247			owner != &T::CrossAccountId::from_eth(H160([0; 20])),1248			Error::<T>::AddressIsZero1249		);12501251		Self::can_create_items_in_collection(collection, sender, owner, 1)?;1252		Self::validate_create_item_args(collection, &data)?;1253		Self::create_item_no_validation(collection, owner, data)?;1254	pub fn transfer_internal(1255		sender: &T::CrossAccountId,1256		recipient: &T::CrossAccountId,1257		target_collection: &CollectionHandle<T>,1258		item_id: TokenId,1259		value: u128,1260	) -> DispatchResult {1261		ensure!(1262			recipient != &T::CrossAccountId::from_eth(H160([0; 20])),1263			Error::<T>::AddressIsZero1264		);12651266		target_collection.consume_gas(2000000)?;1267		// Limits check1268		Self::is_correct_transfer(target_collection, recipient)?;12691270		// Transfer permissions check1271		ensure!(1272			Self::is_item_owner(sender, target_collection, item_id)1273				|| Self::is_owner_or_admin_permissions(target_collection, sender),1274			Error::<T>::NoPermission1275		);12761277		if target_collection.access == AccessMode::WhiteList {1278			Self::check_white_list(target_collection, sender)?;1279			Self::check_white_list(target_collection, recipient)?;1280		}12811282		match target_collection.mode {1283			CollectionMode::NFT => Self::transfer_nft(1284				target_collection,1285				item_id,1286				sender.clone(),1287				recipient.clone(),1288			)?,1289			CollectionMode::Fungible(_) => {1290				Self::transfer_fungible(target_collection, value, sender, recipient)?1291			}1292			CollectionMode::ReFungible => Self::transfer_refungible(1293				target_collection,1294				item_id,1295				value,1296				sender.clone(),1297				recipient.clone(),1298			)?,1299			_ => (),1300		};13011302		Self::deposit_event(RawEvent::Transfer(1303			target_collection.id,1304			item_id,1305			sender.clone(),1306			recipient.clone(),1307			value,1308		));13091310		Ok(())1311	}13121313	pub fn approve_internal(1314		sender: &T::CrossAccountId,1315		spender: &T::CrossAccountId,1316		collection: &CollectionHandle<T>,1317		item_id: TokenId,1318		amount: u128,1319	) -> DispatchResult {1320		collection.consume_gas(2000000)?;1321		Self::token_exists(collection, item_id)?;13221323		// Transfer permissions check1324		let bypasses_limits = collection.limits.owner_can_transfer1325			&& Self::is_owner_or_admin_permissions(collection, sender);13261327		let allowance_limit = if bypasses_limits {1328			None1329		} else if let Some(amount) = Self::owned_amount(sender, collection, item_id) {1330			Some(amount)1331		} else {1332			fail!(Error::<T>::NoPermission);1333		};13341335		if collection.access == AccessMode::WhiteList {1336			Self::check_white_list(collection, sender)?;1337			Self::check_white_list(collection, spender)?;1338		}13391340		let allowance: u128 = amount1341			.checked_add(<Allowances<T>>::get(1342				collection.id,1343				(item_id, sender.as_sub(), spender.as_sub()),1344			))1345			.ok_or(Error::<T>::NumOverflow)?;1346		if let Some(limit) = allowance_limit {1347			ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1348		}1349		<Allowances<T>>::insert(1350			collection.id,1351			(item_id, sender.as_sub(), spender.as_sub()),1352			allowance,1353		);13541355		if matches!(collection.mode, CollectionMode::NFT) {1356			// TODO: NFT: only one owner may exist for token in ERC7211357			collection.log(ERC721Events::Approval {1358				owner: *sender.as_eth(),1359				approved: *spender.as_eth(),1360				token_id: item_id.into(),1361			})?;1362		}13631364		if matches!(collection.mode, CollectionMode::Fungible(_)) {1365			// TODO: NFT: only one owner may exist for token in ERC201366			collection.log(ERC20Events::Approval {1367				owner: *sender.as_eth(),1368				spender: *spender.as_eth(),1369				value: allowance.into(),1370			})?;1371		}13721373		Self::deposit_event(RawEvent::Approved(1374			collection.id,1375			item_id,1376			sender.clone(),1377			spender.clone(),1378			allowance,1379		));1380		Ok(())1381	}13821383	pub fn transfer_from_internal(1384		sender: &T::CrossAccountId,1385		from: &T::CrossAccountId,1386		recipient: &T::CrossAccountId,1387		collection: &CollectionHandle<T>,1388		item_id: TokenId,1389		amount: u128,1390	) -> DispatchResult {1391		collection.consume_gas(2000000)?;1392		// Check approval1393		let approval: u128 =1394			<Allowances<T>>::get(collection.id, (item_id, from.as_sub(), sender.as_sub()));13951396		// Limits check1397		Self::is_correct_transfer(collection, recipient)?;13981399		// Transfer permissions check1400		ensure!(1401			approval >= amount1402				|| (collection.limits.owner_can_transfer1403					&& Self::is_owner_or_admin_permissions(collection, sender)),1404			Error::<T>::NoPermission1405		);14061407		if collection.access == AccessMode::WhiteList {1408			Self::check_white_list(collection, sender)?;1409			Self::check_white_list(collection, recipient)?;1410		}14111412		// Reduce approval by transferred amount or remove if remaining approval drops to 01413		let allowance = approval.saturating_sub(amount);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		let account_items: u32 =1554			<AddressTokens<T>>::get(collection_id, recipient.as_sub()).len() as u32;1555		ensure!(1556			collection.limits.account_token_ownership_limit > account_items,1557			Error::<T>::AccountTokenLimitExceeded1558		);15591560		// preliminary transfer check1561		ensure!(collection.transfers_enabled, Error::<T>::TransferNotAllowed);15621563		Ok(())1564	}15651566	fn can_create_items_in_collection(1567		collection: &CollectionHandle<T>,1568		sender: &T::CrossAccountId,1569		owner: &T::CrossAccountId,1570		amount: u32,1571	) -> DispatchResult {1572		let collection_id = collection.id;15731574		// check token limit and account token limit1575		let total_items: u32 = ItemListIndex::get(collection_id)1576			.checked_add(amount)1577			.ok_or(Error::<T>::CollectionTokenLimitExceeded)?;1578		let account_items: u32 = (<AddressTokens<T>>::get(collection_id, owner.as_sub()).len()1579			as u32)1580			.checked_add(amount)1581			.ok_or(Error::<T>::AccountTokenLimitExceeded)?;1582		ensure!(1583			collection.limits.token_limit >= total_items,1584			Error::<T>::CollectionTokenLimitExceeded1585		);1586		ensure!(1587			collection.limits.account_token_ownership_limit >= account_items,1588			Error::<T>::AccountTokenLimitExceeded1589		);15901591		if !Self::is_owner_or_admin_permissions(collection, sender) {1592			ensure!(collection.mint_mode, Error::<T>::PublicMintingNotAllowed);1593			Self::check_white_list(collection, owner)?;1594			Self::check_white_list(collection, sender)?;1595		}15961597		Ok(())1598	}15991600	fn validate_create_item_args(1601		target_collection: &CollectionHandle<T>,1602		data: &CreateItemData,1603	) -> DispatchResult {1604		match target_collection.mode {1605			CollectionMode::NFT => {1606				if !matches!(data, CreateItemData::NFT(_)) {1607					fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1608				}1609			}1610			CollectionMode::Fungible(_) => {1611				if !matches!(data, CreateItemData::Fungible(_)) {1612					fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1613				}1614			}1615			CollectionMode::ReFungible => {1616				if let CreateItemData::ReFungible(data) = data {1617					// Check refungibility limits1618					ensure!(1619						data.pieces <= MAX_REFUNGIBLE_PIECES,1620						Error::<T>::WrongRefungiblePieces1621					);1622					ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1623				} else {1624					fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1625				}1626			}1627			_ => {1628				fail!(Error::<T>::UnexpectedCollectionType);1629			}1630		};16311632		Ok(())1633	}16341635	fn create_item_no_validation(1636		collection: &CollectionHandle<T>,1637		owner: &T::CrossAccountId,1638		data: CreateItemData,1639	) -> DispatchResult {1640		match data {1641			CreateItemData::NFT(data) => {1642				let item = NftItemType {1643					owner: owner.clone(),1644					const_data: data.const_data.into_inner(),1645					variable_data: data.variable_data.into_inner(),1646				};16471648				Self::add_nft_item(collection, item)?;1649			}1650			CreateItemData::Fungible(data) => {1651				Self::add_fungible_item(collection, owner, data.value)?;1652			}1653			CreateItemData::ReFungible(data) => {1654				let owner_list = vec![Ownership {1655					owner: owner.clone(),1656					fraction: data.pieces,1657				}];16581659				let item = ReFungibleItemType {1660					owner: owner_list,1661					const_data: data.const_data.into_inner(),1662					variable_data: data.variable_data.into_inner(),1663				};16641665				Self::add_refungible_item(collection, item)?;1666			}1667		};16681669		Ok(())1670	}16711672	fn add_fungible_item(1673		collection: &CollectionHandle<T>,1674		owner: &T::CrossAccountId,1675		value: u128,1676	) -> DispatchResult {1677		let collection_id = collection.id;16781679		// Does new owner already have an account?1680		let balance: u128 = <FungibleItemList<T>>::get(collection_id, owner.as_sub()).value;16811682		// Mint1683		let item = FungibleItemType {1684			value: balance.checked_add(value).ok_or(Error::<T>::NumOverflow)?,1685		};1686		<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), item);16871688		// Update balance1689		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1690			.checked_add(value)1691			.ok_or(Error::<T>::NumOverflow)?;1692		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);16931694		Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));1695		Ok(())1696	}16971698	fn add_refungible_item(1699		collection: &CollectionHandle<T>,1700		item: ReFungibleItemType<T::CrossAccountId>,1701	) -> DispatchResult {1702		let collection_id = collection.id;17031704		let current_index = <ItemListIndex>::get(collection_id)1705			.checked_add(1)1706			.ok_or(Error::<T>::NumOverflow)?;1707		let itemcopy = item.clone();17081709		ensure!(item.owner.len() == 1, Error::<T>::BadCreateRefungibleCall,);1710		let item_owner = item.owner.first().expect("only one owner is defined");17111712		let value = item_owner.fraction;1713		let owner = item_owner.owner.clone();17141715		Self::add_token_index(collection_id, current_index, &owner)?;17161717		<ItemListIndex>::insert(collection_id, current_index);1718		<ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);17191720		// Update balance1721		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1722			.checked_add(value)1723			.ok_or(Error::<T>::NumOverflow)?;1724		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);17251726		Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));1727		Ok(())1728	}17291730	fn add_nft_item(1731		collection: &CollectionHandle<T>,1732		item: NftItemType<T::CrossAccountId>,1733	) -> DispatchResult {1734		let collection_id = collection.id;17351736		let current_index = <ItemListIndex>::get(collection_id)1737			.checked_add(1)1738			.ok_or(Error::<T>::NumOverflow)?;17391740		let item_owner = item.owner.clone();1741		Self::add_token_index(collection_id, current_index, &item.owner)?;17421743		<ItemListIndex>::insert(collection_id, current_index);1744		<NftItemList<T>>::insert(collection_id, current_index, item);17451746		// Update balance1747		let new_balance = <Balance<T>>::get(collection_id, item_owner.as_sub())1748			.checked_add(1)1749			.ok_or(Error::<T>::NumOverflow)?;1750		<Balance<T>>::insert(collection_id, item_owner.as_sub(), new_balance);17511752		collection.log(ERC721Events::Transfer {1753			from: H160::default(),1754			to: *item_owner.as_eth(),1755			token_id: current_index.into(),1756		})?;1757		Self::deposit_event(RawEvent::ItemCreated(1758			collection_id,1759			current_index,1760			item_owner,1761		));1762		Ok(())1763	}17641765	fn burn_refungible_item(1766		collection: &CollectionHandle<T>,1767		item_id: TokenId,1768		owner: &T::CrossAccountId,1769	) -> DispatchResult {1770		let collection_id = collection.id;17711772		let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id)1773			.ok_or(Error::<T>::TokenNotFound)?;1774		let rft_balance = token1775			.owner1776			.iter()1777			.find(|&i| i.owner == *owner)1778			.ok_or(Error::<T>::TokenNotFound)?;1779		Self::remove_token_index(collection_id, item_id, owner)?;17801781		// update balance1782		let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.as_sub())1783			.checked_sub(rft_balance.fraction)1784			.ok_or(Error::<T>::NumOverflow)?;1785		<Balance<T>>::insert(collection_id, rft_balance.owner.as_sub(), new_balance);17861787		// Re-create owners list with sender removed1788		let index = token1789			.owner1790			.iter()1791			.position(|i| i.owner == *owner)1792			.expect("owned item is exists");1793		token.owner.remove(index);1794		let owner_count = token.owner.len();17951796		// Burn the token completely if this was the last (only) owner1797		if owner_count == 0 {1798			<ReFungibleItemList<T>>::remove(collection_id, item_id);1799			<VariableMetaDataBasket<T>>::remove(collection_id, item_id);1800		} else {1801			<ReFungibleItemList<T>>::insert(collection_id, item_id, token);1802		}18031804		Ok(())1805	}18061807	fn burn_nft_item(collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1808		let collection_id = collection.id;18091810		let item =1811			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;1812		Self::remove_token_index(collection_id, item_id, &item.owner)?;18131814		// update balance1815		let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())1816			.checked_sub(1)1817			.ok_or(Error::<T>::NumOverflow)?;1818		<Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);1819		<NftItemList<T>>::remove(collection_id, item_id);1820		<VariableMetaDataBasket<T>>::remove(collection_id, item_id);18211822		Self::deposit_event(RawEvent::ItemDestroyed(collection.id, item_id));1823		Ok(())1824	}18251826	fn burn_fungible_item(1827		owner: &T::CrossAccountId,1828		collection: &CollectionHandle<T>,1829		value: u128,1830	) -> DispatchResult {1831		let collection_id = collection.id;18321833		let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());1834		ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);18351836		// update balance1837		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1838			.checked_sub(value)1839			.ok_or(Error::<T>::NumOverflow)?;1840		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);18411842		if balance.value - value > 0 {1843			balance.value -= value;1844			<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);1845		} else {1846			<FungibleItemList<T>>::remove(collection_id, owner.as_sub());1847		}18481849		collection.log(ERC20Events::Transfer {1850			from: *owner.as_eth(),1851			to: H160::default(),1852			value: value.into(),1853		})?;1854		Ok(())1855	}18561857	pub fn get_collection(1858		collection_id: CollectionId,1859	) -> Result<CollectionHandle<T>, sp_runtime::DispatchError> {1860		Ok(<CollectionHandle<T>>::get(collection_id).ok_or(Error::<T>::CollectionNotFound)?)1861	}18621863	fn check_owner_permissions(1864		target_collection: &CollectionHandle<T>,1865		subject: &T::AccountId,1866	) -> DispatchResult {1867		ensure!(1868			*subject == target_collection.owner,1869			Error::<T>::NoPermission1870		);18711872		Ok(())1873	}18741875	fn is_owner_or_admin_permissions(1876		collection: &CollectionHandle<T>,1877		subject: &T::CrossAccountId,1878	) -> bool {1879		*subject.as_sub() == collection.owner1880			|| <AdminList<T>>::get(collection.id).contains(subject)1881	}18821883	fn check_owner_or_admin_permissions(1884		collection: &CollectionHandle<T>,1885		subject: &T::CrossAccountId,1886	) -> DispatchResult {1887		ensure!(1888			Self::is_owner_or_admin_permissions(collection, subject),1889			Error::<T>::NoPermission1890		);18911892		Ok(())1893	}18941895	fn owned_amount(1896		subject: &T::CrossAccountId,1897		target_collection: &CollectionHandle<T>,1898		item_id: TokenId,1899	) -> Option<u128> {1900		let collection_id = target_collection.id;19011902		match target_collection.mode {1903			CollectionMode::NFT => {1904				(<NftItemList<T>>::get(collection_id, item_id)?.owner == *subject).then(|| 1)1905			}1906			CollectionMode::Fungible(_) => {1907				Some(<FungibleItemList<T>>::get(collection_id, &subject.as_sub()).value)1908			}1909			CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)?1910				.owner1911				.iter()1912				.find(|i| i.owner == *subject)1913				.map(|i| i.fraction),1914			CollectionMode::Invalid => None,1915		}1916	}19171918	fn is_item_owner(1919		subject: &T::CrossAccountId,1920		target_collection: &CollectionHandle<T>,1921		item_id: TokenId,1922	) -> bool {1923		match target_collection.mode {1924			CollectionMode::Fungible(_) => true,1925			_ => Self::owned_amount(subject, target_collection, item_id).is_some(),1926		}1927	}19281929	fn check_white_list(1930		collection: &CollectionHandle<T>,1931		address: &T::CrossAccountId,1932	) -> DispatchResult {1933		let collection_id = collection.id;19341935		let mes = Error::<T>::AddresNotInWhiteList;1936		ensure!(1937			<WhiteList<T>>::contains_key(collection_id, address.as_sub()),1938			mes1939		);19401941		Ok(())1942	}19431944	/// Check if token exists. In case of Fungible, check if there is an entry for1945	/// the owner in fungible balances double map1946	fn token_exists(target_collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1947		let collection_id = target_collection.id;1948		let exists = match target_collection.mode {1949			CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),1950			CollectionMode::Fungible(_) => true,1951			CollectionMode::ReFungible => {1952				<ReFungibleItemList<T>>::contains_key(collection_id, item_id)1953			}1954			_ => false,1955		};19561957		ensure!(exists, Error::<T>::TokenNotFound);1958		Ok(())1959	}19601961	fn transfer_fungible(1962		collection: &CollectionHandle<T>,1963		value: u128,1964		owner: &T::CrossAccountId,1965		recipient: &T::CrossAccountId,1966	) -> DispatchResult {1967		let collection_id = collection.id;19681969		let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());1970		ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);19711972		// Send balance to recipient (updates balanceOf of recipient)1973		Self::add_fungible_item(collection, recipient, value)?;19741975		// update balanceOf of sender1976		<Balance<T>>::insert(collection_id, owner.as_sub(), balance.value - value);19771978		// Reduce or remove sender1979		if balance.value == value {1980			<FungibleItemList<T>>::remove(collection_id, owner.as_sub());1981		} else {1982			balance.value -= value;1983			<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);1984		}19851986		collection.log(ERC20Events::Transfer {1987			from: *owner.as_eth(),1988			to: *recipient.as_eth(),1989			value: value.into(),1990		})?;1991		Self::deposit_event(RawEvent::Transfer(1992			collection.id,1993			1,1994			owner.clone(),1995			recipient.clone(),1996			value,1997		));19981999		Ok(())2000	}20012002	fn transfer_refungible(2003		collection: &CollectionHandle<T>,2004		item_id: TokenId,2005		value: u128,2006		owner: T::CrossAccountId,2007		new_owner: T::CrossAccountId,2008	) -> DispatchResult {2009		let collection_id = collection.id;2010		let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id)2011			.ok_or(Error::<T>::TokenNotFound)?;20122013		let item = full_item2014			.owner2015			.iter()2016			.find(|i| i.owner == owner)2017			.ok_or(Error::<T>::TokenNotFound)?;2018		let amount = item.fraction;20192020		ensure!(amount >= value, Error::<T>::TokenValueTooLow);20212022		// update balance2023		let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2024			.checked_sub(value)2025			.ok_or(Error::<T>::NumOverflow)?;2026		<Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);20272028		let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2029			.checked_add(value)2030			.ok_or(Error::<T>::NumOverflow)?;2031		<Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);20322033		let old_owner = item.owner.clone();2034		let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);20352036		let mut new_full_item = full_item.clone();2037		// transfer2038		if amount == value && !new_owner_has_account {2039			// change owner2040			// new owner do not have account2041			new_full_item2042				.owner2043				.iter_mut()2044				.find(|i| i.owner == owner)2045				.expect("old owner does present in refungible")2046				.owner = new_owner.clone();2047			<ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);20482049			// update index collection2050			Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;2051		} else {2052			new_full_item2053				.owner2054				.iter_mut()2055				.find(|i| i.owner == owner)2056				.expect("old owner does present in refungible")2057				.fraction -= value;20582059			// separate amount2060			if new_owner_has_account {2061				// new owner has account2062				new_full_item2063					.owner2064					.iter_mut()2065					.find(|i| i.owner == new_owner)2066					.expect("new owner has account")2067					.fraction += value;2068			} else {2069				// new owner do not have account2070				new_full_item.owner.push(Ownership {2071					owner: new_owner.clone(),2072					fraction: value,2073				});2074				Self::add_token_index(collection_id, item_id, &new_owner)?;2075			}20762077			<ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2078		}20792080		Self::deposit_event(RawEvent::Transfer(2081			collection.id,2082			item_id,2083			owner,2084			new_owner,2085			amount,2086		));20872088		Ok(())2089	}20902091	fn transfer_nft(2092		collection: &CollectionHandle<T>,2093		item_id: TokenId,2094		sender: T::CrossAccountId,2095		new_owner: T::CrossAccountId,2096	) -> DispatchResult {2097		let collection_id = collection.id;2098		let mut item =2099			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;21002101		ensure!(sender == item.owner, Error::<T>::MustBeTokenOwner);21022103		// update balance2104		let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2105			.checked_sub(1)2106			.ok_or(Error::<T>::NumOverflow)?;2107		<Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);21082109		let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2110			.checked_add(1)2111			.ok_or(Error::<T>::NumOverflow)?;2112		<Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);21132114		// change owner2115		let old_owner = item.owner.clone();2116		item.owner = new_owner.clone();2117		<NftItemList<T>>::insert(collection_id, item_id, item);21182119		// update index collection2120		Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;21212122		collection.log(ERC721Events::Transfer {2123			from: *sender.as_eth(),2124			to: *new_owner.as_eth(),2125			token_id: item_id.into(),2126		})?;2127		Self::deposit_event(RawEvent::Transfer(2128			collection.id,2129			item_id,2130			sender,2131			new_owner,2132			1,2133		));21342135		Ok(())2136	}21372138	fn set_re_fungible_variable_data(2139		collection: &CollectionHandle<T>,2140		item_id: TokenId,2141		data: Vec<u8>,2142	) -> DispatchResult {2143		let collection_id = collection.id;2144		let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id)2145			.ok_or(Error::<T>::TokenNotFound)?;21462147		item.variable_data = data;21482149		<ReFungibleItemList<T>>::insert(collection_id, item_id, item);21502151		Ok(())2152	}21532154	fn set_nft_variable_data(2155		collection: &CollectionHandle<T>,2156		item_id: TokenId,2157		data: Vec<u8>,2158	) -> DispatchResult {2159		let collection_id = collection.id;2160		let mut item =2161			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;21622163		item.variable_data = data;21642165		<NftItemList<T>>::insert(collection_id, item_id, item);21662167		Ok(())2168	}21692170	#[allow(dead_code)]2171	fn init_collection(item: &Collection<T>) {2172		// check params2173		assert!(2174			item.decimal_points <= MAX_DECIMAL_POINTS,2175			"decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2176		);2177		assert!(2178			item.name.len() <= 64,2179			"Collection name can not be longer than 63 char"2180		);2181		assert!(2182			item.name.len() <= 256,2183			"Collection description can not be longer than 255 char"2184		);2185		assert!(2186			item.token_prefix.len() <= 16,2187			"Token prefix can not be longer than 15 char"2188		);21892190		// Generate next collection ID2191		let next_id = CreatedCollectionCount::get().checked_add(1).unwrap();21922193		CreatedCollectionCount::put(next_id);2194	}21952196	#[allow(dead_code)]2197	fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::CrossAccountId>) {2198		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();21992200		Self::add_token_index(collection_id, current_index, &item.owner).unwrap();22012202		<ItemListIndex>::insert(collection_id, current_index);22032204		// Update balance2205		let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2206			.checked_add(1)2207			.unwrap();2208		<Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2209	}22102211	#[allow(dead_code)]2212	fn init_fungible_token(2213		collection_id: CollectionId,2214		owner: &T::CrossAccountId,2215		item: &FungibleItemType,2216	) {2217		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();22182219		Self::add_token_index(collection_id, current_index, owner).unwrap();22202221		<ItemListIndex>::insert(collection_id, current_index);22222223		// Update balance2224		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2225			.checked_add(item.value)2226			.unwrap();2227		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2228	}22292230	#[allow(dead_code)]2231	fn init_refungible_token(2232		collection_id: CollectionId,2233		item: &ReFungibleItemType<T::CrossAccountId>,2234	) {2235		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();22362237		let value = item.owner.first().unwrap().fraction;2238		let owner = item.owner.first().unwrap().owner.clone();22392240		Self::add_token_index(collection_id, current_index, &owner).unwrap();22412242		<ItemListIndex>::insert(collection_id, current_index);22432244		// Update balance2245		let new_balance = <Balance<T>>::get(collection_id, &owner.as_sub())2246			.checked_add(value)2247			.unwrap();2248		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2249	}22502251	fn add_token_index(2252		collection_id: CollectionId,2253		item_index: TokenId,2254		owner: &T::CrossAccountId,2255	) -> DispatchResult {2256		// add to account limit2257		if <AccountItemCount<T>>::contains_key(owner.as_sub()) {2258			// bound Owned tokens by a single address2259			let count = <AccountItemCount<T>>::get(owner.as_sub());2260			ensure!(2261				count < ACCOUNT_TOKEN_OWNERSHIP_LIMIT,2262				Error::<T>::AddressOwnershipLimitExceeded2263			);22642265			<AccountItemCount<T>>::insert(2266				owner.as_sub(),2267				count.checked_add(1).ok_or(Error::<T>::NumOverflow)?,2268			);2269		} else {2270			<AccountItemCount<T>>::insert(owner.as_sub(), 1);2271		}22722273		let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2274		if list_exists {2275			let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2276			let item_contains = list.contains(&item_index.clone());22772278			if !item_contains {2279				list.push(item_index);2280			}22812282			<AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2283		} else {2284			let itm = vec![item_index];2285			<AddressTokens<T>>::insert(collection_id, owner.as_sub(), itm);2286		}22872288		Ok(())2289	}22902291	fn remove_token_index(2292		collection_id: CollectionId,2293		item_index: TokenId,2294		owner: &T::CrossAccountId,2295	) -> DispatchResult {2296		// update counter2297		<AccountItemCount<T>>::insert(2298			owner.as_sub(),2299			<AccountItemCount<T>>::get(owner.as_sub())2300				.checked_sub(1)2301				.ok_or(Error::<T>::NumOverflow)?,2302		);23032304		let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2305		if list_exists {2306			let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2307			let item_contains = list.contains(&item_index.clone());23082309			if item_contains {2310				list.retain(|&item| item != item_index);2311				<AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2312			}2313		}23142315		Ok(())2316	}23172318	fn move_token_index(2319		collection_id: CollectionId,2320		item_index: TokenId,2321		old_owner: &T::CrossAccountId,2322		new_owner: &T::CrossAccountId,2323	) -> DispatchResult {2324		Self::remove_token_index(collection_id, item_index, old_owner)?;2325		Self::add_token_index(collection_id, item_index, new_owner)?;23262327		Ok(())2328	}2329}23302331sp_api::decl_runtime_apis! {2332	pub trait NftApi {2333		/// Used for ethereum integration2334		fn eth_contract_code(account: H160) -> Option<Vec<u8>>;2335	}2336}