git.delta.rocks / unique-network / refs/commits / 0999c791f8d4

difftreelog

fix correct gas metering

Yaroslav Bolyukin2021-08-18parent: #d614bc7.patch.diff
in: master

1 file changed

modifiedpallets/nft/src/lib.rsdiffbeforeafterboth
before · pallets/nft/src/lib.rs
1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]7#![cfg_attr(not(feature = "std"), no_std)]8#![allow(9	clippy::too_many_arguments,10	clippy::unnecessary_mut_passed,11	clippy::unused_unit12)]1314extern crate alloc;1516pub use serde::{Serialize, Deserialize};1718pub use frame_support::{19	construct_runtime, decl_event, decl_module, decl_storage, decl_error,20	dispatch::DispatchResult,21	ensure, fail, parameter_types,22	traits::{23		Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,24		Randomness, IsSubType, WithdrawReasons,25	},26	weights::{27		constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},28		DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,29		WeightToFeePolynomial, DispatchClass,30	},31	StorageValue, transactional,32};3334use frame_system::{self as system, ensure_signed, ensure_root};35use sp_core::H160;36use sp_std::vec;37use sp_runtime::sp_std::prelude::Vec;38use core::ops::{Deref, DerefMut};39use nft_data_structs::{40	MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, MAX_REFUNGIBLE_PIECES,41	AccessMode, ChainLimits, Collection, CreateItemData, CollectionLimits, CollectionId,42	CollectionMode, TokenId, SchemaVersion, SponsorshipState, Ownership, NftItemType,43	FungibleItemType, ReFungibleItemType,44};4546#[cfg(test)]47mod mock;4849#[cfg(test)]50mod tests;5152mod default_weights;53mod eth;54mod sponsorship;55pub use sponsorship::NftSponsorshipHandler;56pub use eth::sponsoring::NftEthSponsorshipHandler;5758pub use eth::NftErcSupport;59pub use eth::account::*;60use eth::erc::{ERC20Events, ERC721Events};6162#[cfg(feature = "runtime-benchmarks")]63mod benchmarking;6465pub trait WeightInfo {66	fn create_collection() -> Weight;67	fn destroy_collection() -> Weight;68	fn add_to_white_list() -> Weight;69	fn remove_from_white_list() -> Weight;70	fn set_public_access_mode() -> Weight;71	fn set_mint_permission() -> Weight;72	fn change_collection_owner() -> Weight;73	fn add_collection_admin() -> Weight;74	fn remove_collection_admin() -> Weight;75	fn set_collection_sponsor() -> Weight;76	fn confirm_sponsorship() -> Weight;77	fn remove_collection_sponsor() -> Weight;78	fn create_item(s: usize) -> Weight;79	fn burn_item() -> Weight;80	fn transfer() -> Weight;81	fn approve() -> Weight;82	fn transfer_from() -> Weight;83	fn set_offchain_schema() -> Weight;84	fn set_const_on_chain_schema() -> Weight;85	fn set_variable_on_chain_schema() -> Weight;86	fn set_variable_meta_data() -> Weight;87	fn enable_contract_sponsoring() -> Weight;88	fn set_schema_version() -> Weight;89	fn set_chain_limits() -> Weight;90	fn set_contract_sponsoring_rate_limit() -> Weight;91	fn set_variable_meta_data_sponsoring_rate_limit() -> Weight;92	fn toggle_contract_white_list() -> Weight;93	fn add_to_contract_white_list() -> Weight;94	fn remove_from_contract_white_list() -> Weight;95	fn set_collection_limits() -> Weight;96}9798decl_error! {99	/// Error for non-fungible-token module.100	pub enum Error for Module<T: Config> {101		/// Total collections bound exceeded.102		TotalCollectionsLimitExceeded,103		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.104		CollectionDecimalPointLimitExceeded,105		/// Collection name can not be longer than 63 char.106		CollectionNameLimitExceeded,107		/// Collection description can not be longer than 255 char.108		CollectionDescriptionLimitExceeded,109		/// Token prefix can not be longer than 15 char.110		CollectionTokenPrefixLimitExceeded,111		/// This collection does not exist.112		CollectionNotFound,113		/// Item not exists.114		TokenNotFound,115		/// Admin not found116		AdminNotFound,117		/// Arithmetic calculation overflow.118		NumOverflow,119		/// Account already has admin role.120		AlreadyAdmin,121		/// You do not own this collection.122		NoPermission,123		/// This address is not set as sponsor, use setCollectionSponsor first.124		ConfirmUnsetSponsorFail,125		/// Collection is not in mint mode.126		PublicMintingNotAllowed,127		/// Sender parameter and item owner must be equal.128		MustBeTokenOwner,129		/// Item balance not enough.130		TokenValueTooLow,131		/// Size of item is too large.132		NftSizeLimitExceeded,133		/// No approve found134		ApproveNotFound,135		/// Requested value more than approved.136		TokenValueNotEnough,137		/// Only approved addresses can call this method.138		ApproveRequired,139		/// Address is not in white list.140		AddresNotInWhiteList,141		/// Number of collection admins bound exceeded.142		CollectionAdminsLimitExceeded,143		/// Owned tokens by a single address bound exceeded.144		AddressOwnershipLimitExceeded,145		/// Length of items properties must be greater than 0.146		EmptyArgument,147		/// const_data exceeded data limit.148		TokenConstDataLimitExceeded,149		/// variable_data exceeded data limit.150		TokenVariableDataLimitExceeded,151		/// Not NFT item data used to mint in NFT collection.152		NotNftDataUsedToMintNftCollectionToken,153		/// Not Fungible item data used to mint in Fungible collection.154		NotFungibleDataUsedToMintFungibleCollectionToken,155		/// Not Re Fungible item data used to mint in Re Fungible collection.156		NotReFungibleDataUsedToMintReFungibleCollectionToken,157		/// Unexpected collection type.158		UnexpectedCollectionType,159		/// Can't store metadata in fungible tokens.160		CantStoreMetadataInFungibleTokens,161		/// Collection token limit exceeded162		CollectionTokenLimitExceeded,163		/// Account token limit exceeded per collection164		AccountTokenLimitExceeded,165		/// Collection limit bounds per collection exceeded166		CollectionLimitBoundsExceeded,167		/// Tried to enable permissions which are only permitted to be disabled168		OwnerPermissionsCantBeReverted,169		/// Schema data size limit bound exceeded170		SchemaDataLimitExceeded,171		/// Maximum refungibility exceeded172		WrongRefungiblePieces,173		/// createRefungible should be called with one owner174		BadCreateRefungibleCall,175		/// Gas limit exceeded176		OutOfGas,177		/// Collection settings not allowing items transferring178		TransferNotAllowed,179	}180}181182#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]183pub struct CollectionHandle<T: Config> {184	pub id: CollectionId,185	collection: Collection<T>,186	recorder: pallet_evm_coder_substrate::SubstrateRecorder<T>,187}188impl<T: Config> CollectionHandle<T> {189	pub fn get_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {190		<CollectionById<T>>::get(id).map(|collection| Self {191			id,192			collection,193			recorder: pallet_evm_coder_substrate::SubstrateRecorder::new(194				eth::collection_id_to_address(id),195				gas_limit,196			),197		})198	}199	pub fn get(id: CollectionId) -> Option<Self> {200		Self::get_with_gas_limit(id, u64::MAX)201	}202	pub fn log(&self, log: impl evm_coder::ToLog) -> DispatchResult {203		self.recorder.log_sub(log)204	}205	fn consume_gas(&self, gas: u64) -> DispatchResult {206		self.recorder.consume_gas_sub(gas)207	}208	pub fn submit_logs(self) -> DispatchResult {209		self.recorder.submit_logs()210	}211	pub fn save(self) -> DispatchResult {212		self.recorder.submit_logs()?;213		<CollectionById<T>>::insert(self.id, self.collection);214		Ok(())215	}216}217impl<T: Config> Deref for CollectionHandle<T> {218	type Target = Collection<T>;219220	fn deref(&self) -> &Self::Target {221		&self.collection222	}223}224225impl<T: Config> DerefMut for CollectionHandle<T> {226	fn deref_mut(&mut self) -> &mut Self::Target {227		&mut self.collection228	}229}230231pub trait Config: system::Config + pallet_evm_coder_substrate::Config + Sized {232	type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;233234	/// Weight information for extrinsics in this pallet.235	type WeightInfo: WeightInfo;236237	type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;238	type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;239240	type CrossAccountId: CrossAccountId<Self::AccountId>;241	type Currency: Currency<Self::AccountId>;242	type CollectionCreationPrice: Get<243		<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,244	>;245	type TreasuryAccountId: Get<Self::AccountId>;246}247248// # Used definitions249//250// ## User control levels251//252// chain-controlled - key is uncontrolled by user253//                    i.e autoincrementing index254//                    can use non-cryptographic hash255// real - key is controlled by user256//        but it is hard to generate enough colliding values, i.e owner of signed txs257//        can use non-cryptographic hash258// controlled - key is completly controlled by users259//              i.e maps with mutable keys260//              should use cryptographic hash261//262// ## User control level downgrade reasons263//264// ?1 - chain-controlled -> controlled265//      collections/tokens can be destroyed, resulting in massive holes266// ?2 - chain-controlled -> controlled267//      same as ?1, but can be only added, resulting in easier exploitation268// ?3 - real -> controlled269//      no confirmation required, so addresses can be easily generated270decl_storage! {271	trait Store for Module<T: Config> as Nft {272273		//#region Private members274		/// Id of next collection275		CreatedCollectionCount: u32;276		/// Used for migrations277		ChainVersion: u64;278		/// Id of last collection token279		/// Collection id (controlled?1)280		ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;281		//#endregion282283		//#region Chain limits struct284		pub ChainLimit get(fn chain_limit) config(): ChainLimits;285		//#endregion286287		//#region Bound counters288		/// Amount of collections destroyed, used for total amount tracking with289		/// CreatedCollectionCount290		DestroyedCollectionCount: u32;291		/// Total amount of account owned tokens (NFTs + RFTs + unique fungibles)292		/// Account id (real)293		pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;294		//#endregion295296		//#region Basic collections297		/// Collection info298		/// Collection id (controlled?1)299		pub CollectionById get(fn collection_id) config(): map hasher(blake2_128_concat) CollectionId => Option<Collection<T>> = None;300		/// List of collection admins301		/// Collection id (controlled?2)302		pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec<T::CrossAccountId>;303		/// Whitelisted collection users304		/// Collection id (controlled?2), user id (controlled?3)305		pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool;306		//#endregion307308		/// How many of collection items user have309		/// Collection id (controlled?2), account id (real)310		pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128;311312		/// Amount of items which spender can transfer out of owners account (via transferFrom)313		/// Collection id (controlled?2), (token id (controlled ?2) + owner account id (real) + spender account id (controlled?3))314		/// TODO: Off chain worker should remove from this map when token gets removed315		pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128;316317		//#region Item collections318		/// Collection id (controlled?2), token id (controlled?1)319		pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<NftItemType<T::CrossAccountId>>;320		/// Collection id (controlled?2), owner (controlled?2)321		pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType;322		/// Collection id (controlled?2), token id (controlled?1)323		pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<ReFungibleItemType<T::CrossAccountId>>;324		//#endregion325326		//#region Index list327		/// Collection id (controlled?2), tokens owner (controlled?2)328		pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;329		//#endregion330331		//#region Tokens transfer rate limit baskets332		/// (Collection id (controlled?2), who created (real))333		/// TODO: Off chain worker should remove from this map when collection gets removed334		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;335		/// Collection id (controlled?2), token id (controlled?2)336		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;337		/// Collection id (controlled?2), owning user (real)338		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;339		/// Collection id (controlled?2), token id (controlled?2)340		pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;341		//#endregion342343		/// Variable metadata sponsoring344		/// Collection id (controlled?2), token id (controlled?2)345		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;346	}347	add_extra_genesis {348		build(|config: &GenesisConfig<T>| {349			// Modification of storage350			for (_num, _c) in &config.collection_id {351				<Module<T>>::init_collection(_c);352			}353354			for (_num, _c, _i) in &config.nft_item_id {355				<Module<T>>::init_nft_token(*_c, _i);356			}357358			for (collection_id, account_id, fungible_item) in &config.fungible_item_id {359				<Module<T>>::init_fungible_token(*collection_id, &T::CrossAccountId::from_sub(account_id.clone()), fungible_item);360			}361362			for (_num, _c, _i) in &config.refungible_item_id {363				<Module<T>>::init_refungible_token(*_c, _i);364			}365		})366	}367}368369decl_event!(370	pub enum Event<T>371	where372		AccountId = <T as frame_system::Config>::AccountId,373		CrossAccountId = <T as Config>::CrossAccountId,374	{375		/// New collection was created376		///377		/// # Arguments378		///379		/// * collection_id: Globally unique identifier of newly created collection.380		///381		/// * mode: [CollectionMode] converted into u8.382		///383		/// * account_id: Collection owner.384		CollectionCreated(CollectionId, u8, AccountId),385386		/// New item was created.387		///388		/// # Arguments389		///390		/// * collection_id: Id of the collection where item was created.391		///392		/// * item_id: Id of an item. Unique within the collection.393		///394		/// * recipient: Owner of newly created item395		ItemCreated(CollectionId, TokenId, CrossAccountId),396397		/// Collection item was burned.398		///399		/// # Arguments400		///401		/// collection_id.402		///403		/// item_id: Identifier of burned NFT.404		ItemDestroyed(CollectionId, TokenId),405406		/// Item was transferred407		///408		/// * collection_id: Id of collection to which item is belong409		///410		/// * item_id: Id of an item411		///412		/// * sender: Original owner of item413		///414		/// * recipient: New owner of item415		///416		/// * amount: Always 1 for NFT417		Transfer(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),418419		/// * collection_id420		///421		/// * item_id422		///423		/// * sender424		///425		/// * spender426		///427		/// * amount428		Approved(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),429	}430);431432decl_module! {433	pub struct Module<T: Config> for enum Call434	where435		origin: T::Origin436	{437		fn deposit_event() = default;438		type Error = Error<T>;439440		fn on_initialize(_now: T::BlockNumber) -> Weight {441			0442		}443444		/// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.445		///446		/// # Permissions447		///448		/// * Anyone.449		///450		/// # Arguments451		///452		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.453		///454		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.455		///456		/// * token_prefix: UTF-8 string with token prefix.457		///458		/// * mode: [CollectionMode] collection type and type dependent data.459		// returns collection ID460		#[weight = <T as Config>::WeightInfo::create_collection()]461		#[transactional]462		pub fn create_collection(origin,463								 collection_name: Vec<u16>,464								 collection_description: Vec<u16>,465								 token_prefix: Vec<u8>,466								 mode: CollectionMode) -> DispatchResult {467468			// Anyone can create a collection469			let who = ensure_signed(origin)?;470471			// Take a (non-refundable) deposit of collection creation472			let mut imbalance = <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();473			imbalance.subsume(<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(474				&T::TreasuryAccountId::get(),475				T::CollectionCreationPrice::get(),476			));477			<T as Config>::Currency::settle(478				&who,479				imbalance,480				WithdrawReasons::TRANSFER,481				ExistenceRequirement::KeepAlive,482			).map_err(|_| Error::<T>::NoPermission)?;483484			let decimal_points = match mode {485				CollectionMode::Fungible(points) => points,486				_ => 0487			};488489			let chain_limit = ChainLimit::get();490491			let created_count = CreatedCollectionCount::get();492			let destroyed_count = DestroyedCollectionCount::get();493494			// bound Total number of collections495			ensure!(created_count - destroyed_count < chain_limit.collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);496497			// check params498			ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);499			ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);500			ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);501			ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);502503			// Generate next collection ID504			let next_id = created_count505				.checked_add(1)506				.ok_or(Error::<T>::NumOverflow)?;507508			CreatedCollectionCount::put(next_id);509510			let limits = CollectionLimits {511				sponsored_data_size: chain_limit.custom_data_limit,512				..Default::default()513			};514515			// Create new collection516			let new_collection = Collection {517				owner: who.clone(),518				name: collection_name,519				mode: mode.clone(),520				mint_mode: false,521				access: AccessMode::Normal,522				description: collection_description,523				decimal_points,524				token_prefix,525				offchain_schema: Vec::new(),526				schema_version: SchemaVersion::ImageURL,527				sponsorship: SponsorshipState::Disabled,528				variable_on_chain_schema: Vec::new(),529				const_on_chain_schema: Vec::new(),530				limits,531				transfers_enabled: true,532			};533534			// Add new collection to map535			<CollectionById<T>>::insert(next_id, new_collection);536537			// call event538			Self::deposit_event(RawEvent::CollectionCreated(next_id, mode.id(), who));539540			Ok(())541		}542543		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.544		///545		/// # Permissions546		///547		/// * Collection Owner.548		///549		/// # Arguments550		///551		/// * collection_id: collection to destroy.552		#[weight = <T as Config>::WeightInfo::destroy_collection()]553		#[transactional]554		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {555556			let sender = ensure_signed(origin)?;557			let collection = Self::get_collection(collection_id)?;558			Self::check_owner_permissions(&collection, &sender)?;559			if !collection.limits.owner_can_destroy {560				fail!(Error::<T>::NoPermission);561			}562563			<AddressTokens<T>>::remove_prefix(collection_id, None);564			<Allowances<T>>::remove_prefix(collection_id, None);565			<Balance<T>>::remove_prefix(collection_id, None);566			<ItemListIndex>::remove(collection_id);567			<AdminList<T>>::remove(collection_id);568			<CollectionById<T>>::remove(collection_id);569			<WhiteList<T>>::remove_prefix(collection_id, None);570571			<NftItemList<T>>::remove_prefix(collection_id, None);572			<FungibleItemList<T>>::remove_prefix(collection_id, None);573			<ReFungibleItemList<T>>::remove_prefix(collection_id, None);574575			<NftTransferBasket<T>>::remove_prefix(collection_id, None);576			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);577			<ReFungibleTransferBasket<T>>::remove_prefix(collection_id, None);578579			<VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);580581			DestroyedCollectionCount::put(DestroyedCollectionCount::get()582				.checked_add(1)583				.ok_or(Error::<T>::NumOverflow)?);584585			Ok(())586		}587588		/// Add an address to white list.589		///590		/// # Permissions591		///592		/// * Collection Owner593		/// * Collection Admin594		///595		/// # Arguments596		///597		/// * collection_id.598		///599		/// * address.600		#[weight = <T as Config>::WeightInfo::add_to_white_list()]601		#[transactional]602		pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{603604			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);605			let collection = Self::get_collection(collection_id)?;606607			Self::toggle_white_list_internal(608				&sender,609				&collection,610				&address,611				true,612			)?;613614			Ok(())615		}616617		/// Remove an address from white list.618		///619		/// # Permissions620		///621		/// * Collection Owner622		/// * Collection Admin623		///624		/// # Arguments625		///626		/// * collection_id.627		///628		/// * address.629		#[weight = <T as Config>::WeightInfo::remove_from_white_list()]630		#[transactional]631		pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{632633			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);634			let collection = Self::get_collection(collection_id)?;635636			Self::toggle_white_list_internal(637				&sender,638				&collection,639				&address,640				false,641			)?;642643			Ok(())644		}645646		/// Toggle between normal and white list access for the methods with access for `Anyone`.647		///648		/// # Permissions649		///650		/// * Collection Owner.651		///652		/// # Arguments653		///654		/// * collection_id.655		///656		/// * mode: [AccessMode]657		#[weight = <T as Config>::WeightInfo::set_public_access_mode()]658		#[transactional]659		pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult660		{661			let sender = ensure_signed(origin)?;662663			let mut target_collection = Self::get_collection(collection_id)?;664			Self::check_owner_permissions(&target_collection, &sender)?;665			target_collection.access = mode;666			target_collection.save()667		}668669		/// Allows Anyone to create tokens if:670		/// * White List is enabled, and671		/// * Address is added to white list, and672		/// * This method was called with True parameter673		///674		/// # Permissions675		/// * Collection Owner676		///677		/// # Arguments678		///679		/// * collection_id.680		///681		/// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.682		#[weight = <T as Config>::WeightInfo::set_mint_permission()]683		#[transactional]684		pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult685		{686			let sender = ensure_signed(origin)?;687688			let mut target_collection = Self::get_collection(collection_id)?;689			Self::check_owner_permissions(&target_collection, &sender)?;690			target_collection.mint_mode = mint_permission;691			target_collection.save()692		}693694		/// Change the owner of the collection.695		///696		/// # Permissions697		///698		/// * Collection Owner.699		///700		/// # Arguments701		///702		/// * collection_id.703		///704		/// * new_owner.705		#[weight = <T as Config>::WeightInfo::change_collection_owner()]706		#[transactional]707		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {708709			let sender = ensure_signed(origin)?;710			let mut target_collection = Self::get_collection(collection_id)?;711			Self::check_owner_permissions(&target_collection, &sender)?;712			target_collection.owner = new_owner;713			target_collection.save()714		}715716		/// Adds an admin of the Collection.717		/// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership.718		///719		/// # Permissions720		///721		/// * Collection Owner.722		/// * Collection Admin.723		///724		/// # Arguments725		///726		/// * collection_id: ID of the Collection to add admin for.727		///728		/// * new_admin_id: Address of new admin to add.729		#[weight = <T as Config>::WeightInfo::add_collection_admin()]730		#[transactional]731		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {732			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);733			let collection = Self::get_collection(collection_id)?;734			Self::check_owner_or_admin_permissions(&collection, &sender)?;735			let mut admin_arr = <AdminList<T>>::get(collection_id);736737			match admin_arr.binary_search(&new_admin_id) {738				Ok(_) => {},739				Err(idx) => {740					let limits = ChainLimit::get();741					ensure!(admin_arr.len() < limits.collections_admins_limit as usize, Error::<T>::CollectionAdminsLimitExceeded);742					admin_arr.insert(idx, new_admin_id);743					<AdminList<T>>::insert(collection_id, admin_arr);744				}745			}746			Ok(())747		}748749		/// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.750		///751		/// # Permissions752		///753		/// * Collection Owner.754		/// * Collection Admin.755		///756		/// # Arguments757		///758		/// * collection_id: ID of the Collection to remove admin for.759		///760		/// * account_id: Address of admin to remove.761		#[weight = <T as Config>::WeightInfo::remove_collection_admin()]762		#[transactional]763		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {764			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);765			let collection = Self::get_collection(collection_id)?;766			Self::check_owner_or_admin_permissions(&collection, &sender)?;767			let mut admin_arr = <AdminList<T>>::get(collection_id);768769			if let Ok(idx) = admin_arr.binary_search(&account_id) {770				admin_arr.remove(idx);771				<AdminList<T>>::insert(collection_id, admin_arr);772			}773			Ok(())774		}775776		/// # Permissions777		///778		/// * Collection Owner779		///780		/// # Arguments781		///782		/// * collection_id.783		///784		/// * new_sponsor.785		#[weight = <T as Config>::WeightInfo::set_collection_sponsor()]786		#[transactional]787		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {788			let sender = ensure_signed(origin)?;789			let mut target_collection = Self::get_collection(collection_id)?;790			Self::check_owner_permissions(&target_collection, &sender)?;791792			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);793			target_collection.save()794		}795796		/// # Permissions797		///798		/// * Sponsor.799		///800		/// # Arguments801		///802		/// * collection_id.803		#[weight = <T as Config>::WeightInfo::confirm_sponsorship()]804		#[transactional]805		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {806			let sender = ensure_signed(origin)?;807808			let mut target_collection = Self::get_collection(collection_id)?;809			ensure!(810				target_collection.sponsorship.pending_sponsor() == Some(&sender),811				Error::<T>::ConfirmUnsetSponsorFail812			);813814			target_collection.sponsorship = SponsorshipState::Confirmed(sender);815			target_collection.save()816		}817818		/// Switch back to pay-per-own-transaction model.819		///820		/// # Permissions821		///822		/// * Collection owner.823		///824		/// # Arguments825		///826		/// * collection_id.827		#[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]828		#[transactional]829		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {830			let sender = ensure_signed(origin)?;831832			let mut target_collection = Self::get_collection(collection_id)?;833			Self::check_owner_permissions(&target_collection, &sender)?;834835			target_collection.sponsorship = SponsorshipState::Disabled;836			target_collection.save()837		}838839		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.840		///841		/// # Permissions842		///843		/// * Collection Owner.844		/// * Collection Admin.845		/// * Anyone if846		///     * White List is enabled, and847		///     * Address is added to white list, and848		///     * MintPermission is enabled (see SetMintPermission method)849		///850		/// # Arguments851		///852		/// * collection_id: ID of the collection.853		///854		/// * owner: Address, initial owner of the NFT.855		///856		/// * data: Token data to store on chain.857		// #[weight =858		// (130_000_000 as Weight)859		// .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))860		// .saturating_add(RocksDbWeight::get().reads(10 as Weight))861		// .saturating_add(RocksDbWeight::get().writes(8 as Weight))]862863		#[weight = <T as Config>::WeightInfo::create_item(data.data_size())]864		#[transactional]865		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResult {866			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);867			let collection = Self::get_collection(collection_id)?;868869			Self::create_item_internal(&sender, &collection, &owner, data)?;870871			collection.submit_logs()872		}873874		/// This method creates multiple items in a collection created with CreateCollection method.875		///876		/// # Permissions877		///878		/// * Collection Owner.879		/// * Collection Admin.880		/// * Anyone if881		///     * White List is enabled, and882		///     * Address is added to white list, and883		///     * MintPermission is enabled (see SetMintPermission method)884		///885		/// # Arguments886		///887		/// * collection_id: ID of the collection.888		///889		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].890		///891		/// * owner: Address, initial owner of the NFT.892		#[weight = <T as Config>::WeightInfo::create_item(items_data.iter()893							   .map(|data| { data.data_size() })894							   .sum())]895		#[transactional]896		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResult {897898			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);899			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);900			let collection = Self::get_collection(collection_id)?;901902			Self::create_multiple_items_internal(&sender, &collection, &owner, items_data)?;903904			collection.submit_logs()905		}906907		// TODO! transaction weight908909		/// Set transfers_enabled value for particular collection910		///911		/// # Permissions912		///913		/// * Collection Owner.914		///915		/// # Arguments916		///917		/// * collection_id: ID of the collection.918		///919		/// * value: New flag value.920		#[weight = <T as Config>::WeightInfo::burn_item()]921		#[transactional]922		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {923924			let sender = ensure_signed(origin)?;925			let mut target_collection = Self::get_collection(collection_id)?;926927			Self::check_owner_permissions(&target_collection, &sender)?;928929			target_collection.transfers_enabled = value;930			target_collection.save()931		}932933		/// Destroys a concrete instance of NFT.934		///935		/// # Permissions936		///937		/// * Collection Owner.938		/// * Collection Admin.939		/// * Current NFT Owner.940		///941		/// # Arguments942		///943		/// * collection_id: ID of the collection.944		///945		/// * item_id: ID of NFT to burn.946		#[weight = <T as Config>::WeightInfo::burn_item()]947		#[transactional]948		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {949950			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);951			let target_collection = Self::get_collection(collection_id)?;952953			Self::burn_item_internal(&sender, &target_collection, item_id, value)?;954955			target_collection.submit_logs()956		}957958		/// Change ownership of the token.959		///960		/// # Permissions961		///962		/// * Collection Owner963		/// * Collection Admin964		/// * Current NFT owner965		///966		/// # Arguments967		///968		/// * recipient: Address of token recipient.969		///970		/// * collection_id.971		///972		/// * item_id: ID of the item973		///     * Non-Fungible Mode: Required.974		///     * Fungible Mode: Ignored.975		///     * Re-Fungible Mode: Required.976		///977		/// * value: Amount to transfer.978		///     * Non-Fungible Mode: Ignored979		///     * Fungible Mode: Must specify transferred amount980		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)981		#[weight = <T as Config>::WeightInfo::transfer()]982		#[transactional]983		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {984			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);985			let collection = Self::get_collection(collection_id)?;986987			Self::transfer_internal(&sender, &recipient, &collection, item_id, value)?;988989			collection.submit_logs()990		}991992		/// Set, change, or remove approved address to transfer the ownership of the NFT.993		///994		/// # Permissions995		///996		/// * Collection Owner997		/// * Collection Admin998		/// * Current NFT owner999		///1000		/// # Arguments1001		///1002		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1003		///1004		/// * collection_id.1005		///1006		/// * item_id: ID of the item.1007		#[weight = <T as Config>::WeightInfo::approve()]1008		#[transactional]1009		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {1010			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1011			let collection = Self::get_collection(collection_id)?;10121013			Self::approve_internal(&sender, &spender, &collection, item_id, amount)?;10141015			collection.submit_logs()1016		}10171018		/// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.1019		///1020		/// # Permissions1021		/// * Collection Owner1022		/// * Collection Admin1023		/// * Current NFT owner1024		/// * Address approved by current NFT owner1025		///1026		/// # Arguments1027		///1028		/// * from: Address that owns token.1029		///1030		/// * recipient: Address of token recipient.1031		///1032		/// * collection_id.1033		///1034		/// * item_id: ID of the item.1035		///1036		/// * value: Amount to transfer.1037		#[weight = <T as Config>::WeightInfo::transfer_from()]1038		#[transactional]1039		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {1040			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1041			let collection = Self::get_collection(collection_id)?;10421043			Self::transfer_from_internal(&sender, &from, &recipient, &collection, item_id, value)?;10441045			collection.submit_logs()1046		}1047		// #[weight = 0]1048		//     // let no_perm_mes = "You do not have permissions to modify this collection";1049		//     // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1050		//     // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1051		//     // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);10521053		//     // // on_nft_received  call10541055		//     // Self::transfer(origin, collection_id, item_id, new_owner)?;10561057		//     Ok(())1058		// }10591060		/// Set off-chain data schema.1061		///1062		/// # Permissions1063		///1064		/// * Collection Owner1065		/// * Collection Admin1066		///1067		/// # Arguments1068		///1069		/// * collection_id.1070		///1071		/// * schema: String representing the offchain data schema.1072		#[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1073		#[transactional]1074		pub fn set_variable_meta_data (1075			origin,1076			collection_id: CollectionId,1077			item_id: TokenId,1078			data: Vec<u8>1079		) -> DispatchResult {1080			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);10811082			let collection = Self::get_collection(collection_id)?;10831084			Self::set_variable_meta_data_internal(&sender, &collection, item_id, data)?;10851086			Ok(())1087		}10881089		/// Set schema standard1090		/// ImageURL1091		/// Unique1092		///1093		/// # Permissions1094		///1095		/// * Collection Owner1096		/// * Collection Admin1097		///1098		/// # Arguments1099		///1100		/// * collection_id.1101		///1102		/// * schema: SchemaVersion: enum1103		#[weight = <T as Config>::WeightInfo::set_schema_version()]1104		#[transactional]1105		pub fn set_schema_version(1106			origin,1107			collection_id: CollectionId,1108			version: SchemaVersion1109		) -> DispatchResult {1110			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1111			let mut target_collection = Self::get_collection(collection_id)?;1112			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;1113			target_collection.schema_version = version;1114			target_collection.save()1115		}11161117		/// Set off-chain data schema.1118		///1119		/// # Permissions1120		///1121		/// * Collection Owner1122		/// * Collection Admin1123		///1124		/// # Arguments1125		///1126		/// * collection_id.1127		///1128		/// * schema: String representing the offchain data schema.1129		#[weight = <T as Config>::WeightInfo::set_offchain_schema()]1130		#[transactional]1131		pub fn set_offchain_schema(1132			origin,1133			collection_id: CollectionId,1134			schema: Vec<u8>1135		) -> DispatchResult {1136			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1137			let mut target_collection = Self::get_collection(collection_id)?;1138			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11391140			// check schema limit1141			ensure!(schema.len() as u32 <= ChainLimit::get().offchain_schema_limit, "");11421143			target_collection.offchain_schema = schema;1144			target_collection.save()1145		}11461147		/// Set const on-chain data schema.1148		///1149		/// # Permissions1150		///1151		/// * Collection Owner1152		/// * Collection Admin1153		///1154		/// # Arguments1155		///1156		/// * collection_id.1157		///1158		/// * schema: String representing the const on-chain data schema.1159		#[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1160		#[transactional]1161		pub fn set_const_on_chain_schema (1162			origin,1163			collection_id: CollectionId,1164			schema: Vec<u8>1165		) -> DispatchResult {1166			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1167			let mut target_collection = Self::get_collection(collection_id)?;1168			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11691170			// check schema limit1171			ensure!(schema.len() as u32 <= ChainLimit::get().const_on_chain_schema_limit, "");11721173			target_collection.const_on_chain_schema = schema;1174			target_collection.save()1175		}11761177		/// Set variable on-chain data schema.1178		///1179		/// # Permissions1180		///1181		/// * Collection Owner1182		/// * Collection Admin1183		///1184		/// # Arguments1185		///1186		/// * collection_id.1187		///1188		/// * schema: String representing the variable on-chain data schema.1189		#[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1190		#[transactional]1191		pub fn set_variable_on_chain_schema (1192			origin,1193			collection_id: CollectionId,1194			schema: Vec<u8>1195		) -> DispatchResult {1196			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1197			let mut target_collection = Self::get_collection(collection_id)?;1198			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11991200			// check schema limit1201			ensure!(schema.len() as u32 <= ChainLimit::get().variable_on_chain_schema_limit, "");12021203			target_collection.variable_on_chain_schema = schema;1204			target_collection.save()1205		}12061207		// Sudo permissions function1208		#[weight = <T as Config>::WeightInfo::set_chain_limits()]1209		#[transactional]1210		pub fn set_chain_limits(1211			origin,1212			limits: ChainLimits1213		) -> DispatchResult {12141215			#[cfg(not(feature = "runtime-benchmarks"))]1216			ensure_root(origin)?;12171218			<ChainLimit>::put(limits);1219			Ok(())1220		}12211222		#[weight = <T as Config>::WeightInfo::set_collection_limits()]1223		#[transactional]1224		pub fn set_collection_limits(1225			origin,1226			collection_id: u32,1227			new_limits: CollectionLimits<T::BlockNumber>,1228		) -> DispatchResult {1229			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1230			let mut target_collection = Self::get_collection(collection_id)?;1231			Self::check_owner_permissions(&target_collection, sender.as_sub())?;1232			let old_limits = &target_collection.limits;1233			let chain_limits = ChainLimit::get();12341235			// collection bounds1236			ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1237				new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP &&1238				new_limits.sponsored_data_size <= chain_limits.custom_data_limit,1239				Error::<T>::CollectionLimitBoundsExceeded);12401241			// token_limit   check  prev1242			ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1243			ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);12441245			ensure!(1246				(old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1247				(old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1248				Error::<T>::OwnerPermissionsCantBeReverted,1249			);12501251			target_collection.limits = new_limits;12521253			target_collection.save()1254		}1255	}1256}12571258impl<T: Config> Module<T> {1259	pub fn create_item_internal(1260		sender: &T::CrossAccountId,1261		collection: &CollectionHandle<T>,1262		owner: &T::CrossAccountId,1263		data: CreateItemData,1264	) -> DispatchResult {1265		Self::can_create_items_in_collection(collection, sender, owner, 1)?;1266		Self::validate_create_item_args(collection, &data)?;1267		Self::create_item_no_validation(collection, owner, data)?;12681269		Ok(())1270	}12711272	pub fn transfer_internal(1273		sender: &T::CrossAccountId,1274		recipient: &T::CrossAccountId,1275		target_collection: &CollectionHandle<T>,1276		item_id: TokenId,1277		value: u128,1278	) -> DispatchResult {1279		target_collection.consume_gas(2000000)?;1280		// Limits check1281		Self::is_correct_transfer(target_collection, recipient)?;12821283		// Transfer permissions check1284		ensure!(1285			Self::is_item_owner(sender, target_collection, item_id)1286				|| Self::is_owner_or_admin_permissions(target_collection, sender),1287			Error::<T>::NoPermission1288		);12891290		if target_collection.access == AccessMode::WhiteList {1291			Self::check_white_list(target_collection, sender)?;1292			Self::check_white_list(target_collection, recipient)?;1293		}12941295		match target_collection.mode {1296			CollectionMode::NFT => Self::transfer_nft(1297				target_collection,1298				item_id,1299				sender.clone(),1300				recipient.clone(),1301			)?,1302			CollectionMode::Fungible(_) => {1303				Self::transfer_fungible(target_collection, value, sender, recipient)?1304			}1305			CollectionMode::ReFungible => Self::transfer_refungible(1306				target_collection,1307				item_id,1308				value,1309				sender.clone(),1310				recipient.clone(),1311			)?,1312			_ => (),1313		};13141315		Self::deposit_event(RawEvent::Transfer(1316			target_collection.id,1317			item_id,1318			sender.clone(),1319			recipient.clone(),1320			value,1321		));13221323		Ok(())1324	}13251326	pub fn approve_internal(1327		sender: &T::CrossAccountId,1328		spender: &T::CrossAccountId,1329		collection: &CollectionHandle<T>,1330		item_id: TokenId,1331		amount: u128,1332	) -> DispatchResult {1333		collection.consume_gas(2000000)?;1334		Self::token_exists(collection, item_id)?;13351336		// Transfer permissions check1337		let bypasses_limits = collection.limits.owner_can_transfer1338			&& Self::is_owner_or_admin_permissions(collection, sender);13391340		let allowance_limit = if bypasses_limits {1341			None1342		} else if let Some(amount) = Self::owned_amount(sender, collection, item_id) {1343			Some(amount)1344		} else {1345			fail!(Error::<T>::NoPermission);1346		};13471348		if collection.access == AccessMode::WhiteList {1349			Self::check_white_list(collection, sender)?;1350			Self::check_white_list(collection, spender)?;1351		}13521353		let allowance: u128 = amount1354			.checked_add(<Allowances<T>>::get(1355				collection.id,1356				(item_id, sender.as_sub(), spender.as_sub()),1357			))1358			.ok_or(Error::<T>::NumOverflow)?;1359		if let Some(limit) = allowance_limit {1360			ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1361		}1362		<Allowances<T>>::insert(1363			collection.id,1364			(item_id, sender.as_sub(), spender.as_sub()),1365			allowance,1366		);13671368		if matches!(collection.mode, CollectionMode::NFT) {1369			// TODO: NFT: only one owner may exist for token in ERC7211370			collection.log(ERC721Events::Approval {1371				owner: *sender.as_eth(),1372				approved: *spender.as_eth(),1373				token_id: item_id.into(),1374			})?;1375		}13761377		if matches!(collection.mode, CollectionMode::Fungible(_)) {1378			// TODO: NFT: only one owner may exist for token in ERC201379			collection.log(ERC20Events::Approval {1380				owner: *sender.as_eth(),1381				spender: *spender.as_eth(),1382				value: allowance.into(),1383			})?;1384		}13851386		Self::deposit_event(RawEvent::Approved(1387			collection.id,1388			item_id,1389			sender.clone(),1390			spender.clone(),1391			allowance,1392		));1393		Ok(())1394	}13951396	pub fn transfer_from_internal(1397		sender: &T::CrossAccountId,1398		from: &T::CrossAccountId,1399		recipient: &T::CrossAccountId,1400		collection: &CollectionHandle<T>,1401		item_id: TokenId,1402		amount: u128,1403	) -> DispatchResult {1404		collection.consume_gas(2000000)?;1405		// Check approval1406		let approval: u128 =1407			<Allowances<T>>::get(collection.id, (item_id, from.as_sub(), sender.as_sub()));14081409		// Limits check1410		Self::is_correct_transfer(collection, recipient)?;14111412		// Transfer permissions check1413		ensure!(1414			approval >= amount1415				|| (collection.limits.owner_can_transfer1416					&& Self::is_owner_or_admin_permissions(collection, sender)),1417			Error::<T>::NoPermission1418		);14191420		if collection.access == AccessMode::WhiteList {1421			Self::check_white_list(collection, sender)?;1422			Self::check_white_list(collection, recipient)?;1423		}14241425		// Reduce approval by transferred amount or remove if remaining approval drops to 01426		let allowance = approval.saturating_sub(amount);1427		if allowance > 0 {1428			<Allowances<T>>::insert(1429				collection.id,1430				(item_id, from.as_sub(), sender.as_sub()),1431				allowance,1432			);1433		} else {1434			<Allowances<T>>::remove(collection.id, (item_id, from.as_sub(), sender.as_sub()));1435		}14361437		match collection.mode {1438			CollectionMode::NFT => {1439				Self::transfer_nft(collection, item_id, from.clone(), recipient.clone())?1440			}1441			CollectionMode::Fungible(_) => {1442				Self::transfer_fungible(collection, amount, from, recipient)?1443			}1444			CollectionMode::ReFungible => Self::transfer_refungible(1445				collection,1446				item_id,1447				amount,1448				from.clone(),1449				recipient.clone(),1450			)?,1451			_ => (),1452		};14531454		if matches!(collection.mode, CollectionMode::Fungible(_)) {1455			collection.log(ERC20Events::Approval {1456				owner: *from.as_eth(),1457				spender: *sender.as_eth(),1458				value: allowance.into(),1459			})?;1460		}14611462		Ok(())1463	}14641465	pub fn set_variable_meta_data_internal(1466		sender: &T::CrossAccountId,1467		collection: &CollectionHandle<T>,1468		item_id: TokenId,1469		data: Vec<u8>,1470	) -> DispatchResult {1471		Self::token_exists(collection, item_id)?;14721473		ensure!(1474			ChainLimit::get().custom_data_limit >= data.len() as u32,1475			Error::<T>::TokenVariableDataLimitExceeded1476		);14771478		// Modify permissions check1479		ensure!(1480			Self::is_item_owner(sender, collection, item_id)1481				|| Self::is_owner_or_admin_permissions(collection, sender),1482			Error::<T>::NoPermission1483		);14841485		match collection.mode {1486			CollectionMode::NFT => Self::set_nft_variable_data(collection, item_id, data)?,1487			CollectionMode::ReFungible => {1488				Self::set_re_fungible_variable_data(collection, item_id, data)?1489			}1490			CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1491			_ => fail!(Error::<T>::UnexpectedCollectionType),1492		};14931494		Ok(())1495	}14961497	pub fn create_multiple_items_internal(1498		sender: &T::CrossAccountId,1499		collection: &CollectionHandle<T>,1500		owner: &T::CrossAccountId,1501		items_data: Vec<CreateItemData>,1502	) -> DispatchResult {1503		Self::can_create_items_in_collection(collection, sender, owner, items_data.len() as u32)?;15041505		for data in &items_data {1506			Self::validate_create_item_args(collection, data)?;1507		}1508		for data in &items_data {1509			Self::create_item_no_validation(collection, owner, data.clone())?;1510		}15111512		Ok(())1513	}15141515	pub fn burn_item_internal(1516		sender: &T::CrossAccountId,1517		collection: &CollectionHandle<T>,1518		item_id: TokenId,1519		value: u128,1520	) -> DispatchResult {1521		ensure!(1522			Self::is_item_owner(sender, collection, item_id)1523				|| (collection.limits.owner_can_transfer1524					&& Self::is_owner_or_admin_permissions(collection, sender)),1525			Error::<T>::NoPermission1526		);15271528		if collection.access == AccessMode::WhiteList {1529			Self::check_white_list(collection, sender)?;1530		}15311532		match collection.mode {1533			CollectionMode::NFT => Self::burn_nft_item(collection, item_id)?,1534			CollectionMode::Fungible(_) => Self::burn_fungible_item(sender, collection, value)?,1535			CollectionMode::ReFungible => Self::burn_refungible_item(collection, item_id, sender)?,1536			_ => (),1537		};15381539		Ok(())1540	}15411542	pub fn toggle_white_list_internal(1543		sender: &T::CrossAccountId,1544		collection: &CollectionHandle<T>,1545		address: &T::CrossAccountId,1546		whitelisted: bool,1547	) -> DispatchResult {1548		Self::check_owner_or_admin_permissions(collection, sender)?;15491550		if whitelisted {1551			<WhiteList<T>>::insert(collection.id, address.as_sub(), true);1552		} else {1553			<WhiteList<T>>::remove(collection.id, address.as_sub());1554		}15551556		Ok(())1557	}15581559	fn is_correct_transfer(1560		collection: &CollectionHandle<T>,1561		recipient: &T::CrossAccountId,1562	) -> DispatchResult {1563		let collection_id = collection.id;15641565		// check token limit and account token limit1566		let account_items: u32 =1567			<AddressTokens<T>>::get(collection_id, recipient.as_sub()).len() as u32;1568		ensure!(1569			collection.limits.account_token_ownership_limit > account_items,1570			Error::<T>::AccountTokenLimitExceeded1571		);15721573		// preliminary transfer check1574		ensure!(collection.transfers_enabled, Error::<T>::TransferNotAllowed);15751576		Ok(())1577	}15781579	fn can_create_items_in_collection(1580		collection: &CollectionHandle<T>,1581		sender: &T::CrossAccountId,1582		owner: &T::CrossAccountId,1583		amount: u32,1584	) -> DispatchResult {1585		let collection_id = collection.id;15861587		// check token limit and account token limit1588		let total_items: u32 = ItemListIndex::get(collection_id)1589			.checked_add(amount)1590			.ok_or(Error::<T>::CollectionTokenLimitExceeded)?;1591		let account_items: u32 = (<AddressTokens<T>>::get(collection_id, owner.as_sub()).len()1592			as u32)1593			.checked_add(amount)1594			.ok_or(Error::<T>::AccountTokenLimitExceeded)?;1595		ensure!(1596			collection.limits.token_limit >= total_items,1597			Error::<T>::CollectionTokenLimitExceeded1598		);1599		ensure!(1600			collection.limits.account_token_ownership_limit >= account_items,1601			Error::<T>::AccountTokenLimitExceeded1602		);16031604		if !Self::is_owner_or_admin_permissions(collection, sender) {1605			ensure!(collection.mint_mode, Error::<T>::PublicMintingNotAllowed);1606			Self::check_white_list(collection, owner)?;1607			Self::check_white_list(collection, sender)?;1608		}16091610		Ok(())1611	}16121613	fn validate_create_item_args(1614		target_collection: &CollectionHandle<T>,1615		data: &CreateItemData,1616	) -> DispatchResult {1617		match target_collection.mode {1618			CollectionMode::NFT => {1619				if let CreateItemData::NFT(data) = data {1620					// check sizes1621					ensure!(1622						ChainLimit::get().custom_data_limit >= data.const_data.len() as u32,1623						Error::<T>::TokenConstDataLimitExceeded1624					);1625					ensure!(1626						ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32,1627						Error::<T>::TokenVariableDataLimitExceeded1628					);1629				} else {1630					fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1631				}1632			}1633			CollectionMode::Fungible(_) => {1634				if let CreateItemData::Fungible(_) = data {1635				} else {1636					fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1637				}1638			}1639			CollectionMode::ReFungible => {1640				if let CreateItemData::ReFungible(data) = data {1641					// check sizes1642					ensure!(1643						ChainLimit::get().custom_data_limit >= data.const_data.len() as u32,1644						Error::<T>::TokenConstDataLimitExceeded1645					);1646					ensure!(1647						ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32,1648						Error::<T>::TokenVariableDataLimitExceeded1649					);16501651					// Check refungibility limits1652					ensure!(1653						data.pieces <= MAX_REFUNGIBLE_PIECES,1654						Error::<T>::WrongRefungiblePieces1655					);1656					ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1657				} else {1658					fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1659				}1660			}1661			_ => {1662				fail!(Error::<T>::UnexpectedCollectionType);1663			}1664		};16651666		Ok(())1667	}16681669	fn create_item_no_validation(1670		collection: &CollectionHandle<T>,1671		owner: &T::CrossAccountId,1672		data: CreateItemData,1673	) -> DispatchResult {1674		match data {1675			CreateItemData::NFT(data) => {1676				let item = NftItemType {1677					owner: owner.clone(),1678					const_data: data.const_data,1679					variable_data: data.variable_data,1680				};16811682				Self::add_nft_item(collection, item)?;1683			}1684			CreateItemData::Fungible(data) => {1685				Self::add_fungible_item(collection, owner, data.value)?;1686			}1687			CreateItemData::ReFungible(data) => {1688				let owner_list = vec![Ownership {1689					owner: owner.clone(),1690					fraction: data.pieces,1691				}];16921693				let item = ReFungibleItemType {1694					owner: owner_list,1695					const_data: data.const_data,1696					variable_data: data.variable_data,1697				};16981699				Self::add_refungible_item(collection, item)?;1700			}1701		};17021703		Ok(())1704	}17051706	fn add_fungible_item(1707		collection: &CollectionHandle<T>,1708		owner: &T::CrossAccountId,1709		value: u128,1710	) -> DispatchResult {1711		let collection_id = collection.id;17121713		// Does new owner already have an account?1714		let balance: u128 = <FungibleItemList<T>>::get(collection_id, owner.as_sub()).value;17151716		// Mint1717		let item = FungibleItemType {1718			value: balance.checked_add(value).ok_or(Error::<T>::NumOverflow)?,1719		};1720		<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), item);17211722		// Update balance1723		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1724			.checked_add(value)1725			.ok_or(Error::<T>::NumOverflow)?;1726		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);17271728		Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));1729		Ok(())1730	}17311732	fn add_refungible_item(1733		collection: &CollectionHandle<T>,1734		item: ReFungibleItemType<T::CrossAccountId>,1735	) -> DispatchResult {1736		let collection_id = collection.id;17371738		let current_index = <ItemListIndex>::get(collection_id)1739			.checked_add(1)1740			.ok_or(Error::<T>::NumOverflow)?;1741		let itemcopy = item.clone();17421743		ensure!(item.owner.len() == 1, Error::<T>::BadCreateRefungibleCall,);1744		let item_owner = item.owner.first().expect("only one owner is defined");17451746		let value = item_owner.fraction;1747		let owner = item_owner.owner.clone();17481749		Self::add_token_index(collection_id, current_index, &owner)?;17501751		<ItemListIndex>::insert(collection_id, current_index);1752		<ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);17531754		// Update balance1755		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1756			.checked_add(value)1757			.ok_or(Error::<T>::NumOverflow)?;1758		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);17591760		Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));1761		Ok(())1762	}17631764	fn add_nft_item(1765		collection: &CollectionHandle<T>,1766		item: NftItemType<T::CrossAccountId>,1767	) -> DispatchResult {1768		let collection_id = collection.id;17691770		let current_index = <ItemListIndex>::get(collection_id)1771			.checked_add(1)1772			.ok_or(Error::<T>::NumOverflow)?;17731774		let item_owner = item.owner.clone();1775		Self::add_token_index(collection_id, current_index, &item.owner)?;17761777		<ItemListIndex>::insert(collection_id, current_index);1778		<NftItemList<T>>::insert(collection_id, current_index, item);17791780		// Update balance1781		let new_balance = <Balance<T>>::get(collection_id, item_owner.as_sub())1782			.checked_add(1)1783			.ok_or(Error::<T>::NumOverflow)?;1784		<Balance<T>>::insert(collection_id, item_owner.as_sub(), new_balance);17851786		collection.log(ERC721Events::Transfer {1787			from: H160::default(),1788			to: *item_owner.as_eth(),1789			token_id: current_index.into(),1790		})?;1791		Self::deposit_event(RawEvent::ItemCreated(1792			collection_id,1793			current_index,1794			item_owner,1795		));1796		Ok(())1797	}17981799	fn burn_refungible_item(1800		collection: &CollectionHandle<T>,1801		item_id: TokenId,1802		owner: &T::CrossAccountId,1803	) -> DispatchResult {1804		let collection_id = collection.id;18051806		let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id)1807			.ok_or(Error::<T>::TokenNotFound)?;1808		let rft_balance = token1809			.owner1810			.iter()1811			.find(|&i| i.owner == *owner)1812			.ok_or(Error::<T>::TokenNotFound)?;1813		Self::remove_token_index(collection_id, item_id, owner)?;18141815		// update balance1816		let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.as_sub())1817			.checked_sub(rft_balance.fraction)1818			.ok_or(Error::<T>::NumOverflow)?;1819		<Balance<T>>::insert(collection_id, rft_balance.owner.as_sub(), new_balance);18201821		// Re-create owners list with sender removed1822		let index = token1823			.owner1824			.iter()1825			.position(|i| i.owner == *owner)1826			.expect("owned item is exists");1827		token.owner.remove(index);1828		let owner_count = token.owner.len();18291830		// Burn the token completely if this was the last (only) owner1831		if owner_count == 0 {1832			<ReFungibleItemList<T>>::remove(collection_id, item_id);1833			<VariableMetaDataBasket<T>>::remove(collection_id, item_id);1834		} else {1835			<ReFungibleItemList<T>>::insert(collection_id, item_id, token);1836		}18371838		Ok(())1839	}18401841	fn burn_nft_item(collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1842		let collection_id = collection.id;18431844		let item =1845			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;1846		Self::remove_token_index(collection_id, item_id, &item.owner)?;18471848		// update balance1849		let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())1850			.checked_sub(1)1851			.ok_or(Error::<T>::NumOverflow)?;1852		<Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);1853		<NftItemList<T>>::remove(collection_id, item_id);1854		<VariableMetaDataBasket<T>>::remove(collection_id, item_id);18551856		Self::deposit_event(RawEvent::ItemDestroyed(collection.id, item_id));1857		Ok(())1858	}18591860	fn burn_fungible_item(1861		owner: &T::CrossAccountId,1862		collection: &CollectionHandle<T>,1863		value: u128,1864	) -> DispatchResult {1865		let collection_id = collection.id;18661867		let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());1868		ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);18691870		// update balance1871		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1872			.checked_sub(value)1873			.ok_or(Error::<T>::NumOverflow)?;1874		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);18751876		if balance.value - value > 0 {1877			balance.value -= value;1878			<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);1879		} else {1880			<FungibleItemList<T>>::remove(collection_id, owner.as_sub());1881		}18821883		collection.log(ERC20Events::Transfer {1884			from: *owner.as_eth(),1885			to: H160::default(),1886			value: value.into(),1887		})?;1888		Ok(())1889	}18901891	pub fn get_collection(1892		collection_id: CollectionId,1893	) -> Result<CollectionHandle<T>, sp_runtime::DispatchError> {1894		Ok(<CollectionHandle<T>>::get(collection_id).ok_or(Error::<T>::CollectionNotFound)?)1895	}18961897	fn check_owner_permissions(1898		target_collection: &CollectionHandle<T>,1899		subject: &T::AccountId,1900	) -> DispatchResult {1901		ensure!(1902			*subject == target_collection.owner,1903			Error::<T>::NoPermission1904		);19051906		Ok(())1907	}19081909	fn is_owner_or_admin_permissions(1910		collection: &CollectionHandle<T>,1911		subject: &T::CrossAccountId,1912	) -> bool {1913		*subject.as_sub() == collection.owner1914			|| <AdminList<T>>::get(collection.id).contains(subject)1915	}19161917	fn check_owner_or_admin_permissions(1918		collection: &CollectionHandle<T>,1919		subject: &T::CrossAccountId,1920	) -> DispatchResult {1921		ensure!(1922			Self::is_owner_or_admin_permissions(collection, subject),1923			Error::<T>::NoPermission1924		);19251926		Ok(())1927	}19281929	fn owned_amount(1930		subject: &T::CrossAccountId,1931		target_collection: &CollectionHandle<T>,1932		item_id: TokenId,1933	) -> Option<u128> {1934		let collection_id = target_collection.id;19351936		match target_collection.mode {1937			CollectionMode::NFT => {1938				(<NftItemList<T>>::get(collection_id, item_id)?.owner == *subject).then(|| 1)1939			}1940			CollectionMode::Fungible(_) => {1941				Some(<FungibleItemList<T>>::get(collection_id, &subject.as_sub()).value)1942			}1943			CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)?1944				.owner1945				.iter()1946				.find(|i| i.owner == *subject)1947				.map(|i| i.fraction),1948			CollectionMode::Invalid => None,1949		}1950	}19511952	fn is_item_owner(1953		subject: &T::CrossAccountId,1954		target_collection: &CollectionHandle<T>,1955		item_id: TokenId,1956	) -> bool {1957		match target_collection.mode {1958			CollectionMode::Fungible(_) => true,1959			_ => Self::owned_amount(subject, target_collection, item_id).is_some(),1960		}1961	}19621963	fn check_white_list(1964		collection: &CollectionHandle<T>,1965		address: &T::CrossAccountId,1966	) -> DispatchResult {1967		let collection_id = collection.id;19681969		let mes = Error::<T>::AddresNotInWhiteList;1970		ensure!(1971			<WhiteList<T>>::contains_key(collection_id, address.as_sub()),1972			mes1973		);19741975		Ok(())1976	}19771978	/// Check if token exists. In case of Fungible, check if there is an entry for1979	/// the owner in fungible balances double map1980	fn token_exists(target_collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1981		let collection_id = target_collection.id;1982		let exists = match target_collection.mode {1983			CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),1984			CollectionMode::Fungible(_) => true,1985			CollectionMode::ReFungible => {1986				<ReFungibleItemList<T>>::contains_key(collection_id, item_id)1987			}1988			_ => false,1989		};19901991		ensure!(exists, Error::<T>::TokenNotFound);1992		Ok(())1993	}19941995	fn transfer_fungible(1996		collection: &CollectionHandle<T>,1997		value: u128,1998		owner: &T::CrossAccountId,1999		recipient: &T::CrossAccountId,2000	) -> DispatchResult {2001		let collection_id = collection.id;20022003		let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());2004		ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);20052006		// Send balance to recipient (updates balanceOf of recipient)2007		Self::add_fungible_item(collection, recipient, value)?;20082009		// update balanceOf of sender2010		<Balance<T>>::insert(collection_id, owner.as_sub(), balance.value - value);20112012		// Reduce or remove sender2013		if balance.value == value {2014			<FungibleItemList<T>>::remove(collection_id, owner.as_sub());2015		} else {2016			balance.value -= value;2017			<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);2018		}20192020		collection.log(ERC20Events::Transfer {2021			from: *owner.as_eth(),2022			to: *recipient.as_eth(),2023			value: value.into(),2024		})?;2025		Self::deposit_event(RawEvent::Transfer(2026			collection.id,2027			1,2028			owner.clone(),2029			recipient.clone(),2030			value,2031		));20322033		Ok(())2034	}20352036	fn transfer_refungible(2037		collection: &CollectionHandle<T>,2038		item_id: TokenId,2039		value: u128,2040		owner: T::CrossAccountId,2041		new_owner: T::CrossAccountId,2042	) -> DispatchResult {2043		let collection_id = collection.id;2044		let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id)2045			.ok_or(Error::<T>::TokenNotFound)?;20462047		let item = full_item2048			.owner2049			.iter()2050			.find(|i| i.owner == owner)2051			.ok_or(Error::<T>::TokenNotFound)?;2052		let amount = item.fraction;20532054		ensure!(amount >= value, Error::<T>::TokenValueTooLow);20552056		// update balance2057		let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2058			.checked_sub(value)2059			.ok_or(Error::<T>::NumOverflow)?;2060		<Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);20612062		let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2063			.checked_add(value)2064			.ok_or(Error::<T>::NumOverflow)?;2065		<Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);20662067		let old_owner = item.owner.clone();2068		let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);20692070		let mut new_full_item = full_item.clone();2071		// transfer2072		if amount == value && !new_owner_has_account {2073			// change owner2074			// new owner do not have account2075			new_full_item2076				.owner2077				.iter_mut()2078				.find(|i| i.owner == owner)2079				.expect("old owner does present in refungible")2080				.owner = new_owner.clone();2081			<ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);20822083			// update index collection2084			Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;2085		} else {2086			new_full_item2087				.owner2088				.iter_mut()2089				.find(|i| i.owner == owner)2090				.expect("old owner does present in refungible")2091				.fraction -= value;20922093			// separate amount2094			if new_owner_has_account {2095				// new owner has account2096				new_full_item2097					.owner2098					.iter_mut()2099					.find(|i| i.owner == new_owner)2100					.expect("new owner has account")2101					.fraction += value;2102			} else {2103				// new owner do not have account2104				new_full_item.owner.push(Ownership {2105					owner: new_owner.clone(),2106					fraction: value,2107				});2108				Self::add_token_index(collection_id, item_id, &new_owner)?;2109			}21102111			<ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2112		}21132114		Self::deposit_event(RawEvent::Transfer(2115			collection.id,2116			item_id,2117			owner,2118			new_owner,2119			amount,2120		));21212122		Ok(())2123	}21242125	fn transfer_nft(2126		collection: &CollectionHandle<T>,2127		item_id: TokenId,2128		sender: T::CrossAccountId,2129		new_owner: T::CrossAccountId,2130	) -> DispatchResult {2131		let collection_id = collection.id;2132		let mut item =2133			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;21342135		ensure!(sender == item.owner, Error::<T>::MustBeTokenOwner);21362137		// update balance2138		let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2139			.checked_sub(1)2140			.ok_or(Error::<T>::NumOverflow)?;2141		<Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);21422143		let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2144			.checked_add(1)2145			.ok_or(Error::<T>::NumOverflow)?;2146		<Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);21472148		// change owner2149		let old_owner = item.owner.clone();2150		item.owner = new_owner.clone();2151		<NftItemList<T>>::insert(collection_id, item_id, item);21522153		// update index collection2154		Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;21552156		collection.log(ERC721Events::Transfer {2157			from: *sender.as_eth(),2158			to: *new_owner.as_eth(),2159			token_id: item_id.into(),2160		})?;2161		Self::deposit_event(RawEvent::Transfer(2162			collection.id,2163			item_id,2164			sender,2165			new_owner,2166			1,2167		));21682169		Ok(())2170	}21712172	fn set_re_fungible_variable_data(2173		collection: &CollectionHandle<T>,2174		item_id: TokenId,2175		data: Vec<u8>,2176	) -> DispatchResult {2177		let collection_id = collection.id;2178		let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id)2179			.ok_or(Error::<T>::TokenNotFound)?;21802181		item.variable_data = data;21822183		<ReFungibleItemList<T>>::insert(collection_id, item_id, item);21842185		Ok(())2186	}21872188	fn set_nft_variable_data(2189		collection: &CollectionHandle<T>,2190		item_id: TokenId,2191		data: Vec<u8>,2192	) -> DispatchResult {2193		let collection_id = collection.id;2194		let mut item =2195			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;21962197		item.variable_data = data;21982199		<NftItemList<T>>::insert(collection_id, item_id, item);22002201		Ok(())2202	}22032204	#[allow(dead_code)]2205	fn init_collection(item: &Collection<T>) {2206		// check params2207		assert!(2208			item.decimal_points <= MAX_DECIMAL_POINTS,2209			"decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2210		);2211		assert!(2212			item.name.len() <= 64,2213			"Collection name can not be longer than 63 char"2214		);2215		assert!(2216			item.name.len() <= 256,2217			"Collection description can not be longer than 255 char"2218		);2219		assert!(2220			item.token_prefix.len() <= 16,2221			"Token prefix can not be longer than 15 char"2222		);22232224		// Generate next collection ID2225		let next_id = CreatedCollectionCount::get().checked_add(1).unwrap();22262227		CreatedCollectionCount::put(next_id);2228	}22292230	#[allow(dead_code)]2231	fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::CrossAccountId>) {2232		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();22332234		Self::add_token_index(collection_id, current_index, &item.owner).unwrap();22352236		<ItemListIndex>::insert(collection_id, current_index);22372238		// Update balance2239		let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2240			.checked_add(1)2241			.unwrap();2242		<Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2243	}22442245	#[allow(dead_code)]2246	fn init_fungible_token(2247		collection_id: CollectionId,2248		owner: &T::CrossAccountId,2249		item: &FungibleItemType,2250	) {2251		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();22522253		Self::add_token_index(collection_id, current_index, owner).unwrap();22542255		<ItemListIndex>::insert(collection_id, current_index);22562257		// Update balance2258		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2259			.checked_add(item.value)2260			.unwrap();2261		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2262	}22632264	#[allow(dead_code)]2265	fn init_refungible_token(2266		collection_id: CollectionId,2267		item: &ReFungibleItemType<T::CrossAccountId>,2268	) {2269		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();22702271		let value = item.owner.first().unwrap().fraction;2272		let owner = item.owner.first().unwrap().owner.clone();22732274		Self::add_token_index(collection_id, current_index, &owner).unwrap();22752276		<ItemListIndex>::insert(collection_id, current_index);22772278		// Update balance2279		let new_balance = <Balance<T>>::get(collection_id, &owner.as_sub())2280			.checked_add(value)2281			.unwrap();2282		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2283	}22842285	fn add_token_index(2286		collection_id: CollectionId,2287		item_index: TokenId,2288		owner: &T::CrossAccountId,2289	) -> DispatchResult {2290		// add to account limit2291		if <AccountItemCount<T>>::contains_key(owner.as_sub()) {2292			// bound Owned tokens by a single address2293			let count = <AccountItemCount<T>>::get(owner.as_sub());2294			ensure!(2295				count < ChainLimit::get().account_token_ownership_limit,2296				Error::<T>::AddressOwnershipLimitExceeded2297			);22982299			<AccountItemCount<T>>::insert(2300				owner.as_sub(),2301				count.checked_add(1).ok_or(Error::<T>::NumOverflow)?,2302			);2303		} else {2304			<AccountItemCount<T>>::insert(owner.as_sub(), 1);2305		}23062307		let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2308		if list_exists {2309			let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2310			let item_contains = list.contains(&item_index.clone());23112312			if !item_contains {2313				list.push(item_index);2314			}23152316			<AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2317		} else {2318			let itm = vec![item_index];2319			<AddressTokens<T>>::insert(collection_id, owner.as_sub(), itm);2320		}23212322		Ok(())2323	}23242325	fn remove_token_index(2326		collection_id: CollectionId,2327		item_index: TokenId,2328		owner: &T::CrossAccountId,2329	) -> DispatchResult {2330		// update counter2331		<AccountItemCount<T>>::insert(2332			owner.as_sub(),2333			<AccountItemCount<T>>::get(owner.as_sub())2334				.checked_sub(1)2335				.ok_or(Error::<T>::NumOverflow)?,2336		);23372338		let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2339		if list_exists {2340			let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2341			let item_contains = list.contains(&item_index.clone());23422343			if item_contains {2344				list.retain(|&item| item != item_index);2345				<AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2346			}2347		}23482349		Ok(())2350	}23512352	fn move_token_index(2353		collection_id: CollectionId,2354		item_index: TokenId,2355		old_owner: &T::CrossAccountId,2356		new_owner: &T::CrossAccountId,2357	) -> DispatchResult {2358		Self::remove_token_index(collection_id, item_index, old_owner)?;2359		Self::add_token_index(collection_id, item_index, new_owner)?;23602361		Ok(())2362	}2363}23642365sp_api::decl_runtime_apis! {2366	pub trait NftApi {2367		/// Used for ethereum integration2368		fn eth_contract_code(account: H160) -> Option<Vec<u8>>;2369	}2370}
after · pallets/nft/src/lib.rs
1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]7#![cfg_attr(not(feature = "std"), no_std)]8#![allow(9	clippy::too_many_arguments,10	clippy::unnecessary_mut_passed,11	clippy::unused_unit12)]1314extern crate alloc;1516pub use serde::{Serialize, Deserialize};1718pub use frame_support::{19	construct_runtime, decl_event, decl_module, decl_storage, decl_error,20	dispatch::DispatchResult,21	ensure, fail, parameter_types,22	traits::{23		Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,24		Randomness, IsSubType, WithdrawReasons,25	},26	weights::{27		constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},28		DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,29		WeightToFeePolynomial, DispatchClass,30	},31	StorageValue, transactional,32};3334use frame_system::{self as system, ensure_signed, ensure_root};35use sp_core::H160;36use sp_std::vec;37use sp_runtime::{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	AccessMode, ChainLimits, Collection, CreateItemData, CollectionLimits, CollectionId,42	CollectionMode, TokenId, SchemaVersion, SponsorshipState, Ownership, NftItemType,43	FungibleItemType, ReFungibleItemType,44};4546#[cfg(test)]47mod mock;4849#[cfg(test)]50mod tests;5152mod default_weights;53mod eth;54mod sponsorship;55pub use sponsorship::NftSponsorshipHandler;56pub use eth::sponsoring::NftEthSponsorshipHandler;5758pub use eth::NftErcSupport;59pub use eth::account::*;60use eth::erc::{ERC20Events, ERC721Events};6162#[cfg(feature = "runtime-benchmarks")]63mod benchmarking;6465pub trait WeightInfo {66	fn create_collection() -> Weight;67	fn destroy_collection() -> Weight;68	fn add_to_white_list() -> Weight;69	fn remove_from_white_list() -> Weight;70	fn set_public_access_mode() -> Weight;71	fn set_mint_permission() -> Weight;72	fn change_collection_owner() -> Weight;73	fn add_collection_admin() -> Weight;74	fn remove_collection_admin() -> Weight;75	fn set_collection_sponsor() -> Weight;76	fn confirm_sponsorship() -> Weight;77	fn remove_collection_sponsor() -> Weight;78	fn create_item(s: usize) -> Weight;79	fn burn_item() -> Weight;80	fn transfer() -> Weight;81	fn approve() -> Weight;82	fn transfer_from() -> Weight;83	fn set_offchain_schema() -> Weight;84	fn set_const_on_chain_schema() -> Weight;85	fn set_variable_on_chain_schema() -> Weight;86	fn set_variable_meta_data() -> Weight;87	fn enable_contract_sponsoring() -> Weight;88	fn set_schema_version() -> Weight;89	fn set_chain_limits() -> Weight;90	fn set_contract_sponsoring_rate_limit() -> Weight;91	fn set_variable_meta_data_sponsoring_rate_limit() -> Weight;92	fn toggle_contract_white_list() -> Weight;93	fn add_to_contract_white_list() -> Weight;94	fn remove_from_contract_white_list() -> Weight;95	fn set_collection_limits() -> Weight;96}9798decl_error! {99	/// Error for non-fungible-token module.100	pub enum Error for Module<T: Config> {101		/// Total collections bound exceeded.102		TotalCollectionsLimitExceeded,103		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.104		CollectionDecimalPointLimitExceeded,105		/// Collection name can not be longer than 63 char.106		CollectionNameLimitExceeded,107		/// Collection description can not be longer than 255 char.108		CollectionDescriptionLimitExceeded,109		/// Token prefix can not be longer than 15 char.110		CollectionTokenPrefixLimitExceeded,111		/// This collection does not exist.112		CollectionNotFound,113		/// Item not exists.114		TokenNotFound,115		/// Admin not found116		AdminNotFound,117		/// Arithmetic calculation overflow.118		NumOverflow,119		/// Account already has admin role.120		AlreadyAdmin,121		/// You do not own this collection.122		NoPermission,123		/// This address is not set as sponsor, use setCollectionSponsor first.124		ConfirmUnsetSponsorFail,125		/// Collection is not in mint mode.126		PublicMintingNotAllowed,127		/// Sender parameter and item owner must be equal.128		MustBeTokenOwner,129		/// Item balance not enough.130		TokenValueTooLow,131		/// Size of item is too large.132		NftSizeLimitExceeded,133		/// No approve found134		ApproveNotFound,135		/// Requested value more than approved.136		TokenValueNotEnough,137		/// Only approved addresses can call this method.138		ApproveRequired,139		/// Address is not in white list.140		AddresNotInWhiteList,141		/// Number of collection admins bound exceeded.142		CollectionAdminsLimitExceeded,143		/// Owned tokens by a single address bound exceeded.144		AddressOwnershipLimitExceeded,145		/// Length of items properties must be greater than 0.146		EmptyArgument,147		/// const_data exceeded data limit.148		TokenConstDataLimitExceeded,149		/// variable_data exceeded data limit.150		TokenVariableDataLimitExceeded,151		/// Not NFT item data used to mint in NFT collection.152		NotNftDataUsedToMintNftCollectionToken,153		/// Not Fungible item data used to mint in Fungible collection.154		NotFungibleDataUsedToMintFungibleCollectionToken,155		/// Not Re Fungible item data used to mint in Re Fungible collection.156		NotReFungibleDataUsedToMintReFungibleCollectionToken,157		/// Unexpected collection type.158		UnexpectedCollectionType,159		/// Can't store metadata in fungible tokens.160		CantStoreMetadataInFungibleTokens,161		/// Collection token limit exceeded162		CollectionTokenLimitExceeded,163		/// Account token limit exceeded per collection164		AccountTokenLimitExceeded,165		/// Collection limit bounds per collection exceeded166		CollectionLimitBoundsExceeded,167		/// Tried to enable permissions which are only permitted to be disabled168		OwnerPermissionsCantBeReverted,169		/// Schema data size limit bound exceeded170		SchemaDataLimitExceeded,171		/// Maximum refungibility exceeded172		WrongRefungiblePieces,173		/// createRefungible should be called with one owner174		BadCreateRefungibleCall,175		/// Gas limit exceeded176		OutOfGas,177		/// Collection settings not allowing items transferring178		TransferNotAllowed,179	}180}181182#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]183pub struct CollectionHandle<T: Config> {184	pub id: CollectionId,185	collection: Collection<T>,186	recorder: pallet_evm_coder_substrate::SubstrateRecorder<T>,187}188impl<T: Config> CollectionHandle<T> {189	pub fn get_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {190		<CollectionById<T>>::get(id).map(|collection| Self {191			id,192			collection,193			recorder: pallet_evm_coder_substrate::SubstrateRecorder::new(194				eth::collection_id_to_address(id),195				gas_limit,196			),197		})198	}199	pub fn get(id: CollectionId) -> Option<Self> {200		Self::get_with_gas_limit(id, u64::MAX)201	}202	pub fn log(&self, log: impl evm_coder::ToLog) -> DispatchResult {203		self.recorder.log_sub(log)204	}205	#[allow(dead_code)]206	fn consume_gas(&self, gas: u64) -> DispatchResult {207		self.recorder.consume_gas_sub(gas)208	}209	fn consume_sload(&self) -> DispatchResult {210		self.recorder.consume_sload_sub()211	}212	fn consume_sstore(&self) -> DispatchResult {213		self.recorder.consume_sstore_sub()214	}215	pub fn submit_logs(self) -> DispatchResult {216		self.recorder.submit_logs()217	}218	pub fn save(self) -> DispatchResult {219		self.recorder.submit_logs()?;220		<CollectionById<T>>::insert(self.id, self.collection);221		Ok(())222	}223}224impl<T: Config> Deref for CollectionHandle<T> {225	type Target = Collection<T>;226227	fn deref(&self) -> &Self::Target {228		&self.collection229	}230}231232impl<T: Config> DerefMut for CollectionHandle<T> {233	fn deref_mut(&mut self) -> &mut Self::Target {234		&mut self.collection235	}236}237238pub trait Config: system::Config + pallet_evm_coder_substrate::Config + Sized {239	type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;240241	/// Weight information for extrinsics in this pallet.242	type WeightInfo: WeightInfo;243244	type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;245	type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;246247	type CrossAccountId: CrossAccountId<Self::AccountId>;248	type Currency: Currency<Self::AccountId>;249	type CollectionCreationPrice: Get<250		<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,251	>;252	type TreasuryAccountId: Get<Self::AccountId>;253}254255// # Used definitions256//257// ## User control levels258//259// chain-controlled - key is uncontrolled by user260//                    i.e autoincrementing index261//                    can use non-cryptographic hash262// real - key is controlled by user263//        but it is hard to generate enough colliding values, i.e owner of signed txs264//        can use non-cryptographic hash265// controlled - key is completly controlled by users266//              i.e maps with mutable keys267//              should use cryptographic hash268//269// ## User control level downgrade reasons270//271// ?1 - chain-controlled -> controlled272//      collections/tokens can be destroyed, resulting in massive holes273// ?2 - chain-controlled -> controlled274//      same as ?1, but can be only added, resulting in easier exploitation275// ?3 - real -> controlled276//      no confirmation required, so addresses can be easily generated277decl_storage! {278	trait Store for Module<T: Config> as Nft {279280		//#region Private members281		/// Id of next collection282		CreatedCollectionCount: u32;283		/// Used for migrations284		ChainVersion: u64;285		/// Id of last collection token286		/// Collection id (controlled?1)287		ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;288		//#endregion289290		//#region Chain limits struct291		pub ChainLimit get(fn chain_limit) config(): ChainLimits;292		//#endregion293294		//#region Bound counters295		/// Amount of collections destroyed, used for total amount tracking with296		/// CreatedCollectionCount297		DestroyedCollectionCount: u32;298		/// Total amount of account owned tokens (NFTs + RFTs + unique fungibles)299		/// Account id (real)300		pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;301		//#endregion302303		//#region Basic collections304		/// Collection info305		/// Collection id (controlled?1)306		pub CollectionById get(fn collection_id) config(): map hasher(blake2_128_concat) CollectionId => Option<Collection<T>> = None;307		/// List of collection admins308		/// Collection id (controlled?2)309		pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec<T::CrossAccountId>;310		/// Whitelisted collection users311		/// Collection id (controlled?2), user id (controlled?3)312		pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool;313		//#endregion314315		/// How many of collection items user have316		/// Collection id (controlled?2), account id (real)317		pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128;318319		/// Amount of items which spender can transfer out of owners account (via transferFrom)320		/// Collection id (controlled?2), (token id (controlled ?2) + owner account id (real) + spender account id (controlled?3))321		/// TODO: Off chain worker should remove from this map when token gets removed322		pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128;323324		//#region Item collections325		/// Collection id (controlled?2), token id (controlled?1)326		pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<NftItemType<T::CrossAccountId>>;327		/// Collection id (controlled?2), owner (controlled?2)328		pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType;329		/// Collection id (controlled?2), token id (controlled?1)330		pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<ReFungibleItemType<T::CrossAccountId>>;331		//#endregion332333		//#region Index list334		/// Collection id (controlled?2), tokens owner (controlled?2)335		pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;336		//#endregion337338		//#region Tokens transfer rate limit baskets339		/// (Collection id (controlled?2), who created (real))340		/// TODO: Off chain worker should remove from this map when collection gets removed341		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;342		/// Collection id (controlled?2), token id (controlled?2)343		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;344		/// Collection id (controlled?2), owning user (real)345		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;346		/// Collection id (controlled?2), token id (controlled?2)347		pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;348		//#endregion349350		/// Variable metadata sponsoring351		/// Collection id (controlled?2), token id (controlled?2)352		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;353	}354	add_extra_genesis {355		build(|config: &GenesisConfig<T>| {356			// Modification of storage357			for (_num, _c) in &config.collection_id {358				<Module<T>>::init_collection(_c);359			}360361			for (_num, _c, _i) in &config.nft_item_id {362				<Module<T>>::init_nft_token(*_c, _i);363			}364365			for (collection_id, account_id, fungible_item) in &config.fungible_item_id {366				<Module<T>>::init_fungible_token(*collection_id, &T::CrossAccountId::from_sub(account_id.clone()), fungible_item);367			}368369			for (_num, _c, _i) in &config.refungible_item_id {370				<Module<T>>::init_refungible_token(*_c, _i);371			}372		})373	}374}375376decl_event!(377	pub enum Event<T>378	where379		AccountId = <T as frame_system::Config>::AccountId,380		CrossAccountId = <T as Config>::CrossAccountId,381	{382		/// New collection was created383		///384		/// # Arguments385		///386		/// * collection_id: Globally unique identifier of newly created collection.387		///388		/// * mode: [CollectionMode] converted into u8.389		///390		/// * account_id: Collection owner.391		CollectionCreated(CollectionId, u8, AccountId),392393		/// New item was created.394		///395		/// # Arguments396		///397		/// * collection_id: Id of the collection where item was created.398		///399		/// * item_id: Id of an item. Unique within the collection.400		///401		/// * recipient: Owner of newly created item402		ItemCreated(CollectionId, TokenId, CrossAccountId),403404		/// Collection item was burned.405		///406		/// # Arguments407		///408		/// collection_id.409		///410		/// item_id: Identifier of burned NFT.411		ItemDestroyed(CollectionId, TokenId),412413		/// Item was transferred414		///415		/// * collection_id: Id of collection to which item is belong416		///417		/// * item_id: Id of an item418		///419		/// * sender: Original owner of item420		///421		/// * recipient: New owner of item422		///423		/// * amount: Always 1 for NFT424		Transfer(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),425426		/// * collection_id427		///428		/// * item_id429		///430		/// * sender431		///432		/// * spender433		///434		/// * amount435		Approved(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),436	}437);438439decl_module! {440	pub struct Module<T: Config> for enum Call441	where442		origin: T::Origin443	{444		fn deposit_event() = default;445		type Error = Error<T>;446447		fn on_initialize(_now: T::BlockNumber) -> Weight {448			0449		}450451		/// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.452		///453		/// # Permissions454		///455		/// * Anyone.456		///457		/// # Arguments458		///459		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.460		///461		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.462		///463		/// * token_prefix: UTF-8 string with token prefix.464		///465		/// * mode: [CollectionMode] collection type and type dependent data.466		// returns collection ID467		#[weight = <T as Config>::WeightInfo::create_collection()]468		#[transactional]469		pub fn create_collection(origin,470								 collection_name: Vec<u16>,471								 collection_description: Vec<u16>,472								 token_prefix: Vec<u8>,473								 mode: CollectionMode) -> DispatchResult {474475			// Anyone can create a collection476			let who = ensure_signed(origin)?;477478			// Take a (non-refundable) deposit of collection creation479			let mut imbalance = <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();480			imbalance.subsume(<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(481				&T::TreasuryAccountId::get(),482				T::CollectionCreationPrice::get(),483			));484			<T as Config>::Currency::settle(485				&who,486				imbalance,487				WithdrawReasons::TRANSFER,488				ExistenceRequirement::KeepAlive,489			).map_err(|_| Error::<T>::NoPermission)?;490491			let decimal_points = match mode {492				CollectionMode::Fungible(points) => points,493				_ => 0494			};495496			let chain_limit = ChainLimit::get();497498			let created_count = CreatedCollectionCount::get();499			let destroyed_count = DestroyedCollectionCount::get();500501			// bound Total number of collections502			ensure!(created_count - destroyed_count < chain_limit.collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);503504			// check params505			ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);506			ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);507			ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);508			ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);509510			// Generate next collection ID511			let next_id = created_count512				.checked_add(1)513				.ok_or(Error::<T>::NumOverflow)?;514515			CreatedCollectionCount::put(next_id);516517			let limits = CollectionLimits {518				sponsored_data_size: chain_limit.custom_data_limit,519				..Default::default()520			};521522			// Create new collection523			let new_collection = Collection {524				owner: who.clone(),525				name: collection_name,526				mode: mode.clone(),527				mint_mode: false,528				access: AccessMode::Normal,529				description: collection_description,530				decimal_points,531				token_prefix,532				offchain_schema: Vec::new(),533				schema_version: SchemaVersion::ImageURL,534				sponsorship: SponsorshipState::Disabled,535				variable_on_chain_schema: Vec::new(),536				const_on_chain_schema: Vec::new(),537				limits,538				transfers_enabled: true,539			};540541			// Add new collection to map542			<CollectionById<T>>::insert(next_id, new_collection);543544			// call event545			Self::deposit_event(RawEvent::CollectionCreated(next_id, mode.id(), who));546547			Ok(())548		}549550		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.551		///552		/// # Permissions553		///554		/// * Collection Owner.555		///556		/// # Arguments557		///558		/// * collection_id: collection to destroy.559		#[weight = <T as Config>::WeightInfo::destroy_collection()]560		#[transactional]561		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {562563			let sender = ensure_signed(origin)?;564			let collection = Self::get_collection(collection_id)?;565			Self::check_owner_permissions(&collection, &sender)?;566			if !collection.limits.owner_can_destroy {567				fail!(Error::<T>::NoPermission);568			}569570			<AddressTokens<T>>::remove_prefix(collection_id, None);571			<Allowances<T>>::remove_prefix(collection_id, None);572			<Balance<T>>::remove_prefix(collection_id, None);573			<ItemListIndex>::remove(collection_id);574			<AdminList<T>>::remove(collection_id);575			<CollectionById<T>>::remove(collection_id);576			<WhiteList<T>>::remove_prefix(collection_id, None);577578			<NftItemList<T>>::remove_prefix(collection_id, None);579			<FungibleItemList<T>>::remove_prefix(collection_id, None);580			<ReFungibleItemList<T>>::remove_prefix(collection_id, None);581582			<NftTransferBasket<T>>::remove_prefix(collection_id, None);583			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);584			<ReFungibleTransferBasket<T>>::remove_prefix(collection_id, None);585586			<VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);587588			DestroyedCollectionCount::put(DestroyedCollectionCount::get()589				.checked_add(1)590				.ok_or(Error::<T>::NumOverflow)?);591592			Ok(())593		}594595		/// Add an address to white list.596		///597		/// # Permissions598		///599		/// * Collection Owner600		/// * Collection Admin601		///602		/// # Arguments603		///604		/// * collection_id.605		///606		/// * address.607		#[weight = <T as Config>::WeightInfo::add_to_white_list()]608		#[transactional]609		pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{610611			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);612			let collection = Self::get_collection(collection_id)?;613614			Self::toggle_white_list_internal(615				&sender,616				&collection,617				&address,618				true,619			)?;620621			Ok(())622		}623624		/// Remove an address from white list.625		///626		/// # Permissions627		///628		/// * Collection Owner629		/// * Collection Admin630		///631		/// # Arguments632		///633		/// * collection_id.634		///635		/// * address.636		#[weight = <T as Config>::WeightInfo::remove_from_white_list()]637		#[transactional]638		pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{639640			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);641			let collection = Self::get_collection(collection_id)?;642643			Self::toggle_white_list_internal(644				&sender,645				&collection,646				&address,647				false,648			)?;649650			Ok(())651		}652653		/// Toggle between normal and white list access for the methods with access for `Anyone`.654		///655		/// # Permissions656		///657		/// * Collection Owner.658		///659		/// # Arguments660		///661		/// * collection_id.662		///663		/// * mode: [AccessMode]664		#[weight = <T as Config>::WeightInfo::set_public_access_mode()]665		#[transactional]666		pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult667		{668			let sender = ensure_signed(origin)?;669670			let mut target_collection = Self::get_collection(collection_id)?;671			Self::check_owner_permissions(&target_collection, &sender)?;672			target_collection.access = mode;673			target_collection.save()674		}675676		/// Allows Anyone to create tokens if:677		/// * White List is enabled, and678		/// * Address is added to white list, and679		/// * This method was called with True parameter680		///681		/// # Permissions682		/// * Collection Owner683		///684		/// # Arguments685		///686		/// * collection_id.687		///688		/// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.689		#[weight = <T as Config>::WeightInfo::set_mint_permission()]690		#[transactional]691		pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult692		{693			let sender = ensure_signed(origin)?;694695			let mut target_collection = Self::get_collection(collection_id)?;696			Self::check_owner_permissions(&target_collection, &sender)?;697			target_collection.mint_mode = mint_permission;698			target_collection.save()699		}700701		/// Change the owner of the collection.702		///703		/// # Permissions704		///705		/// * Collection Owner.706		///707		/// # Arguments708		///709		/// * collection_id.710		///711		/// * new_owner.712		#[weight = <T as Config>::WeightInfo::change_collection_owner()]713		#[transactional]714		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {715716			let sender = ensure_signed(origin)?;717			let mut target_collection = Self::get_collection(collection_id)?;718			Self::check_owner_permissions(&target_collection, &sender)?;719			target_collection.owner = new_owner;720			target_collection.save()721		}722723		/// Adds an admin of the Collection.724		/// 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.725		///726		/// # Permissions727		///728		/// * Collection Owner.729		/// * Collection Admin.730		///731		/// # Arguments732		///733		/// * collection_id: ID of the Collection to add admin for.734		///735		/// * new_admin_id: Address of new admin to add.736		#[weight = <T as Config>::WeightInfo::add_collection_admin()]737		#[transactional]738		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {739			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);740			let collection = Self::get_collection(collection_id)?;741			Self::check_owner_or_admin_permissions(&collection, &sender)?;742			let mut admin_arr = <AdminList<T>>::get(collection_id);743744			match admin_arr.binary_search(&new_admin_id) {745				Ok(_) => {},746				Err(idx) => {747					let limits = ChainLimit::get();748					ensure!(admin_arr.len() < limits.collections_admins_limit as usize, Error::<T>::CollectionAdminsLimitExceeded);749					admin_arr.insert(idx, new_admin_id);750					<AdminList<T>>::insert(collection_id, admin_arr);751				}752			}753			Ok(())754		}755756		/// 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.757		///758		/// # Permissions759		///760		/// * Collection Owner.761		/// * Collection Admin.762		///763		/// # Arguments764		///765		/// * collection_id: ID of the Collection to remove admin for.766		///767		/// * account_id: Address of admin to remove.768		#[weight = <T as Config>::WeightInfo::remove_collection_admin()]769		#[transactional]770		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {771			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);772			let collection = Self::get_collection(collection_id)?;773			Self::check_owner_or_admin_permissions(&collection, &sender)?;774			let mut admin_arr = <AdminList<T>>::get(collection_id);775776			if let Ok(idx) = admin_arr.binary_search(&account_id) {777				admin_arr.remove(idx);778				<AdminList<T>>::insert(collection_id, admin_arr);779			}780			Ok(())781		}782783		/// # Permissions784		///785		/// * Collection Owner786		///787		/// # Arguments788		///789		/// * collection_id.790		///791		/// * new_sponsor.792		#[weight = <T as Config>::WeightInfo::set_collection_sponsor()]793		#[transactional]794		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {795			let sender = ensure_signed(origin)?;796			let mut target_collection = Self::get_collection(collection_id)?;797			Self::check_owner_permissions(&target_collection, &sender)?;798799			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);800			target_collection.save()801		}802803		/// # Permissions804		///805		/// * Sponsor.806		///807		/// # Arguments808		///809		/// * collection_id.810		#[weight = <T as Config>::WeightInfo::confirm_sponsorship()]811		#[transactional]812		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {813			let sender = ensure_signed(origin)?;814815			let mut target_collection = Self::get_collection(collection_id)?;816			ensure!(817				target_collection.sponsorship.pending_sponsor() == Some(&sender),818				Error::<T>::ConfirmUnsetSponsorFail819			);820821			target_collection.sponsorship = SponsorshipState::Confirmed(sender);822			target_collection.save()823		}824825		/// Switch back to pay-per-own-transaction model.826		///827		/// # Permissions828		///829		/// * Collection owner.830		///831		/// # Arguments832		///833		/// * collection_id.834		#[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]835		#[transactional]836		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {837			let sender = ensure_signed(origin)?;838839			let mut target_collection = Self::get_collection(collection_id)?;840			Self::check_owner_permissions(&target_collection, &sender)?;841842			target_collection.sponsorship = SponsorshipState::Disabled;843			target_collection.save()844		}845846		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.847		///848		/// # Permissions849		///850		/// * Collection Owner.851		/// * Collection Admin.852		/// * Anyone if853		///     * White List is enabled, and854		///     * Address is added to white list, and855		///     * MintPermission is enabled (see SetMintPermission method)856		///857		/// # Arguments858		///859		/// * collection_id: ID of the collection.860		///861		/// * owner: Address, initial owner of the NFT.862		///863		/// * data: Token data to store on chain.864		// #[weight =865		// (130_000_000 as Weight)866		// .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))867		// .saturating_add(RocksDbWeight::get().reads(10 as Weight))868		// .saturating_add(RocksDbWeight::get().writes(8 as Weight))]869870		#[weight = <T as Config>::WeightInfo::create_item(data.data_size())]871		#[transactional]872		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResult {873			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);874			let collection = Self::get_collection(collection_id)?;875876			Self::create_item_internal(&sender, &collection, &owner, data)?;877878			collection.submit_logs()879		}880881		/// This method creates multiple items in a collection created with CreateCollection method.882		///883		/// # Permissions884		///885		/// * Collection Owner.886		/// * Collection Admin.887		/// * Anyone if888		///     * White List is enabled, and889		///     * Address is added to white list, and890		///     * MintPermission is enabled (see SetMintPermission method)891		///892		/// # Arguments893		///894		/// * collection_id: ID of the collection.895		///896		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].897		///898		/// * owner: Address, initial owner of the NFT.899		#[weight = <T as Config>::WeightInfo::create_item(items_data.iter()900							   .map(|data| { data.data_size() })901							   .sum())]902		#[transactional]903		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResult {904905			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);906			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);907			let collection = Self::get_collection(collection_id)?;908909			Self::create_multiple_items_internal(&sender, &collection, &owner, items_data)?;910911			collection.submit_logs()912		}913914		// TODO! transaction weight915916		/// Set transfers_enabled value for particular collection917		///918		/// # Permissions919		///920		/// * Collection Owner.921		///922		/// # Arguments923		///924		/// * collection_id: ID of the collection.925		///926		/// * value: New flag value.927		#[weight = <T as Config>::WeightInfo::burn_item()]928		#[transactional]929		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {930931			let sender = ensure_signed(origin)?;932			let mut target_collection = Self::get_collection(collection_id)?;933934			Self::check_owner_permissions(&target_collection, &sender)?;935936			target_collection.transfers_enabled = value;937			target_collection.save()938		}939940		/// Destroys a concrete instance of NFT.941		///942		/// # Permissions943		///944		/// * Collection Owner.945		/// * Collection Admin.946		/// * Current NFT Owner.947		///948		/// # Arguments949		///950		/// * collection_id: ID of the collection.951		///952		/// * item_id: ID of NFT to burn.953		#[weight = <T as Config>::WeightInfo::burn_item()]954		#[transactional]955		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {956957			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);958			let target_collection = Self::get_collection(collection_id)?;959960			Self::burn_item_internal(&sender, &target_collection, item_id, value)?;961962			target_collection.submit_logs()963		}964965		/// Change ownership of the token.966		///967		/// # Permissions968		///969		/// * Collection Owner970		/// * Collection Admin971		/// * Current NFT owner972		///973		/// # Arguments974		///975		/// * recipient: Address of token recipient.976		///977		/// * collection_id.978		///979		/// * item_id: ID of the item980		///     * Non-Fungible Mode: Required.981		///     * Fungible Mode: Ignored.982		///     * Re-Fungible Mode: Required.983		///984		/// * value: Amount to transfer.985		///     * Non-Fungible Mode: Ignored986		///     * Fungible Mode: Must specify transferred amount987		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)988		#[weight = <T as Config>::WeightInfo::transfer()]989		#[transactional]990		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {991			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);992			let collection = Self::get_collection(collection_id)?;993994			Self::transfer_internal(&sender, &recipient, &collection, item_id, value)?;995996			collection.submit_logs()997		}998999		/// Set, change, or remove approved address to transfer the ownership of the NFT.1000		///1001		/// # Permissions1002		///1003		/// * Collection Owner1004		/// * Collection Admin1005		/// * Current NFT owner1006		///1007		/// # Arguments1008		///1009		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1010		///1011		/// * collection_id.1012		///1013		/// * item_id: ID of the item.1014		#[weight = <T as Config>::WeightInfo::approve()]1015		#[transactional]1016		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {1017			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1018			let collection = Self::get_collection(collection_id)?;10191020			Self::approve_internal(&sender, &spender, &collection, item_id, amount)?;10211022			collection.submit_logs()1023		}10241025		/// 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.1026		///1027		/// # Permissions1028		/// * Collection Owner1029		/// * Collection Admin1030		/// * Current NFT owner1031		/// * Address approved by current NFT owner1032		///1033		/// # Arguments1034		///1035		/// * from: Address that owns token.1036		///1037		/// * recipient: Address of token recipient.1038		///1039		/// * collection_id.1040		///1041		/// * item_id: ID of the item.1042		///1043		/// * value: Amount to transfer.1044		#[weight = <T as Config>::WeightInfo::transfer_from()]1045		#[transactional]1046		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {1047			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1048			let collection = Self::get_collection(collection_id)?;10491050			Self::transfer_from_internal(&sender, &from, &recipient, &collection, item_id, value)?;10511052			collection.submit_logs()1053		}1054		// #[weight = 0]1055		//     // let no_perm_mes = "You do not have permissions to modify this collection";1056		//     // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1057		//     // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1058		//     // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);10591060		//     // // on_nft_received  call10611062		//     // Self::transfer(origin, collection_id, item_id, new_owner)?;10631064		//     Ok(())1065		// }10661067		/// Set off-chain data schema.1068		///1069		/// # Permissions1070		///1071		/// * Collection Owner1072		/// * Collection Admin1073		///1074		/// # Arguments1075		///1076		/// * collection_id.1077		///1078		/// * schema: String representing the offchain data schema.1079		#[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1080		#[transactional]1081		pub fn set_variable_meta_data (1082			origin,1083			collection_id: CollectionId,1084			item_id: TokenId,1085			data: Vec<u8>1086		) -> DispatchResult {1087			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);10881089			let collection = Self::get_collection(collection_id)?;10901091			Self::set_variable_meta_data_internal(&sender, &collection, item_id, data)?;10921093			Ok(())1094		}10951096		/// Set schema standard1097		/// ImageURL1098		/// Unique1099		///1100		/// # Permissions1101		///1102		/// * Collection Owner1103		/// * Collection Admin1104		///1105		/// # Arguments1106		///1107		/// * collection_id.1108		///1109		/// * schema: SchemaVersion: enum1110		#[weight = <T as Config>::WeightInfo::set_schema_version()]1111		#[transactional]1112		pub fn set_schema_version(1113			origin,1114			collection_id: CollectionId,1115			version: SchemaVersion1116		) -> DispatchResult {1117			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1118			let mut target_collection = Self::get_collection(collection_id)?;1119			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;1120			target_collection.schema_version = version;1121			target_collection.save()1122		}11231124		/// Set off-chain data schema.1125		///1126		/// # Permissions1127		///1128		/// * Collection Owner1129		/// * Collection Admin1130		///1131		/// # Arguments1132		///1133		/// * collection_id.1134		///1135		/// * schema: String representing the offchain data schema.1136		#[weight = <T as Config>::WeightInfo::set_offchain_schema()]1137		#[transactional]1138		pub fn set_offchain_schema(1139			origin,1140			collection_id: CollectionId,1141			schema: Vec<u8>1142		) -> DispatchResult {1143			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1144			let mut target_collection = Self::get_collection(collection_id)?;1145			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11461147			// check schema limit1148			ensure!(schema.len() as u32 <= ChainLimit::get().offchain_schema_limit, "");11491150			target_collection.offchain_schema = schema;1151			target_collection.save()1152		}11531154		/// Set const on-chain data schema.1155		///1156		/// # Permissions1157		///1158		/// * Collection Owner1159		/// * Collection Admin1160		///1161		/// # Arguments1162		///1163		/// * collection_id.1164		///1165		/// * schema: String representing the const on-chain data schema.1166		#[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1167		#[transactional]1168		pub fn set_const_on_chain_schema (1169			origin,1170			collection_id: CollectionId,1171			schema: Vec<u8>1172		) -> DispatchResult {1173			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1174			let mut target_collection = Self::get_collection(collection_id)?;1175			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11761177			// check schema limit1178			ensure!(schema.len() as u32 <= ChainLimit::get().const_on_chain_schema_limit, "");11791180			target_collection.const_on_chain_schema = schema;1181			target_collection.save()1182		}11831184		/// Set variable on-chain data schema.1185		///1186		/// # Permissions1187		///1188		/// * Collection Owner1189		/// * Collection Admin1190		///1191		/// # Arguments1192		///1193		/// * collection_id.1194		///1195		/// * schema: String representing the variable on-chain data schema.1196		#[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1197		#[transactional]1198		pub fn set_variable_on_chain_schema (1199			origin,1200			collection_id: CollectionId,1201			schema: Vec<u8>1202		) -> DispatchResult {1203			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1204			let mut target_collection = Self::get_collection(collection_id)?;1205			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;12061207			// check schema limit1208			ensure!(schema.len() as u32 <= ChainLimit::get().variable_on_chain_schema_limit, "");12091210			target_collection.variable_on_chain_schema = schema;1211			target_collection.save()1212		}12131214		// Sudo permissions function1215		#[weight = <T as Config>::WeightInfo::set_chain_limits()]1216		#[transactional]1217		pub fn set_chain_limits(1218			origin,1219			limits: ChainLimits1220		) -> DispatchResult {12211222			#[cfg(not(feature = "runtime-benchmarks"))]1223			ensure_root(origin)?;12241225			<ChainLimit>::put(limits);1226			Ok(())1227		}12281229		#[weight = <T as Config>::WeightInfo::set_collection_limits()]1230		#[transactional]1231		pub fn set_collection_limits(1232			origin,1233			collection_id: u32,1234			new_limits: CollectionLimits<T::BlockNumber>,1235		) -> DispatchResult {1236			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1237			let mut target_collection = Self::get_collection(collection_id)?;1238			Self::check_owner_permissions(&target_collection, sender.as_sub())?;1239			let old_limits = &target_collection.limits;1240			let chain_limits = ChainLimit::get();12411242			// collection bounds1243			ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1244				new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP &&1245				new_limits.sponsored_data_size <= chain_limits.custom_data_limit,1246				Error::<T>::CollectionLimitBoundsExceeded);12471248			// token_limit   check  prev1249			ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1250			ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);12511252			ensure!(1253				(old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1254				(old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1255				Error::<T>::OwnerPermissionsCantBeReverted,1256			);12571258			target_collection.limits = new_limits;12591260			target_collection.save()1261		}1262	}1263}12641265impl<T: Config> Module<T> {1266	pub fn create_item_internal(1267		sender: &T::CrossAccountId,1268		collection: &CollectionHandle<T>,1269		owner: &T::CrossAccountId,1270		data: CreateItemData,1271	) -> DispatchResult {1272		Self::can_create_items_in_collection(collection, sender, owner, 1)?;1273		Self::validate_create_item_args(collection, &data)?;1274		Self::create_item_no_validation(collection, owner, data)?;12751276		Ok(())1277	}12781279	pub fn transfer_internal(1280		sender: &T::CrossAccountId,1281		recipient: &T::CrossAccountId,1282		target_collection: &CollectionHandle<T>,1283		item_id: TokenId,1284		value: u128,1285	) -> DispatchResult {1286		// Limits check1287		Self::is_correct_transfer(target_collection, recipient)?;12881289		// Transfer permissions check1290		ensure!(1291			Self::is_item_owner(sender, target_collection, item_id)?1292				|| Self::is_owner_or_admin_permissions(target_collection, sender)?,1293			Error::<T>::NoPermission1294		);12951296		if target_collection.access == AccessMode::WhiteList {1297			Self::check_white_list(target_collection, sender)?;1298			Self::check_white_list(target_collection, recipient)?;1299		}13001301		match target_collection.mode {1302			CollectionMode::NFT => Self::transfer_nft(1303				target_collection,1304				item_id,1305				sender.clone(),1306				recipient.clone(),1307			)?,1308			CollectionMode::Fungible(_) => {1309				Self::transfer_fungible(target_collection, value, sender, recipient)?1310			}1311			CollectionMode::ReFungible => Self::transfer_refungible(1312				target_collection,1313				item_id,1314				value,1315				sender.clone(),1316				recipient.clone(),1317			)?,1318			_ => (),1319		};13201321		Self::deposit_event(RawEvent::Transfer(1322			target_collection.id,1323			item_id,1324			sender.clone(),1325			recipient.clone(),1326			value,1327		));13281329		Ok(())1330	}13311332	pub fn approve_internal(1333		sender: &T::CrossAccountId,1334		spender: &T::CrossAccountId,1335		collection: &CollectionHandle<T>,1336		item_id: TokenId,1337		amount: u128,1338	) -> DispatchResult {1339		Self::token_exists(collection, item_id)?;13401341		// Transfer permissions check1342		let bypasses_limits = collection.limits.owner_can_transfer1343			&& Self::is_owner_or_admin_permissions(collection, sender)?;13441345		let allowance_limit = if bypasses_limits {1346			None1347		} else if let Some(amount) = Self::owned_amount(sender, collection, item_id)? {1348			Some(amount)1349		} else {1350			fail!(Error::<T>::NoPermission);1351		};13521353		if collection.access == AccessMode::WhiteList {1354			Self::check_white_list(collection, sender)?;1355			Self::check_white_list(collection, spender)?;1356		}13571358		collection.consume_sload()?;1359		let allowance: u128 = amount1360			.checked_add(<Allowances<T>>::get(1361				collection.id,1362				(item_id, sender.as_sub(), spender.as_sub()),1363			))1364			.ok_or(Error::<T>::NumOverflow)?;1365		if let Some(limit) = allowance_limit {1366			ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1367		}1368		collection.consume_sstore()?;1369		<Allowances<T>>::insert(1370			collection.id,1371			(item_id, sender.as_sub(), spender.as_sub()),1372			allowance,1373		);13741375		if matches!(collection.mode, CollectionMode::NFT) {1376			// TODO: NFT: only one owner may exist for token in ERC7211377			collection.log(ERC721Events::Approval {1378				owner: *sender.as_eth(),1379				approved: *spender.as_eth(),1380				token_id: item_id.into(),1381			})?;1382		}13831384		if matches!(collection.mode, CollectionMode::Fungible(_)) {1385			// TODO: NFT: only one owner may exist for token in ERC201386			collection.log(ERC20Events::Approval {1387				owner: *sender.as_eth(),1388				spender: *spender.as_eth(),1389				value: allowance.into(),1390			})?;1391		}13921393		Self::deposit_event(RawEvent::Approved(1394			collection.id,1395			item_id,1396			sender.clone(),1397			spender.clone(),1398			allowance,1399		));1400		Ok(())1401	}14021403	pub fn transfer_from_internal(1404		sender: &T::CrossAccountId,1405		from: &T::CrossAccountId,1406		recipient: &T::CrossAccountId,1407		collection: &CollectionHandle<T>,1408		item_id: TokenId,1409		amount: u128,1410	) -> DispatchResult {1411		// Check approval1412		collection.consume_sload()?;1413		let approval: u128 =1414			<Allowances<T>>::get(collection.id, (item_id, from.as_sub(), sender.as_sub()));14151416		// Limits check1417		Self::is_correct_transfer(collection, recipient)?;14181419		// Transfer permissions check1420		ensure!(1421			approval >= amount1422				|| (collection.limits.owner_can_transfer1423					&& Self::is_owner_or_admin_permissions(collection, sender)?),1424			Error::<T>::NoPermission1425		);14261427		if collection.access == AccessMode::WhiteList {1428			Self::check_white_list(collection, sender)?;1429			Self::check_white_list(collection, recipient)?;1430		}14311432		// Reduce approval by transferred amount or remove if remaining approval drops to 01433		let allowance = approval.saturating_sub(amount);1434		collection.consume_sstore()?;1435		if allowance > 0 {1436			<Allowances<T>>::insert(1437				collection.id,1438				(item_id, from.as_sub(), sender.as_sub()),1439				allowance,1440			);1441		} else {1442			<Allowances<T>>::remove(collection.id, (item_id, from.as_sub(), sender.as_sub()));1443		}14441445		match collection.mode {1446			CollectionMode::NFT => {1447				Self::transfer_nft(collection, item_id, from.clone(), recipient.clone())?1448			}1449			CollectionMode::Fungible(_) => {1450				Self::transfer_fungible(collection, amount, from, recipient)?1451			}1452			CollectionMode::ReFungible => Self::transfer_refungible(1453				collection,1454				item_id,1455				amount,1456				from.clone(),1457				recipient.clone(),1458			)?,1459			_ => (),1460		};14611462		if matches!(collection.mode, CollectionMode::Fungible(_)) {1463			collection.log(ERC20Events::Approval {1464				owner: *from.as_eth(),1465				spender: *sender.as_eth(),1466				value: allowance.into(),1467			})?;1468		}14691470		Ok(())1471	}14721473	pub fn set_variable_meta_data_internal(1474		sender: &T::CrossAccountId,1475		collection: &CollectionHandle<T>,1476		item_id: TokenId,1477		data: Vec<u8>,1478	) -> DispatchResult {1479		Self::token_exists(collection, item_id)?;14801481		ensure!(1482			ChainLimit::get().custom_data_limit >= data.len() as u32,1483			Error::<T>::TokenVariableDataLimitExceeded1484		);14851486		// Modify permissions check1487		ensure!(1488			Self::is_item_owner(sender, collection, item_id)?1489				|| Self::is_owner_or_admin_permissions(collection, sender)?,1490			Error::<T>::NoPermission1491		);14921493		match collection.mode {1494			CollectionMode::NFT => Self::set_nft_variable_data(collection, item_id, data)?,1495			CollectionMode::ReFungible => {1496				Self::set_re_fungible_variable_data(collection, item_id, data)?1497			}1498			CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1499			_ => fail!(Error::<T>::UnexpectedCollectionType),1500		};15011502		Ok(())1503	}15041505	pub fn create_multiple_items_internal(1506		sender: &T::CrossAccountId,1507		collection: &CollectionHandle<T>,1508		owner: &T::CrossAccountId,1509		items_data: Vec<CreateItemData>,1510	) -> DispatchResult {1511		Self::can_create_items_in_collection(collection, sender, owner, items_data.len() as u32)?;15121513		for data in &items_data {1514			Self::validate_create_item_args(collection, data)?;1515		}1516		for data in &items_data {1517			Self::create_item_no_validation(collection, owner, data.clone())?;1518		}15191520		Ok(())1521	}15221523	pub fn burn_item_internal(1524		sender: &T::CrossAccountId,1525		collection: &CollectionHandle<T>,1526		item_id: TokenId,1527		value: u128,1528	) -> DispatchResult {1529		ensure!(1530			Self::is_item_owner(sender, collection, item_id)?1531				|| (collection.limits.owner_can_transfer1532					&& Self::is_owner_or_admin_permissions(collection, sender)?),1533			Error::<T>::NoPermission1534		);15351536		if collection.access == AccessMode::WhiteList {1537			Self::check_white_list(collection, sender)?;1538		}15391540		match collection.mode {1541			CollectionMode::NFT => Self::burn_nft_item(collection, item_id)?,1542			CollectionMode::Fungible(_) => Self::burn_fungible_item(sender, collection, value)?,1543			CollectionMode::ReFungible => Self::burn_refungible_item(collection, item_id, sender)?,1544			_ => (),1545		};15461547		Ok(())1548	}15491550	pub fn toggle_white_list_internal(1551		sender: &T::CrossAccountId,1552		collection: &CollectionHandle<T>,1553		address: &T::CrossAccountId,1554		whitelisted: bool,1555	) -> DispatchResult {1556		Self::check_owner_or_admin_permissions(collection, sender)?;15571558		if whitelisted {1559			<WhiteList<T>>::insert(collection.id, address.as_sub(), true);1560		} else {1561			<WhiteList<T>>::remove(collection.id, address.as_sub());1562		}15631564		Ok(())1565	}15661567	fn is_correct_transfer(1568		collection: &CollectionHandle<T>,1569		recipient: &T::CrossAccountId,1570	) -> DispatchResult {1571		let collection_id = collection.id;15721573		// check token limit and account token limit1574		collection.consume_sload()?;1575		let account_items: u32 =1576			<AddressTokens<T>>::get(collection_id, recipient.as_sub()).len() as u32;1577		ensure!(1578			collection.limits.account_token_ownership_limit > account_items,1579			Error::<T>::AccountTokenLimitExceeded1580		);15811582		// preliminary transfer check1583		ensure!(collection.transfers_enabled, Error::<T>::TransferNotAllowed);15841585		Ok(())1586	}15871588	fn can_create_items_in_collection(1589		collection: &CollectionHandle<T>,1590		sender: &T::CrossAccountId,1591		owner: &T::CrossAccountId,1592		amount: u32,1593	) -> DispatchResult {1594		let collection_id = collection.id;15951596		// check token limit and account token limit1597		let total_items: u32 = ItemListIndex::get(collection_id)1598			.checked_add(amount)1599			.ok_or(Error::<T>::CollectionTokenLimitExceeded)?;1600		let account_items: u32 = (<AddressTokens<T>>::get(collection_id, owner.as_sub()).len()1601			as u32)1602			.checked_add(amount)1603			.ok_or(Error::<T>::AccountTokenLimitExceeded)?;1604		ensure!(1605			collection.limits.token_limit >= total_items,1606			Error::<T>::CollectionTokenLimitExceeded1607		);1608		ensure!(1609			collection.limits.account_token_ownership_limit >= account_items,1610			Error::<T>::AccountTokenLimitExceeded1611		);16121613		if !Self::is_owner_or_admin_permissions(collection, sender)? {1614			ensure!(collection.mint_mode, Error::<T>::PublicMintingNotAllowed);1615			Self::check_white_list(collection, owner)?;1616			Self::check_white_list(collection, sender)?;1617		}16181619		Ok(())1620	}16211622	fn validate_create_item_args(1623		target_collection: &CollectionHandle<T>,1624		data: &CreateItemData,1625	) -> DispatchResult {1626		match target_collection.mode {1627			CollectionMode::NFT => {1628				if let CreateItemData::NFT(data) = data {1629					// check sizes1630					ensure!(1631						ChainLimit::get().custom_data_limit >= data.const_data.len() as u32,1632						Error::<T>::TokenConstDataLimitExceeded1633					);1634					ensure!(1635						ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32,1636						Error::<T>::TokenVariableDataLimitExceeded1637					);1638				} else {1639					fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1640				}1641			}1642			CollectionMode::Fungible(_) => {1643				if let CreateItemData::Fungible(_) = data {1644				} else {1645					fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1646				}1647			}1648			CollectionMode::ReFungible => {1649				if let CreateItemData::ReFungible(data) = data {1650					// check sizes1651					ensure!(1652						ChainLimit::get().custom_data_limit >= data.const_data.len() as u32,1653						Error::<T>::TokenConstDataLimitExceeded1654					);1655					ensure!(1656						ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32,1657						Error::<T>::TokenVariableDataLimitExceeded1658					);16591660					// Check refungibility limits1661					ensure!(1662						data.pieces <= MAX_REFUNGIBLE_PIECES,1663						Error::<T>::WrongRefungiblePieces1664					);1665					ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1666				} else {1667					fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1668				}1669			}1670			_ => {1671				fail!(Error::<T>::UnexpectedCollectionType);1672			}1673		};16741675		Ok(())1676	}16771678	fn create_item_no_validation(1679		collection: &CollectionHandle<T>,1680		owner: &T::CrossAccountId,1681		data: CreateItemData,1682	) -> DispatchResult {1683		match data {1684			CreateItemData::NFT(data) => {1685				let item = NftItemType {1686					owner: owner.clone(),1687					const_data: data.const_data,1688					variable_data: data.variable_data,1689				};16901691				Self::add_nft_item(collection, item)?;1692			}1693			CreateItemData::Fungible(data) => {1694				Self::add_fungible_item(collection, owner, data.value)?;1695			}1696			CreateItemData::ReFungible(data) => {1697				let owner_list = vec![Ownership {1698					owner: owner.clone(),1699					fraction: data.pieces,1700				}];17011702				let item = ReFungibleItemType {1703					owner: owner_list,1704					const_data: data.const_data,1705					variable_data: data.variable_data,1706				};17071708				Self::add_refungible_item(collection, item)?;1709			}1710		};17111712		Ok(())1713	}17141715	fn add_fungible_item(1716		collection: &CollectionHandle<T>,1717		owner: &T::CrossAccountId,1718		value: u128,1719	) -> DispatchResult {1720		let collection_id = collection.id;17211722		// Does new owner already have an account?1723		collection.consume_sload()?;1724		let balance: u128 = <FungibleItemList<T>>::get(collection_id, owner.as_sub()).value;17251726		// Mint1727		let item = FungibleItemType {1728			value: balance.checked_add(value).ok_or(Error::<T>::NumOverflow)?,1729		};1730		collection.consume_sstore()?;1731		<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), item);17321733		// Update balance1734		collection.consume_sload()?;1735		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1736			.checked_add(value)1737			.ok_or(Error::<T>::NumOverflow)?;1738		collection.consume_sstore()?;1739		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);17401741		collection.log(ERC20Events::Transfer {1742			from: H160::default(),1743			to: *owner.as_eth(),1744			value: value.into(),1745		})?;1746		Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));1747		Ok(())1748	}17491750	fn add_refungible_item(1751		collection: &CollectionHandle<T>,1752		item: ReFungibleItemType<T::CrossAccountId>,1753	) -> DispatchResult {1754		let collection_id = collection.id;17551756		let current_index = <ItemListIndex>::get(collection_id)1757			.checked_add(1)1758			.ok_or(Error::<T>::NumOverflow)?;1759		let itemcopy = item.clone();17601761		ensure!(item.owner.len() == 1, Error::<T>::BadCreateRefungibleCall,);1762		let item_owner = item.owner.first().expect("only one owner is defined");17631764		let value = item_owner.fraction;1765		let owner = item_owner.owner.clone();17661767		Self::add_token_index(collection, current_index, &owner)?;17681769		<ItemListIndex>::insert(collection_id, current_index);1770		<ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);17711772		// Update balance1773		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1774			.checked_add(value)1775			.ok_or(Error::<T>::NumOverflow)?;1776		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);17771778		Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));1779		Ok(())1780	}17811782	fn add_nft_item(1783		collection: &CollectionHandle<T>,1784		item: NftItemType<T::CrossAccountId>,1785	) -> DispatchResult {1786		let collection_id = collection.id;17871788		let current_index = <ItemListIndex>::get(collection_id)1789			.checked_add(1)1790			.ok_or(Error::<T>::NumOverflow)?;17911792		let item_owner = item.owner.clone();1793		Self::add_token_index(collection, current_index, &item.owner)?;17941795		<ItemListIndex>::insert(collection_id, current_index);1796		<NftItemList<T>>::insert(collection_id, current_index, item);17971798		// Update balance1799		let new_balance = <Balance<T>>::get(collection_id, item_owner.as_sub())1800			.checked_add(1)1801			.ok_or(Error::<T>::NumOverflow)?;1802		<Balance<T>>::insert(collection_id, item_owner.as_sub(), new_balance);18031804		collection.log(ERC721Events::Transfer {1805			from: H160::default(),1806			to: *item_owner.as_eth(),1807			token_id: current_index.into(),1808		})?;1809		Self::deposit_event(RawEvent::ItemCreated(1810			collection_id,1811			current_index,1812			item_owner,1813		));1814		Ok(())1815	}18161817	fn burn_refungible_item(1818		collection: &CollectionHandle<T>,1819		item_id: TokenId,1820		owner: &T::CrossAccountId,1821	) -> DispatchResult {1822		let collection_id = collection.id;18231824		let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id)1825			.ok_or(Error::<T>::TokenNotFound)?;1826		let rft_balance = token1827			.owner1828			.iter()1829			.find(|&i| i.owner == *owner)1830			.ok_or(Error::<T>::TokenNotFound)?;1831		Self::remove_token_index(collection, item_id, owner)?;18321833		// update balance1834		let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.as_sub())1835			.checked_sub(rft_balance.fraction)1836			.ok_or(Error::<T>::NumOverflow)?;1837		<Balance<T>>::insert(collection_id, rft_balance.owner.as_sub(), new_balance);18381839		// Re-create owners list with sender removed1840		let index = token1841			.owner1842			.iter()1843			.position(|i| i.owner == *owner)1844			.expect("owned item is exists");1845		token.owner.remove(index);1846		let owner_count = token.owner.len();18471848		// Burn the token completely if this was the last (only) owner1849		if owner_count == 0 {1850			<ReFungibleItemList<T>>::remove(collection_id, item_id);1851			<VariableMetaDataBasket<T>>::remove(collection_id, item_id);1852		} else {1853			<ReFungibleItemList<T>>::insert(collection_id, item_id, token);1854		}18551856		Ok(())1857	}18581859	fn burn_nft_item(collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1860		let collection_id = collection.id;18611862		let item =1863			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;1864		Self::remove_token_index(collection, item_id, &item.owner)?;18651866		// update balance1867		let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())1868			.checked_sub(1)1869			.ok_or(Error::<T>::NumOverflow)?;1870		<Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);1871		<NftItemList<T>>::remove(collection_id, item_id);1872		<VariableMetaDataBasket<T>>::remove(collection_id, item_id);18731874		Self::deposit_event(RawEvent::ItemDestroyed(collection.id, item_id));1875		Ok(())1876	}18771878	fn burn_fungible_item(1879		owner: &T::CrossAccountId,1880		collection: &CollectionHandle<T>,1881		value: u128,1882	) -> DispatchResult {1883		let collection_id = collection.id;18841885		let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());1886		ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);18871888		// update balance1889		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1890			.checked_sub(value)1891			.ok_or(Error::<T>::NumOverflow)?;1892		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);18931894		if balance.value - value > 0 {1895			balance.value -= value;1896			<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);1897		} else {1898			<FungibleItemList<T>>::remove(collection_id, owner.as_sub());1899		}19001901		collection.log(ERC20Events::Transfer {1902			from: *owner.as_eth(),1903			to: H160::default(),1904			value: value.into(),1905		})?;1906		Ok(())1907	}19081909	pub fn get_collection(1910		collection_id: CollectionId,1911	) -> Result<CollectionHandle<T>, sp_runtime::DispatchError> {1912		Ok(<CollectionHandle<T>>::get(collection_id).ok_or(Error::<T>::CollectionNotFound)?)1913	}19141915	fn check_owner_permissions(1916		target_collection: &CollectionHandle<T>,1917		subject: &T::AccountId,1918	) -> DispatchResult {1919		ensure!(1920			*subject == target_collection.owner,1921			Error::<T>::NoPermission1922		);19231924		Ok(())1925	}19261927	fn is_owner_or_admin_permissions(1928		collection: &CollectionHandle<T>,1929		subject: &T::CrossAccountId,1930	) -> Result<bool, DispatchError> {1931		collection.consume_sload()?;1932		Ok(*subject.as_sub() == collection.owner1933			|| <AdminList<T>>::get(collection.id).contains(subject))1934	}19351936	fn check_owner_or_admin_permissions(1937		collection: &CollectionHandle<T>,1938		subject: &T::CrossAccountId,1939	) -> DispatchResult {1940		ensure!(1941			Self::is_owner_or_admin_permissions(collection, subject)?,1942			Error::<T>::NoPermission1943		);19441945		Ok(())1946	}19471948	fn owned_amount(1949		subject: &T::CrossAccountId,1950		collection: &CollectionHandle<T>,1951		item_id: TokenId,1952	) -> Result<Option<u128>, DispatchError> {1953		collection.consume_sload()?;1954		Ok(Self::owned_amount_unchecked(subject, collection, item_id))1955	}19561957	fn owned_amount_unchecked(1958		subject: &T::CrossAccountId,1959		target_collection: &CollectionHandle<T>,1960		item_id: TokenId,1961	) -> Option<u128> {1962		let collection_id = target_collection.id;19631964		match target_collection.mode {1965			CollectionMode::NFT => {1966				(<NftItemList<T>>::get(collection_id, item_id)?.owner == *subject).then(|| 1)1967			}1968			CollectionMode::Fungible(_) => {1969				Some(<FungibleItemList<T>>::get(collection_id, &subject.as_sub()).value)1970			}1971			CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)?1972				.owner1973				.iter()1974				.find(|i| i.owner == *subject)1975				.map(|i| i.fraction),1976			CollectionMode::Invalid => None,1977		}1978	}19791980	fn is_item_owner(1981		subject: &T::CrossAccountId,1982		target_collection: &CollectionHandle<T>,1983		item_id: TokenId,1984	) -> Result<bool, DispatchError> {1985		Ok(match target_collection.mode {1986			CollectionMode::Fungible(_) => true,1987			_ => Self::owned_amount(subject, target_collection, item_id)?.is_some(),1988		})1989	}19901991	fn check_white_list(1992		collection: &CollectionHandle<T>,1993		address: &T::CrossAccountId,1994	) -> DispatchResult {1995		collection.consume_sload()?;1996		ensure!(1997			<WhiteList<T>>::contains_key(collection.id, address.as_sub()),1998			Error::<T>::AddresNotInWhiteList,1999		);2000		Ok(())2001	}20022003	/// Check if token exists. In case of Fungible, check if there is an entry for2004	/// the owner in fungible balances double map2005	fn token_exists(target_collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {2006		let collection_id = target_collection.id;2007		let exists = match target_collection.mode {2008			CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),2009			CollectionMode::Fungible(_) => true,2010			CollectionMode::ReFungible => {2011				<ReFungibleItemList<T>>::contains_key(collection_id, item_id)2012			}2013			_ => false,2014		};20152016		ensure!(exists, Error::<T>::TokenNotFound);2017		Ok(())2018	}20192020	fn transfer_fungible(2021		collection: &CollectionHandle<T>,2022		value: u128,2023		owner: &T::CrossAccountId,2024		recipient: &T::CrossAccountId,2025	) -> DispatchResult {2026		let collection_id = collection.id;20272028		collection.consume_sload()?;2029		let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());2030		ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);20312032		// Send balance to recipient (updates balanceOf of recipient)2033		Self::add_fungible_item(collection, recipient, value)?;20342035		// update balanceOf of sender2036		collection.consume_sstore()?;2037		<Balance<T>>::insert(collection_id, owner.as_sub(), balance.value - value);20382039		// Reduce or remove sender2040		collection.consume_sstore()?;2041		if balance.value == value {2042			<FungibleItemList<T>>::remove(collection_id, owner.as_sub());2043		} else {2044			balance.value -= value;2045			<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);2046		}20472048		collection.log(ERC20Events::Transfer {2049			from: *owner.as_eth(),2050			to: *recipient.as_eth(),2051			value: value.into(),2052		})?;2053		Self::deposit_event(RawEvent::Transfer(2054			collection.id,2055			1,2056			owner.clone(),2057			recipient.clone(),2058			value,2059		));20602061		Ok(())2062	}20632064	fn transfer_refungible(2065		collection: &CollectionHandle<T>,2066		item_id: TokenId,2067		value: u128,2068		owner: T::CrossAccountId,2069		new_owner: T::CrossAccountId,2070	) -> DispatchResult {2071		let collection_id = collection.id;2072		collection.consume_sload()?;2073		let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id)2074			.ok_or(Error::<T>::TokenNotFound)?;20752076		let item = full_item2077			.owner2078			.iter()2079			.find(|i| i.owner == owner)2080			.ok_or(Error::<T>::TokenNotFound)?;2081		let amount = item.fraction;20822083		ensure!(amount >= value, Error::<T>::TokenValueTooLow);20842085		collection.consume_sload()?;2086		// update balance2087		let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2088			.checked_sub(value)2089			.ok_or(Error::<T>::NumOverflow)?;2090		collection.consume_sstore()?;2091		<Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);20922093		collection.consume_sload()?;2094		let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2095			.checked_add(value)2096			.ok_or(Error::<T>::NumOverflow)?;2097		collection.consume_sstore()?;2098		<Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);20992100		let old_owner = item.owner.clone();2101		let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);21022103		let mut new_full_item = full_item.clone();2104		// transfer2105		if amount == value && !new_owner_has_account {2106			// change owner2107			// new owner do not have account2108			new_full_item2109				.owner2110				.iter_mut()2111				.find(|i| i.owner == owner)2112				.expect("old owner does present in refungible")2113				.owner = new_owner.clone();2114			collection.consume_sstore()?;2115			<ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);21162117			// update index collection2118			Self::move_token_index(collection, item_id, &old_owner, &new_owner)?;2119		} else {2120			new_full_item2121				.owner2122				.iter_mut()2123				.find(|i| i.owner == owner)2124				.expect("old owner does present in refungible")2125				.fraction -= value;21262127			// separate amount2128			if new_owner_has_account {2129				// new owner has account2130				new_full_item2131					.owner2132					.iter_mut()2133					.find(|i| i.owner == new_owner)2134					.expect("new owner has account")2135					.fraction += value;2136			} else {2137				// new owner do not have account2138				new_full_item.owner.push(Ownership {2139					owner: new_owner.clone(),2140					fraction: value,2141				});2142				Self::add_token_index(collection, item_id, &new_owner)?;2143			}21442145			collection.consume_sstore()?;2146			<ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2147		}21482149		Self::deposit_event(RawEvent::Transfer(2150			collection.id,2151			item_id,2152			owner,2153			new_owner,2154			amount,2155		));21562157		Ok(())2158	}21592160	fn transfer_nft(2161		collection: &CollectionHandle<T>,2162		item_id: TokenId,2163		sender: T::CrossAccountId,2164		new_owner: T::CrossAccountId,2165	) -> DispatchResult {2166		let collection_id = collection.id;2167		collection.consume_sload()?;2168		let mut item =2169			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;21702171		ensure!(sender == item.owner, Error::<T>::MustBeTokenOwner);21722173		collection.consume_sload()?;2174		// update balance2175		let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2176			.checked_sub(1)2177			.ok_or(Error::<T>::NumOverflow)?;2178		collection.consume_sstore()?;2179		<Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);21802181		collection.consume_sload()?;2182		let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2183			.checked_add(1)2184			.ok_or(Error::<T>::NumOverflow)?;2185		collection.consume_sstore()?;2186		<Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);21872188		// change owner2189		let old_owner = item.owner.clone();2190		item.owner = new_owner.clone();2191		collection.consume_sstore()?;2192		<NftItemList<T>>::insert(collection_id, item_id, item);21932194		// update index collection2195		Self::move_token_index(collection, item_id, &old_owner, &new_owner)?;21962197		collection.log(ERC721Events::Transfer {2198			from: *sender.as_eth(),2199			to: *new_owner.as_eth(),2200			token_id: item_id.into(),2201		})?;2202		Self::deposit_event(RawEvent::Transfer(2203			collection.id,2204			item_id,2205			sender,2206			new_owner,2207			1,2208		));22092210		Ok(())2211	}22122213	fn set_re_fungible_variable_data(2214		collection: &CollectionHandle<T>,2215		item_id: TokenId,2216		data: Vec<u8>,2217	) -> DispatchResult {2218		let collection_id = collection.id;2219		let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id)2220			.ok_or(Error::<T>::TokenNotFound)?;22212222		item.variable_data = data;22232224		<ReFungibleItemList<T>>::insert(collection_id, item_id, item);22252226		Ok(())2227	}22282229	fn set_nft_variable_data(2230		collection: &CollectionHandle<T>,2231		item_id: TokenId,2232		data: Vec<u8>,2233	) -> DispatchResult {2234		let collection_id = collection.id;2235		let mut item =2236			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;22372238		item.variable_data = data;22392240		<NftItemList<T>>::insert(collection_id, item_id, item);22412242		Ok(())2243	}22442245	#[allow(dead_code)]2246	fn init_collection(item: &Collection<T>) {2247		// check params2248		assert!(2249			item.decimal_points <= MAX_DECIMAL_POINTS,2250			"decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2251		);2252		assert!(2253			item.name.len() <= 64,2254			"Collection name can not be longer than 63 char"2255		);2256		assert!(2257			item.name.len() <= 256,2258			"Collection description can not be longer than 255 char"2259		);2260		assert!(2261			item.token_prefix.len() <= 16,2262			"Token prefix can not be longer than 15 char"2263		);22642265		// Generate next collection ID2266		let next_id = CreatedCollectionCount::get().checked_add(1).unwrap();22672268		CreatedCollectionCount::put(next_id);2269	}22702271	#[allow(dead_code)]2272	fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::CrossAccountId>) {2273		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();22742275		Self::add_token_index(2276			&CollectionHandle::get(collection_id).unwrap(),2277			current_index,2278			&item.owner,2279		)2280		.unwrap();22812282		<ItemListIndex>::insert(collection_id, current_index);22832284		// Update balance2285		let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2286			.checked_add(1)2287			.unwrap();2288		<Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2289	}22902291	#[allow(dead_code)]2292	fn init_fungible_token(2293		collection_id: CollectionId,2294		owner: &T::CrossAccountId,2295		item: &FungibleItemType,2296	) {2297		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();22982299		Self::add_token_index(2300			&CollectionHandle::get(collection_id).unwrap(),2301			current_index,2302			owner,2303		)2304		.unwrap();23052306		<ItemListIndex>::insert(collection_id, current_index);23072308		// Update balance2309		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2310			.checked_add(item.value)2311			.unwrap();2312		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2313	}23142315	#[allow(dead_code)]2316	fn init_refungible_token(2317		collection_id: CollectionId,2318		item: &ReFungibleItemType<T::CrossAccountId>,2319	) {2320		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();23212322		let value = item.owner.first().unwrap().fraction;2323		let owner = item.owner.first().unwrap().owner.clone();23242325		Self::add_token_index(2326			&CollectionHandle::get(collection_id).unwrap(),2327			current_index,2328			&owner,2329		)2330		.unwrap();23312332		<ItemListIndex>::insert(collection_id, current_index);23332334		// Update balance2335		let new_balance = <Balance<T>>::get(collection_id, &owner.as_sub())2336			.checked_add(value)2337			.unwrap();2338		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2339	}23402341	fn add_token_index(2342		collection: &CollectionHandle<T>,2343		item_index: TokenId,2344		owner: &T::CrossAccountId,2345	) -> DispatchResult {2346		// add to account limit2347		collection.consume_sload()?;2348		if <AccountItemCount<T>>::contains_key(owner.as_sub()) {2349			// bound Owned tokens by a single address2350			collection.consume_sload()?;2351			let count = <AccountItemCount<T>>::get(owner.as_sub());2352			ensure!(2353				count < ChainLimit::get().account_token_ownership_limit,2354				Error::<T>::AddressOwnershipLimitExceeded2355			);23562357			collection.consume_sstore()?;2358			<AccountItemCount<T>>::insert(2359				owner.as_sub(),2360				count.checked_add(1).ok_or(Error::<T>::NumOverflow)?,2361			);2362		} else {2363			collection.consume_sstore()?;2364			<AccountItemCount<T>>::insert(owner.as_sub(), 1);2365		}23662367		collection.consume_sload()?;2368		let list_exists = <AddressTokens<T>>::contains_key(collection.id, owner.as_sub());2369		if list_exists {2370			collection.consume_sload()?;2371			let mut list = <AddressTokens<T>>::get(collection.id, owner.as_sub());2372			let item_contains = list.contains(&item_index.clone());23732374			if !item_contains {2375				list.push(item_index);2376			}23772378			collection.consume_sstore()?;2379			<AddressTokens<T>>::insert(collection.id, owner.as_sub(), list);2380		} else {2381			let itm = vec![item_index];2382			collection.consume_sstore()?;2383			<AddressTokens<T>>::insert(collection.id, owner.as_sub(), itm);2384		}23852386		Ok(())2387	}23882389	fn remove_token_index(2390		collection: &CollectionHandle<T>,2391		item_index: TokenId,2392		owner: &T::CrossAccountId,2393	) -> DispatchResult {2394		// update counter2395		collection.consume_sload()?;2396		collection.consume_sstore()?;2397		<AccountItemCount<T>>::insert(2398			owner.as_sub(),2399			<AccountItemCount<T>>::get(owner.as_sub())2400				.checked_sub(1)2401				.ok_or(Error::<T>::NumOverflow)?,2402		);24032404		collection.consume_sload()?;2405		let list_exists = <AddressTokens<T>>::contains_key(collection.id, owner.as_sub());2406		if list_exists {2407			collection.consume_sload()?;2408			let mut list = <AddressTokens<T>>::get(collection.id, owner.as_sub());2409			let item_contains = list.contains(&item_index.clone());24102411			if item_contains {2412				list.retain(|&item| item != item_index);2413				collection.consume_sstore()?;2414				<AddressTokens<T>>::insert(collection.id, owner.as_sub(), list);2415			}2416		}24172418		Ok(())2419	}24202421	fn move_token_index(2422		collection: &CollectionHandle<T>,2423		item_index: TokenId,2424		old_owner: &T::CrossAccountId,2425		new_owner: &T::CrossAccountId,2426	) -> DispatchResult {2427		Self::remove_token_index(collection, item_index, old_owner)?;2428		Self::add_token_index(collection, item_index, new_owner)?;24292430		Ok(())2431	}2432}24332434sp_api::decl_runtime_apis! {2435	pub trait NftApi {2436		/// Used for ethereum integration2437		fn eth_contract_code(account: H160) -> Option<Vec<u8>>;2438	}2439}