git.delta.rocks / unique-network / refs/commits / 7791279df554

difftreelog

source

pallets/nft/src/lib.rs73.7 KiBsourcehistory
1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]7#![cfg_attr(not(feature = "std"), no_std)]8#![allow(9	clippy::too_many_arguments,10	clippy::unnecessary_mut_passed,11	clippy::unused_unit12)]1314extern crate alloc;1516pub use serde::{Serialize, Deserialize};1718pub use frame_support::{19	construct_runtime, decl_event, decl_module, decl_storage, decl_error,20	dispatch::DispatchResult,21	ensure, fail, parameter_types,22	traits::{23		Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,24		Randomness, IsSubType, WithdrawReasons,25	},26	weights::{27		constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},28		DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,29		WeightToFeePolynomial, DispatchClass,30	},31	StorageValue, transactional,32};3334use frame_system::{self as system, ensure_signed};35use sp_core::H160;36use sp_std::vec;37use sp_runtime::{DispatchError, sp_std::prelude::Vec};38use core::ops::{Deref, DerefMut};39use nft_data_structs::{40	MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, MAX_REFUNGIBLE_PIECES,41	CUSTOM_DATA_LIMIT, COLLECTION_NUMBER_LIMIT, ACCOUNT_TOKEN_OWNERSHIP_LIMIT,42	VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, COLLECTION_ADMINS_LIMIT,43	OFFCHAIN_SCHEMA_LIMIT, MAX_TOKEN_PREFIX_LENGTH, MAX_COLLECTION_NAME_LENGTH,44	MAX_COLLECTION_DESCRIPTION_LENGTH, AccessMode, Collection, CreateItemData, CollectionLimits,45	CollectionId, CollectionMode, TokenId, SchemaVersion, SponsorshipState, Ownership, NftItemType,46	MetaUpdatePermission, FungibleItemType, ReFungibleItemType,47};4849#[cfg(test)]50mod mock;5152#[cfg(test)]53mod tests;5455mod eth;56mod sponsorship;57pub use sponsorship::NftSponsorshipHandler;58pub use eth::sponsoring::NftEthSponsorshipHandler;5960pub use eth::NftErcSupport;61pub use eth::account::*;62use eth::erc::{ERC20Events, ERC721Events};6364#[cfg(feature = "runtime-benchmarks")]65mod benchmarking;66pub mod weights;67use weights::WeightInfo;6869decl_error! {70	/// Error for non-fungible-token module.71	pub enum Error for Module<T: Config> {72		/// Total collections bound exceeded.73		TotalCollectionsLimitExceeded,74		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.75		CollectionDecimalPointLimitExceeded,76		/// Collection name can not be longer than 63 char.77		CollectionNameLimitExceeded,78		/// Collection description can not be longer than 255 char.79		CollectionDescriptionLimitExceeded,80		/// Token prefix can not be longer than 15 char.81		CollectionTokenPrefixLimitExceeded,82		/// This collection does not exist.83		CollectionNotFound,84		/// Item not exists.85		TokenNotFound,86		/// Admin not found87		AdminNotFound,88		/// Arithmetic calculation overflow.89		NumOverflow,90		/// Account already has admin role.91		AlreadyAdmin,92		/// You do not own this collection.93		NoPermission,94		/// This address is not set as sponsor, use setCollectionSponsor first.95		ConfirmUnsetSponsorFail,96		/// Collection is not in mint mode.97		PublicMintingNotAllowed,98		/// Sender parameter and item owner must be equal.99		MustBeTokenOwner,100		/// Item balance not enough.101		TokenValueTooLow,102		/// Size of item is too large.103		NftSizeLimitExceeded,104		/// No approve found105		ApproveNotFound,106		/// Requested value more than approved.107		TokenValueNotEnough,108		/// Only approved addresses can call this method.109		ApproveRequired,110		/// Address is not in white list.111		AddresNotInWhiteList,112		/// Number of collection admins bound exceeded.113		CollectionAdminsLimitExceeded,114		/// Owned tokens by a single address bound exceeded.115		AddressOwnershipLimitExceeded,116		/// Length of items properties must be greater than 0.117		EmptyArgument,118		/// const_data exceeded data limit.119		TokenConstDataLimitExceeded,120		/// variable_data exceeded data limit.121		TokenVariableDataLimitExceeded,122		/// Not NFT item data used to mint in NFT collection.123		NotNftDataUsedToMintNftCollectionToken,124		/// Not Fungible item data used to mint in Fungible collection.125		NotFungibleDataUsedToMintFungibleCollectionToken,126		/// Not Re Fungible item data used to mint in Re Fungible collection.127		NotReFungibleDataUsedToMintReFungibleCollectionToken,128		/// Unexpected collection type.129		UnexpectedCollectionType,130		/// Can't store metadata in fungible tokens.131		CantStoreMetadataInFungibleTokens,132		/// Collection token limit exceeded133		CollectionTokenLimitExceeded,134		/// Account token limit exceeded per collection135		AccountTokenLimitExceeded,136		/// Collection limit bounds per collection exceeded137		CollectionLimitBoundsExceeded,138		/// Tried to enable permissions which are only permitted to be disabled139		OwnerPermissionsCantBeReverted,140		/// Schema data size limit bound exceeded141		SchemaDataLimitExceeded,142		/// Maximum refungibility exceeded143		WrongRefungiblePieces,144		/// createRefungible should be called with one owner145		BadCreateRefungibleCall,146		/// Gas limit exceeded147		OutOfGas,148		/// Metadata update denied by collection settings149		MetadataUpdateDenied,150		/// Metadata update flag become unmutable with None option151		MetadataFlagFrozen,152		/// Collection settings not allowing items transferring153		TransferNotAllowed,154		/// Can't transfer tokens to ethereum zero address155		AddressIsZero,156	}157}158159#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]160pub struct CollectionHandle<T: Config> {161	pub id: CollectionId,162	collection: Collection<T>,163	recorder: pallet_evm_coder_substrate::SubstrateRecorder<T>,164}165impl<T: Config> CollectionHandle<T> {166	pub fn get_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {167		<CollectionById<T>>::get(id).map(|collection| Self {168			id,169			collection,170			recorder: pallet_evm_coder_substrate::SubstrateRecorder::new(171				eth::collection_id_to_address(id),172				gas_limit,173			),174		})175	}176	pub fn get(id: CollectionId) -> Option<Self> {177		Self::get_with_gas_limit(id, u64::MAX)178	}179	pub fn log(&self, log: impl evm_coder::ToLog) -> DispatchResult {180		self.recorder.log_sub(log)181	}182	#[allow(dead_code)]183	fn consume_gas(&self, gas: u64) -> DispatchResult {184		self.recorder.consume_gas_sub(gas)185	}186	fn consume_sload(&self) -> DispatchResult {187		self.recorder.consume_sload_sub()188	}189	fn consume_sstore(&self) -> DispatchResult {190		self.recorder.consume_sstore_sub()191	}192	pub fn submit_logs(self) -> DispatchResult {193		self.recorder.submit_logs()194	}195	pub fn save(self) -> DispatchResult {196		self.recorder.submit_logs()?;197		<CollectionById<T>>::insert(self.id, self.collection);198		Ok(())199	}200}201impl<T: Config> Deref for CollectionHandle<T> {202	type Target = Collection<T>;203204	fn deref(&self) -> &Self::Target {205		&self.collection206	}207}208209impl<T: Config> DerefMut for CollectionHandle<T> {210	fn deref_mut(&mut self) -> &mut Self::Target {211		&mut self.collection212	}213}214215pub trait Config: system::Config + pallet_evm_coder_substrate::Config + Sized {216	type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;217218	/// Weight information for extrinsics in this pallet.219	type WeightInfo: WeightInfo;220221	type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;222	type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;223224	type CrossAccountId: CrossAccountId<Self::AccountId>;225	type Currency: Currency<Self::AccountId>;226	type CollectionCreationPrice: Get<227		<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,228	>;229	type TreasuryAccountId: Get<Self::AccountId>;230}231232type SelfWeightOf<T> = <T as Config>::WeightInfo;233234trait WeightInfoHelpers: WeightInfo {235	fn transfer() -> Weight {236		Self::transfer_nft()237			.max(Self::transfer_fungible())238			.max(Self::transfer_refungible())239	}240	fn transfer_from() -> Weight {241		Self::transfer_from_nft()242			.max(Self::transfer_from_fungible())243			.max(Self::transfer_from_refungible())244	}245	fn approve() -> Weight {246		// TODO: refungible, fungible247		Self::approve_nft()248	}249	fn set_variable_meta_data(data: u32) -> Weight {250		// TODO: refungible251		Self::set_variable_meta_data_nft(data)252	}253	fn create_item(data: u32) -> Weight {254		Self::create_item_nft(data)255			.max(Self::create_item_fungible())256			.max(Self::create_item_refungible(data))257	}258	fn create_multiple_items(amount: u32) -> Weight {259		Self::create_multiple_items_nft(amount)260			.max(Self::create_multiple_items_fungible(amount))261			.max(Self::create_multiple_items_refungible(amount))262	}263	fn burn_item() -> Weight {264		// TODO: refungible, fungible265		Self::burn_item_nft()266	}267}268impl<T: WeightInfo> WeightInfoHelpers for T {}269270// # Used definitions271//272// ## User control levels273//274// chain-controlled - key is uncontrolled by user275//                    i.e autoincrementing index276//                    can use non-cryptographic hash277// real - key is controlled by user278//        but it is hard to generate enough colliding values, i.e owner of signed txs279//        can use non-cryptographic hash280// controlled - key is completly controlled by users281//              i.e maps with mutable keys282//              should use cryptographic hash283//284// ## User control level downgrade reasons285//286// ?1 - chain-controlled -> controlled287//      collections/tokens can be destroyed, resulting in massive holes288// ?2 - chain-controlled -> controlled289//      same as ?1, but can be only added, resulting in easier exploitation290// ?3 - real -> controlled291//      no confirmation required, so addresses can be easily generated292decl_storage! {293	trait Store for Module<T: Config> as Nft {294295		//#region Private members296		/// Id of next collection297		CreatedCollectionCount: u32;298		/// Used for migrations299		ChainVersion: u64;300		/// Id of last collection token301		/// Collection id (controlled?1)302		ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;303		//#endregion304305		//#region Bound counters306		/// Amount of collections destroyed, used for total amount tracking with307		/// CreatedCollectionCount308		DestroyedCollectionCount: u32;309		/// Total amount of account owned tokens (NFTs + RFTs + unique fungibles)310		/// Account id (real)311		pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;312		//#endregion313314		//#region Basic collections315		/// Collection info316		/// Collection id (controlled?1)317		pub CollectionById get(fn collection_id) config(): map hasher(blake2_128_concat) CollectionId => Option<Collection<T>> = None;318		/// List of collection admins319		/// Collection id (controlled?2)320		pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec<T::CrossAccountId>;321		/// Whitelisted collection users322		/// Collection id (controlled?2), user id (controlled?3)323		pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool;324		//#endregion325326		/// How many of collection items user have327		/// Collection id (controlled?2), account id (real)328		pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128;329330		/// Amount of items which spender can transfer out of owners account (via transferFrom)331		/// Collection id (controlled?2), (token id (controlled ?2) + owner account id (real) + spender account id (controlled?3))332		/// TODO: Off chain worker should remove from this map when token gets removed333		pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128;334335		//#region Item collections336		/// Collection id (controlled?2), token id (controlled?1)337		pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<NftItemType<T::CrossAccountId>>;338		/// Collection id (controlled?2), owner (controlled?2)339		pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType;340		/// Collection id (controlled?2), token id (controlled?1)341		pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<ReFungibleItemType<T::CrossAccountId>>;342		//#endregion343344		//#region Index list345		/// Collection id (controlled?2), tokens owner (controlled?2)346		pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;347		//#endregion348349		//#region Tokens transfer rate limit baskets350		/// (Collection id (controlled?2), who created (real))351		/// TODO: Off chain worker should remove from this map when collection gets removed352		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;353		/// Collection id (controlled?2), token id (controlled?2)354		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;355		/// Collection id (controlled?2), owning user (real)356		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;357		/// Collection id (controlled?2), token id (controlled?2)358		pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;359		//#endregion360361		/// Variable metadata sponsoring362		/// Collection id (controlled?2), token id (controlled?2)363		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;364	}365	add_extra_genesis {366		build(|config: &GenesisConfig<T>| {367			// Modification of storage368			for (_num, _c) in &config.collection_id {369				<Module<T>>::init_collection(_c);370			}371372			for (_num, _c, _i) in &config.nft_item_id {373				<Module<T>>::init_nft_token(*_c, _i);374			}375376			for (collection_id, account_id, fungible_item) in &config.fungible_item_id {377				<Module<T>>::init_fungible_token(*collection_id, &T::CrossAccountId::from_sub(account_id.clone()), fungible_item);378			}379380			for (_num, _c, _i) in &config.refungible_item_id {381				<Module<T>>::init_refungible_token(*_c, _i);382			}383		})384	}385}386387decl_event!(388	pub enum Event<T>389	where390		AccountId = <T as frame_system::Config>::AccountId,391		CrossAccountId = <T as Config>::CrossAccountId,392	{393		/// New collection was created394		///395		/// # Arguments396		///397		/// * collection_id: Globally unique identifier of newly created collection.398		///399		/// * mode: [CollectionMode] converted into u8.400		///401		/// * account_id: Collection owner.402		CollectionCreated(CollectionId, u8, AccountId),403404		/// New item was created.405		///406		/// # Arguments407		///408		/// * collection_id: Id of the collection where item was created.409		///410		/// * item_id: Id of an item. Unique within the collection.411		///412		/// * recipient: Owner of newly created item413		ItemCreated(CollectionId, TokenId, CrossAccountId),414415		/// Collection item was burned.416		///417		/// # Arguments418		///419		/// collection_id.420		///421		/// item_id: Identifier of burned NFT.422		ItemDestroyed(CollectionId, TokenId),423424		/// Item was transferred425		///426		/// * collection_id: Id of collection to which item is belong427		///428		/// * item_id: Id of an item429		///430		/// * sender: Original owner of item431		///432		/// * recipient: New owner of item433		///434		/// * amount: Always 1 for NFT435		Transfer(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),436437		/// * collection_id438		///439		/// * item_id440		///441		/// * sender442		///443		/// * spender444		///445		/// * amount446		Approved(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),447	}448);449450decl_module! {451	pub struct Module<T: Config> for enum Call452	where453		origin: T::Origin454	{455		fn deposit_event() = default;456		const CollectionAdminsLimit: u64 = COLLECTION_ADMINS_LIMIT;457		type Error = Error<T>;458459		fn on_initialize(_now: T::BlockNumber) -> Weight {460			0461		}462463		/// 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.464		///465		/// # Permissions466		///467		/// * Anyone.468		///469		/// # Arguments470		///471		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.472		///473		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.474		///475		/// * token_prefix: UTF-8 string with token prefix.476		///477		/// * mode: [CollectionMode] collection type and type dependent data.478		// returns collection ID479		#[weight = <SelfWeightOf<T>>::create_collection()]480		#[transactional]481		pub fn create_collection(origin,482								 collection_name: Vec<u16>,483								 collection_description: Vec<u16>,484								 token_prefix: Vec<u8>,485								 mode: CollectionMode) -> DispatchResult {486487			// Anyone can create a collection488			let who = ensure_signed(origin)?;489490			// Take a (non-refundable) deposit of collection creation491			let mut imbalance = <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();492			imbalance.subsume(<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(493				&T::TreasuryAccountId::get(),494				T::CollectionCreationPrice::get(),495			));496			<T as Config>::Currency::settle(497				&who,498				imbalance,499				WithdrawReasons::TRANSFER,500				ExistenceRequirement::KeepAlive,501			).map_err(|_| Error::<T>::NoPermission)?;502503			let decimal_points = match mode {504				CollectionMode::Fungible(points) => points,505				_ => 0506			};507508			let created_count = CreatedCollectionCount::get();509			let destroyed_count = DestroyedCollectionCount::get();510511			// bound Total number of collections512			ensure!(created_count - destroyed_count < COLLECTION_NUMBER_LIMIT, Error::<T>::TotalCollectionsLimitExceeded);513514			// check params515			ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);516			ensure!(collection_name.len() <= MAX_COLLECTION_NAME_LENGTH, Error::<T>::CollectionNameLimitExceeded);517			ensure!(collection_description.len() <= MAX_COLLECTION_DESCRIPTION_LENGTH, Error::<T>::CollectionDescriptionLimitExceeded);518			ensure!(token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH, Error::<T>::CollectionTokenPrefixLimitExceeded);519520			// Generate next collection ID521			let next_id = created_count522				.checked_add(1)523				.ok_or(Error::<T>::NumOverflow)?;524525			CreatedCollectionCount::put(next_id);526527			let limits = CollectionLimits {528				sponsored_data_size: CUSTOM_DATA_LIMIT,529				..Default::default()530			};531532			// Create new collection533			let new_collection = Collection {534				owner: who.clone(),535				name: collection_name,536				mode: mode.clone(),537				mint_mode: false,538				access: AccessMode::Normal,539				description: collection_description,540				decimal_points,541				token_prefix,542				offchain_schema: Vec::new(),543				schema_version: SchemaVersion::ImageURL,544				sponsorship: SponsorshipState::Disabled,545				variable_on_chain_schema: Vec::new(),546				const_on_chain_schema: Vec::new(),547				limits,548				meta_update_permission: MetaUpdatePermission::default(),549				transfers_enabled: true,550			};551552			// Add new collection to map553			<CollectionById<T>>::insert(next_id, new_collection);554555			// call event556			Self::deposit_event(RawEvent::CollectionCreated(next_id, mode.id(), who));557558			Ok(())559		}560561		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.562		///563		/// # Permissions564		///565		/// * Collection Owner.566		///567		/// # Arguments568		///569		/// * collection_id: collection to destroy.570		#[weight = <SelfWeightOf<T>>::destroy_collection()]571		#[transactional]572		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {573574			let sender = ensure_signed(origin)?;575			let collection = Self::get_collection(collection_id)?;576			Self::check_owner_permissions(&collection, &sender)?;577			if !collection.limits.owner_can_destroy {578				fail!(Error::<T>::NoPermission);579			}580581			<AddressTokens<T>>::remove_prefix(collection_id, None);582			<Allowances<T>>::remove_prefix(collection_id, None);583			<Balance<T>>::remove_prefix(collection_id, None);584			<ItemListIndex>::remove(collection_id);585			<AdminList<T>>::remove(collection_id);586			<CollectionById<T>>::remove(collection_id);587			<WhiteList<T>>::remove_prefix(collection_id, None);588589			<NftItemList<T>>::remove_prefix(collection_id, None);590			<FungibleItemList<T>>::remove_prefix(collection_id, None);591			<ReFungibleItemList<T>>::remove_prefix(collection_id, None);592593			<NftTransferBasket<T>>::remove_prefix(collection_id, None);594			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);595			<ReFungibleTransferBasket<T>>::remove_prefix(collection_id, None);596597			<VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);598599			DestroyedCollectionCount::put(DestroyedCollectionCount::get()600				.checked_add(1)601				.ok_or(Error::<T>::NumOverflow)?);602603			Ok(())604		}605606		/// Add an address to white list.607		///608		/// # Permissions609		///610		/// * Collection Owner611		/// * Collection Admin612		///613		/// # Arguments614		///615		/// * collection_id.616		///617		/// * address.618		#[weight = <SelfWeightOf<T>>::add_to_white_list()]619		#[transactional]620		pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{621622			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);623			let collection = Self::get_collection(collection_id)?;624625			Self::toggle_white_list_internal(626				&sender,627				&collection,628				&address,629				true,630			)?;631632			Ok(())633		}634635		/// Remove an address from white list.636		///637		/// # Permissions638		///639		/// * Collection Owner640		/// * Collection Admin641		///642		/// # Arguments643		///644		/// * collection_id.645		///646		/// * address.647		#[weight = <SelfWeightOf<T>>::remove_from_white_list()]648		#[transactional]649		pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{650651			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);652			let collection = Self::get_collection(collection_id)?;653654			Self::toggle_white_list_internal(655				&sender,656				&collection,657				&address,658				false,659			)?;660661			Ok(())662		}663664		/// Toggle between normal and white list access for the methods with access for `Anyone`.665		///666		/// # Permissions667		///668		/// * Collection Owner.669		///670		/// # Arguments671		///672		/// * collection_id.673		///674		/// * mode: [AccessMode]675		#[weight = <SelfWeightOf<T>>::set_public_access_mode()]676		#[transactional]677		pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult678		{679			let sender = ensure_signed(origin)?;680681			let mut target_collection = Self::get_collection(collection_id)?;682			Self::check_owner_permissions(&target_collection, &sender)?;683			target_collection.access = mode;684			target_collection.save()685		}686687		/// Allows Anyone to create tokens if:688		/// * White List is enabled, and689		/// * Address is added to white list, and690		/// * This method was called with True parameter691		///692		/// # Permissions693		/// * Collection Owner694		///695		/// # Arguments696		///697		/// * collection_id.698		///699		/// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.700		#[weight = <SelfWeightOf<T>>::set_mint_permission()]701		#[transactional]702		pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult703		{704			let sender = ensure_signed(origin)?;705706			let mut target_collection = Self::get_collection(collection_id)?;707			Self::check_owner_permissions(&target_collection, &sender)?;708			target_collection.mint_mode = mint_permission;709			target_collection.save()710		}711712		/// Change the owner of the collection.713		///714		/// # Permissions715		///716		/// * Collection Owner.717		///718		/// # Arguments719		///720		/// * collection_id.721		///722		/// * new_owner.723		#[weight = <SelfWeightOf<T>>::change_collection_owner()]724		#[transactional]725		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {726727			let sender = ensure_signed(origin)?;728			let mut target_collection = Self::get_collection(collection_id)?;729			Self::check_owner_permissions(&target_collection, &sender)?;730			target_collection.owner = new_owner;731			target_collection.save()732		}733734		/// Adds an admin of the Collection.735		/// 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.736		///737		/// # Permissions738		///739		/// * Collection Owner.740		/// * Collection Admin.741		///742		/// # Arguments743		///744		/// * collection_id: ID of the Collection to add admin for.745		///746		/// * new_admin_id: Address of new admin to add.747		#[weight = <SelfWeightOf<T>>::add_collection_admin()]748		#[transactional]749		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {750			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);751			let collection = Self::get_collection(collection_id)?;752			Self::check_owner_or_admin_permissions(&collection, &sender)?;753			let mut admin_arr = <AdminList<T>>::get(collection_id);754755			match admin_arr.binary_search(&new_admin_id) {756				Ok(_) => {},757				Err(idx) => {758					ensure!(admin_arr.len() < COLLECTION_ADMINS_LIMIT as usize, Error::<T>::CollectionAdminsLimitExceeded);759					admin_arr.insert(idx, new_admin_id);760					<AdminList<T>>::insert(collection_id, admin_arr);761				}762			}763			Ok(())764		}765766		/// 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.767		///768		/// # Permissions769		///770		/// * Collection Owner.771		/// * Collection Admin.772		///773		/// # Arguments774		///775		/// * collection_id: ID of the Collection to remove admin for.776		///777		/// * account_id: Address of admin to remove.778		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]779		#[transactional]780		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {781			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);782			let collection = Self::get_collection(collection_id)?;783			Self::check_owner_or_admin_permissions(&collection, &sender)?;784			let mut admin_arr = <AdminList<T>>::get(collection_id);785786			if let Ok(idx) = admin_arr.binary_search(&account_id) {787				admin_arr.remove(idx);788				<AdminList<T>>::insert(collection_id, admin_arr);789			}790			Ok(())791		}792793		/// # Permissions794		///795		/// * Collection Owner796		///797		/// # Arguments798		///799		/// * collection_id.800		///801		/// * new_sponsor.802		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]803		#[transactional]804		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {805			let sender = ensure_signed(origin)?;806			let mut target_collection = Self::get_collection(collection_id)?;807			Self::check_owner_permissions(&target_collection, &sender)?;808809			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);810			target_collection.save()811		}812813		/// # Permissions814		///815		/// * Sponsor.816		///817		/// # Arguments818		///819		/// * collection_id.820		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]821		#[transactional]822		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {823			let sender = ensure_signed(origin)?;824825			let mut target_collection = Self::get_collection(collection_id)?;826			ensure!(827				target_collection.sponsorship.pending_sponsor() == Some(&sender),828				Error::<T>::ConfirmUnsetSponsorFail829			);830831			target_collection.sponsorship = SponsorshipState::Confirmed(sender);832			target_collection.save()833		}834835		/// Switch back to pay-per-own-transaction model.836		///837		/// # Permissions838		///839		/// * Collection owner.840		///841		/// # Arguments842		///843		/// * collection_id.844		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]845		#[transactional]846		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {847			let sender = ensure_signed(origin)?;848849			let mut target_collection = Self::get_collection(collection_id)?;850			Self::check_owner_permissions(&target_collection, &sender)?;851852			target_collection.sponsorship = SponsorshipState::Disabled;853			target_collection.save()854		}855856		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.857		///858		/// # Permissions859		///860		/// * Collection Owner.861		/// * Collection Admin.862		/// * Anyone if863		///     * White List is enabled, and864		///     * Address is added to white list, and865		///     * MintPermission is enabled (see SetMintPermission method)866		///867		/// # Arguments868		///869		/// * collection_id: ID of the collection.870		///871		/// * owner: Address, initial owner of the NFT.872		///873		/// * data: Token data to store on chain.874		// #[weight =875		// (130_000_000 as Weight)876		// .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))877		// .saturating_add(RocksDbWeight::get().reads(10 as Weight))878		// .saturating_add(RocksDbWeight::get().writes(8 as Weight))]879880		#[weight = <SelfWeightOf<T>>::create_item(data.data_size() as u32)]881		#[transactional]882		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResult {883			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);884			let collection = Self::get_collection(collection_id)?;885886			Self::create_item_internal(&sender, &collection, &owner, data)?;887888			collection.submit_logs()889		}890891		/// This method creates multiple items in a collection created with CreateCollection method.892		///893		/// # Permissions894		///895		/// * Collection Owner.896		/// * Collection Admin.897		/// * Anyone if898		///     * White List is enabled, and899		///     * Address is added to white list, and900		///     * MintPermission is enabled (see SetMintPermission method)901		///902		/// # Arguments903		///904		/// * collection_id: ID of the collection.905		///906		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].907		///908		/// * owner: Address, initial owner of the NFT.909		#[weight = <SelfWeightOf<T>>::create_multiple_items(items_data.len() as u32)]910		#[transactional]911		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResult {912913			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);914			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);915			let collection = Self::get_collection(collection_id)?;916917			Self::create_multiple_items_internal(&sender, &collection, &owner, items_data)?;918919			collection.submit_logs()920		}921922		// TODO! transaction weight923924		/// Set transfers_enabled value for particular collection925		///926		/// # Permissions927		///928		/// * Collection Owner.929		///930		/// # Arguments931		///932		/// * collection_id: ID of the collection.933		///934		/// * value: New flag value.935		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]936		#[transactional]937		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {938939			let sender = ensure_signed(origin)?;940			let mut target_collection = Self::get_collection(collection_id)?;941942			Self::check_owner_permissions(&target_collection, &sender)?;943944			target_collection.transfers_enabled = value;945			target_collection.save()946		}947948		// TODO! transaction weight949		/// Set meta_update_permission value for particular collection950		///951		/// # Permissions952		///953		/// * Collection Owner.954		///955		/// # Arguments956		///957		/// * collection_id: ID of the collection.958		///959		/// * value: New flag value.960		#[weight = <T as Config>::WeightInfo::burn_item()]961		#[transactional]962		pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {963964			let sender = ensure_signed(origin)?;965			let mut target_collection = Self::get_collection(collection_id)?;966967			ensure!(968				target_collection.meta_update_permission != MetaUpdatePermission::None,969				Error::<T>::MetadataFlagFrozen970			);971			Self::check_owner_permissions(&target_collection, &sender)?;972973			target_collection.meta_update_permission = value;974			<CollectionById<T>>::insert(target_collection.id, target_collection.collection);975976			Ok(())977		}978979		/// Destroys a concrete instance of NFT.980		///981		/// # Permissions982		///983		/// * Collection Owner.984		/// * Collection Admin.985		/// * Current NFT Owner.986		///987		/// # Arguments988		///989		/// * collection_id: ID of the collection.990		///991		/// * item_id: ID of NFT to burn.992		#[weight = <SelfWeightOf<T>>::burn_item()]993		#[transactional]994		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {995996			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);997			let target_collection = Self::get_collection(collection_id)?;998999			Self::burn_item_internal(&sender, &target_collection, item_id, value)?;10001001			target_collection.submit_logs()1002		}10031004		/// Change ownership of the token.1005		///1006		/// # Permissions1007		///1008		/// * Collection Owner1009		/// * Collection Admin1010		/// * Current NFT owner1011		///1012		/// # Arguments1013		///1014		/// * recipient: Address of token recipient.1015		///1016		/// * collection_id.1017		///1018		/// * item_id: ID of the item1019		///     * Non-Fungible Mode: Required.1020		///     * Fungible Mode: Ignored.1021		///     * Re-Fungible Mode: Required.1022		///1023		/// * value: Amount to transfer.1024		///     * Non-Fungible Mode: Ignored1025		///     * Fungible Mode: Must specify transferred amount1026		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1027		#[weight = <SelfWeightOf<T>>::transfer()]1028		#[transactional]1029		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {1030			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1031			let collection = Self::get_collection(collection_id)?;10321033			Self::transfer_internal(&sender, &recipient, &collection, item_id, value)?;10341035			collection.submit_logs()1036		}10371038		/// Set, change, or remove approved address to transfer the ownership of the NFT.1039		///1040		/// # Permissions1041		///1042		/// * Collection Owner1043		/// * Collection Admin1044		/// * Current NFT owner1045		///1046		/// # Arguments1047		///1048		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1049		///1050		/// * collection_id.1051		///1052		/// * item_id: ID of the item.1053		#[weight = <SelfWeightOf<T>>::approve()]1054		#[transactional]1055		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {1056			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1057			let collection = Self::get_collection(collection_id)?;10581059			Self::approve_internal(&sender, &spender, &collection, item_id, amount)?;10601061			collection.submit_logs()1062		}10631064		/// 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.1065		///1066		/// # Permissions1067		/// * Collection Owner1068		/// * Collection Admin1069		/// * Current NFT owner1070		/// * Address approved by current NFT owner1071		///1072		/// # Arguments1073		///1074		/// * from: Address that owns token.1075		///1076		/// * recipient: Address of token recipient.1077		///1078		/// * collection_id.1079		///1080		/// * item_id: ID of the item.1081		///1082		/// * value: Amount to transfer.1083		#[weight = <SelfWeightOf<T>>::transfer_from()]1084		#[transactional]1085		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {1086			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1087			let collection = Self::get_collection(collection_id)?;10881089			Self::transfer_from_internal(&sender, &from, &recipient, &collection, item_id, value)?;10901091			collection.submit_logs()1092		}1093		// #[weight = 0]1094		//     // let no_perm_mes = "You do not have permissions to modify this collection";1095		//     // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1096		//     // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1097		//     // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);10981099		//     // // on_nft_received  call11001101		//     // Self::transfer(origin, collection_id, item_id, new_owner)?;11021103		//     Ok(())1104		// }11051106		/// Set off-chain data schema.1107		///1108		/// # Permissions1109		///1110		/// * Collection Owner1111		/// * Collection Admin1112		///1113		/// # Arguments1114		///1115		/// * collection_id.1116		///1117		/// * schema: String representing the offchain data schema.1118		#[weight = <SelfWeightOf<T>>::set_variable_meta_data(data.len() as u32)]1119		#[transactional]1120		pub fn set_variable_meta_data (1121			origin,1122			collection_id: CollectionId,1123			item_id: TokenId,1124			data: Vec<u8>1125		) -> DispatchResult {1126			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);11271128			let collection = Self::get_collection(collection_id)?;11291130			Self::set_variable_meta_data_internal(&sender, &collection, item_id, data)?;11311132			Ok(())1133		}11341135		/// Set schema standard1136		/// ImageURL1137		/// Unique1138		///1139		/// # Permissions1140		///1141		/// * Collection Owner1142		/// * Collection Admin1143		///1144		/// # Arguments1145		///1146		/// * collection_id.1147		///1148		/// * schema: SchemaVersion: enum1149		#[weight = <SelfWeightOf<T>>::set_schema_version()]1150		#[transactional]1151		pub fn set_schema_version(1152			origin,1153			collection_id: CollectionId,1154			version: SchemaVersion1155		) -> DispatchResult {1156			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1157			let mut target_collection = Self::get_collection(collection_id)?;1158			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;1159			target_collection.schema_version = version;1160			target_collection.save()1161		}11621163		/// Set off-chain data schema.1164		///1165		/// # Permissions1166		///1167		/// * Collection Owner1168		/// * Collection Admin1169		///1170		/// # Arguments1171		///1172		/// * collection_id.1173		///1174		/// * schema: String representing the offchain data schema.1175		#[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]1176		#[transactional]1177		pub fn set_offchain_schema(1178			origin,1179			collection_id: CollectionId,1180			schema: Vec<u8>1181		) -> DispatchResult {1182			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1183			let mut target_collection = Self::get_collection(collection_id)?;1184			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11851186			// check schema limit1187			ensure!(schema.len() as u32 <= OFFCHAIN_SCHEMA_LIMIT, "");11881189			target_collection.offchain_schema = schema;1190			target_collection.save()1191		}11921193		/// Set const on-chain data schema.1194		///1195		/// # Permissions1196		///1197		/// * Collection Owner1198		/// * Collection Admin1199		///1200		/// # Arguments1201		///1202		/// * collection_id.1203		///1204		/// * schema: String representing the const on-chain data schema.1205		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1206		#[transactional]1207		pub fn set_const_on_chain_schema (1208			origin,1209			collection_id: CollectionId,1210			schema: Vec<u8>1211		) -> DispatchResult {1212			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1213			let mut target_collection = Self::get_collection(collection_id)?;1214			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;12151216			// check schema limit1217			ensure!(schema.len() as u32 <= CONST_ON_CHAIN_SCHEMA_LIMIT, "");12181219			target_collection.const_on_chain_schema = schema;1220			target_collection.save()1221		}12221223		/// Set variable on-chain data schema.1224		///1225		/// # Permissions1226		///1227		/// * Collection Owner1228		/// * Collection Admin1229		///1230		/// # Arguments1231		///1232		/// * collection_id.1233		///1234		/// * schema: String representing the variable on-chain data schema.1235		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1236		#[transactional]1237		pub fn set_variable_on_chain_schema (1238			origin,1239			collection_id: CollectionId,1240			schema: Vec<u8>1241		) -> DispatchResult {1242			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1243			let mut target_collection = Self::get_collection(collection_id)?;1244			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;12451246			// check schema limit1247			ensure!(schema.len() as u32 <= VARIABLE_ON_CHAIN_SCHEMA_LIMIT, "");12481249			target_collection.variable_on_chain_schema = schema;1250			target_collection.save()1251		}12521253		#[weight = <SelfWeightOf<T>>::set_collection_limits()]1254		#[transactional]1255		pub fn set_collection_limits(1256			origin,1257			collection_id: u32,1258			new_limits: CollectionLimits<T::BlockNumber>,1259		) -> DispatchResult {1260			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1261			let mut target_collection = Self::get_collection(collection_id)?;1262			Self::check_owner_permissions(&target_collection, sender.as_sub())?;1263			let old_limits = &target_collection.limits;12641265			// collection bounds1266			ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1267				new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP &&1268				new_limits.sponsored_data_size <= CUSTOM_DATA_LIMIT,1269				Error::<T>::CollectionLimitBoundsExceeded);12701271			// token_limit   check  prev1272			ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1273			ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);12741275			ensure!(1276				(old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1277				(old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1278				Error::<T>::OwnerPermissionsCantBeReverted,1279			);12801281			target_collection.limits = new_limits;12821283			target_collection.save()1284		}1285	}1286}12871288impl<T: Config> Module<T> {1289	pub fn create_item_internal(1290		sender: &T::CrossAccountId,1291		collection: &CollectionHandle<T>,1292		owner: &T::CrossAccountId,1293		data: CreateItemData,1294	) -> DispatchResult {1295		ensure!(1296			owner != &T::CrossAccountId::from_eth(H160([0; 20])),1297			Error::<T>::AddressIsZero1298		);12991300		Self::can_create_items_in_collection(collection, sender, owner, 1)?;1301		Self::validate_create_item_args(collection, &data)?;1302		Self::create_item_no_validation(collection, owner, data)?;13031304		Ok(())1305	}13061307	pub fn transfer_internal(1308		sender: &T::CrossAccountId,1309		recipient: &T::CrossAccountId,1310		target_collection: &CollectionHandle<T>,1311		item_id: TokenId,1312		value: u128,1313	) -> DispatchResult {1314		ensure!(1315			recipient != &T::CrossAccountId::from_eth(H160([0; 20])),1316			Error::<T>::AddressIsZero1317		);13181319		// Limits check1320		Self::is_correct_transfer(target_collection, recipient)?;13211322		// Transfer permissions check1323		ensure!(1324			Self::is_item_owner(sender, target_collection, item_id)?1325				|| Self::is_owner_or_admin_permissions(target_collection, sender)?,1326			Error::<T>::NoPermission1327		);13281329		if target_collection.access == AccessMode::WhiteList {1330			Self::check_white_list(target_collection, sender)?;1331			Self::check_white_list(target_collection, recipient)?;1332		}13331334		match target_collection.mode {1335			CollectionMode::NFT => Self::transfer_nft(1336				target_collection,1337				item_id,1338				sender.clone(),1339				recipient.clone(),1340			)?,1341			CollectionMode::Fungible(_) => {1342				Self::transfer_fungible(target_collection, value, sender, recipient)?1343			}1344			CollectionMode::ReFungible => Self::transfer_refungible(1345				target_collection,1346				item_id,1347				value,1348				sender.clone(),1349				recipient.clone(),1350			)?,1351			_ => (),1352		};13531354		Self::deposit_event(RawEvent::Transfer(1355			target_collection.id,1356			item_id,1357			sender.clone(),1358			recipient.clone(),1359			value,1360		));13611362		Ok(())1363	}13641365	pub fn approve_internal(1366		sender: &T::CrossAccountId,1367		spender: &T::CrossAccountId,1368		collection: &CollectionHandle<T>,1369		item_id: TokenId,1370		amount: u128,1371	) -> DispatchResult {1372		Self::token_exists(collection, item_id)?;13731374		// Transfer permissions check1375		let bypasses_limits = collection.limits.owner_can_transfer1376			&& Self::is_owner_or_admin_permissions(collection, sender)?;13771378		let allowance_limit = if bypasses_limits {1379			None1380		} else if let Some(amount) = Self::owned_amount(sender, collection, item_id)? {1381			Some(amount)1382		} else {1383			fail!(Error::<T>::NoPermission);1384		};13851386		if collection.access == AccessMode::WhiteList {1387			Self::check_white_list(collection, sender)?;1388			Self::check_white_list(collection, spender)?;1389		}13901391		collection.consume_sload()?;1392		let allowance: u128 = amount1393			.checked_add(<Allowances<T>>::get(1394				collection.id,1395				(item_id, sender.as_sub(), spender.as_sub()),1396			))1397			.ok_or(Error::<T>::NumOverflow)?;1398		if let Some(limit) = allowance_limit {1399			ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1400		}1401		collection.consume_sstore()?;1402		<Allowances<T>>::insert(1403			collection.id,1404			(item_id, sender.as_sub(), spender.as_sub()),1405			allowance,1406		);14071408		if matches!(collection.mode, CollectionMode::NFT) {1409			// TODO: NFT: only one owner may exist for token in ERC7211410			collection.log(ERC721Events::Approval {1411				owner: *sender.as_eth(),1412				approved: *spender.as_eth(),1413				token_id: item_id.into(),1414			})?;1415		}14161417		if matches!(collection.mode, CollectionMode::Fungible(_)) {1418			// TODO: NFT: only one owner may exist for token in ERC201419			collection.log(ERC20Events::Approval {1420				owner: *sender.as_eth(),1421				spender: *spender.as_eth(),1422				value: allowance.into(),1423			})?;1424		}14251426		Self::deposit_event(RawEvent::Approved(1427			collection.id,1428			item_id,1429			sender.clone(),1430			spender.clone(),1431			allowance,1432		));1433		Ok(())1434	}14351436	pub fn transfer_from_internal(1437		sender: &T::CrossAccountId,1438		from: &T::CrossAccountId,1439		recipient: &T::CrossAccountId,1440		collection: &CollectionHandle<T>,1441		item_id: TokenId,1442		amount: u128,1443	) -> DispatchResult {1444		if sender == from {1445			// Transfer by `from`, because it is either equal to sender, or derived from him1446			return Self::transfer_internal(from, recipient, collection, item_id, amount);1447		}14481449		// Check approval1450		collection.consume_sload()?;1451		let approval: u128 =1452			<Allowances<T>>::get(collection.id, (item_id, from.as_sub(), sender.as_sub()));14531454		// Limits check1455		Self::is_correct_transfer(collection, recipient)?;14561457		// Transfer permissions check1458		ensure!(1459			approval >= amount1460				|| (collection.limits.owner_can_transfer1461					&& Self::is_owner_or_admin_permissions(collection, sender)?),1462			Error::<T>::NoPermission1463		);14641465		if collection.access == AccessMode::WhiteList {1466			Self::check_white_list(collection, sender)?;1467			Self::check_white_list(collection, recipient)?;1468		}14691470		// Reduce approval by transferred amount or remove if remaining approval drops to 01471		let allowance = approval.saturating_sub(amount);1472		collection.consume_sstore()?;1473		if allowance > 0 {1474			<Allowances<T>>::insert(1475				collection.id,1476				(item_id, from.as_sub(), sender.as_sub()),1477				allowance,1478			);1479		} else {1480			<Allowances<T>>::remove(collection.id, (item_id, from.as_sub(), sender.as_sub()));1481		}14821483		match collection.mode {1484			CollectionMode::NFT => {1485				Self::transfer_nft(collection, item_id, from.clone(), recipient.clone())?1486			}1487			CollectionMode::Fungible(_) => {1488				Self::transfer_fungible(collection, amount, from, recipient)?1489			}1490			CollectionMode::ReFungible => Self::transfer_refungible(1491				collection,1492				item_id,1493				amount,1494				from.clone(),1495				recipient.clone(),1496			)?,1497			_ => (),1498		};14991500		if matches!(collection.mode, CollectionMode::Fungible(_)) {1501			collection.log(ERC20Events::Approval {1502				owner: *from.as_eth(),1503				spender: *sender.as_eth(),1504				value: allowance.into(),1505			})?;1506		}15071508		Ok(())1509	}15101511	pub fn set_variable_meta_data_internal(1512		sender: &T::CrossAccountId,1513		collection: &CollectionHandle<T>,1514		item_id: TokenId,1515		data: Vec<u8>,1516	) -> DispatchResult {1517		Self::token_exists(collection, item_id)?;15181519		ensure!(1520			CUSTOM_DATA_LIMIT >= data.len() as u32,1521			Error::<T>::TokenVariableDataLimitExceeded1522		);15231524		// Modify permissions check1525		ensure!(1526			Self::is_item_owner(sender, collection, item_id)?1527				|| Self::is_owner_or_admin_permissions(collection, sender)?,1528			Error::<T>::NoPermission1529		);15301531		match collection.mode {1532			CollectionMode::NFT => Self::set_nft_variable_data(collection, item_id, data)?,1533			CollectionMode::ReFungible => {1534				Self::set_re_fungible_variable_data(collection, item_id, data)?1535			}1536			CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1537			_ => fail!(Error::<T>::UnexpectedCollectionType),1538		};15391540		Ok(())1541	}15421543	pub fn meta_update_check(1544		sender: &T::CrossAccountId,1545		collection: &CollectionHandle<T>,1546		item_id: TokenId,1547	) -> DispatchResult {1548		match collection.meta_update_permission {1549			MetaUpdatePermission::ItemOwner => ensure!(1550				Self::is_item_owner(sender, collection, item_id)?,1551				Error::<T>::NoPermission1552			),1553			MetaUpdatePermission::Admin => ensure!(1554				Self::is_owner_or_admin_permissions(collection, sender)?,1555				Error::<T>::NoPermission1556			),1557			MetaUpdatePermission::None => fail!(Error::<T>::MetadataUpdateDenied),1558		}15591560		Ok(())1561	}15621563	pub fn create_multiple_items_internal(1564		sender: &T::CrossAccountId,1565		collection: &CollectionHandle<T>,1566		owner: &T::CrossAccountId,1567		items_data: Vec<CreateItemData>,1568	) -> DispatchResult {1569		Self::can_create_items_in_collection(collection, sender, owner, items_data.len() as u32)?;15701571		for data in &items_data {1572			Self::validate_create_item_args(collection, data)?;1573		}1574		for data in &items_data {1575			Self::create_item_no_validation(collection, owner, data.clone())?;1576		}15771578		Ok(())1579	}15801581	pub fn burn_item_internal(1582		sender: &T::CrossAccountId,1583		collection: &CollectionHandle<T>,1584		item_id: TokenId,1585		value: u128,1586	) -> DispatchResult {1587		ensure!(1588			Self::is_item_owner(sender, collection, item_id)?1589				|| (collection.limits.owner_can_transfer1590					&& Self::is_owner_or_admin_permissions(collection, sender)?),1591			Error::<T>::NoPermission1592		);15931594		if collection.access == AccessMode::WhiteList {1595			Self::check_white_list(collection, sender)?;1596		}15971598		match collection.mode {1599			CollectionMode::NFT => Self::burn_nft_item(collection, item_id)?,1600			CollectionMode::Fungible(_) => Self::burn_fungible_item(sender, collection, value)?,1601			CollectionMode::ReFungible => Self::burn_refungible_item(collection, item_id, sender)?,1602			_ => (),1603		};16041605		Ok(())1606	}16071608	pub fn toggle_white_list_internal(1609		sender: &T::CrossAccountId,1610		collection: &CollectionHandle<T>,1611		address: &T::CrossAccountId,1612		whitelisted: bool,1613	) -> DispatchResult {1614		Self::check_owner_or_admin_permissions(collection, sender)?;16151616		if whitelisted {1617			<WhiteList<T>>::insert(collection.id, address.as_sub(), true);1618		} else {1619			<WhiteList<T>>::remove(collection.id, address.as_sub());1620		}16211622		Ok(())1623	}16241625	fn is_correct_transfer(1626		collection: &CollectionHandle<T>,1627		recipient: &T::CrossAccountId,1628	) -> DispatchResult {1629		let collection_id = collection.id;16301631		// check token limit and account token limit1632		collection.consume_sload()?;1633		let account_items: u32 =1634			<AddressTokens<T>>::get(collection_id, recipient.as_sub()).len() as u32;1635		ensure!(1636			collection.limits.account_token_ownership_limit > account_items,1637			Error::<T>::AccountTokenLimitExceeded1638		);16391640		// preliminary transfer check1641		ensure!(collection.transfers_enabled, Error::<T>::TransferNotAllowed);16421643		Ok(())1644	}16451646	fn can_create_items_in_collection(1647		collection: &CollectionHandle<T>,1648		sender: &T::CrossAccountId,1649		owner: &T::CrossAccountId,1650		amount: u32,1651	) -> DispatchResult {1652		let collection_id = collection.id;16531654		// check token limit and account token limit1655		let total_items: u32 = ItemListIndex::get(collection_id)1656			.checked_add(amount)1657			.ok_or(Error::<T>::CollectionTokenLimitExceeded)?;1658		let account_items: u32 = (<AddressTokens<T>>::get(collection_id, owner.as_sub()).len()1659			as u32)1660			.checked_add(amount)1661			.ok_or(Error::<T>::AccountTokenLimitExceeded)?;1662		ensure!(1663			collection.limits.token_limit >= total_items,1664			Error::<T>::CollectionTokenLimitExceeded1665		);1666		ensure!(1667			collection.limits.account_token_ownership_limit >= account_items,1668			Error::<T>::AccountTokenLimitExceeded1669		);16701671		if !Self::is_owner_or_admin_permissions(collection, sender)? {1672			ensure!(collection.mint_mode, Error::<T>::PublicMintingNotAllowed);1673			Self::check_white_list(collection, owner)?;1674			Self::check_white_list(collection, sender)?;1675		}16761677		Ok(())1678	}16791680	fn validate_create_item_args(1681		target_collection: &CollectionHandle<T>,1682		data: &CreateItemData,1683	) -> DispatchResult {1684		match target_collection.mode {1685			CollectionMode::NFT => {1686				if !matches!(data, CreateItemData::NFT(_)) {1687					fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1688				}1689			}1690			CollectionMode::Fungible(_) => {1691				if !matches!(data, CreateItemData::Fungible(_)) {1692					fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1693				}1694			}1695			CollectionMode::ReFungible => {1696				if let CreateItemData::ReFungible(data) = data {1697					// Check refungibility limits1698					ensure!(1699						data.pieces <= MAX_REFUNGIBLE_PIECES,1700						Error::<T>::WrongRefungiblePieces1701					);1702					ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1703				} else {1704					fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1705				}1706			}1707			_ => {1708				fail!(Error::<T>::UnexpectedCollectionType);1709			}1710		};17111712		Ok(())1713	}17141715	fn create_item_no_validation(1716		collection: &CollectionHandle<T>,1717		owner: &T::CrossAccountId,1718		data: CreateItemData,1719	) -> DispatchResult {1720		match data {1721			CreateItemData::NFT(data) => {1722				let item = NftItemType {1723					owner: owner.clone(),1724					const_data: data.const_data.into_inner(),1725					variable_data: data.variable_data.into_inner(),1726				};17271728				Self::add_nft_item(collection, item)?;1729			}1730			CreateItemData::Fungible(data) => {1731				Self::add_fungible_item(collection, owner, data.value)?;1732			}1733			CreateItemData::ReFungible(data) => {1734				let owner_list = vec![Ownership {1735					owner: owner.clone(),1736					fraction: data.pieces,1737				}];17381739				let item = ReFungibleItemType {1740					owner: owner_list,1741					const_data: data.const_data.into_inner(),1742					variable_data: data.variable_data.into_inner(),1743				};17441745				Self::add_refungible_item(collection, item)?;1746			}1747		};17481749		Ok(())1750	}17511752	fn add_fungible_item(1753		collection: &CollectionHandle<T>,1754		owner: &T::CrossAccountId,1755		value: u128,1756	) -> DispatchResult {1757		let collection_id = collection.id;17581759		// Does new owner already have an account?1760		collection.consume_sload()?;1761		let balance: u128 = <FungibleItemList<T>>::get(collection_id, owner.as_sub()).value;17621763		// Mint1764		let item = FungibleItemType {1765			value: balance.checked_add(value).ok_or(Error::<T>::NumOverflow)?,1766		};1767		collection.consume_sstore()?;1768		<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), item);17691770		// Update balance1771		collection.consume_sload()?;1772		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1773			.checked_add(value)1774			.ok_or(Error::<T>::NumOverflow)?;1775		collection.consume_sstore()?;1776		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);17771778		collection.log(ERC20Events::Transfer {1779			from: H160::default(),1780			to: *owner.as_eth(),1781			value: value.into(),1782		})?;1783		Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));1784		Ok(())1785	}17861787	fn add_refungible_item(1788		collection: &CollectionHandle<T>,1789		item: ReFungibleItemType<T::CrossAccountId>,1790	) -> DispatchResult {1791		let collection_id = collection.id;17921793		let current_index = <ItemListIndex>::get(collection_id)1794			.checked_add(1)1795			.ok_or(Error::<T>::NumOverflow)?;1796		let itemcopy = item.clone();17971798		ensure!(item.owner.len() == 1, Error::<T>::BadCreateRefungibleCall,);1799		let item_owner = item.owner.first().expect("only one owner is defined");18001801		let value = item_owner.fraction;1802		let owner = item_owner.owner.clone();18031804		Self::add_token_index(collection, current_index, &owner)?;18051806		<ItemListIndex>::insert(collection_id, current_index);1807		<ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);18081809		// Update balance1810		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1811			.checked_add(value)1812			.ok_or(Error::<T>::NumOverflow)?;1813		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);18141815		Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));1816		Ok(())1817	}18181819	fn add_nft_item(1820		collection: &CollectionHandle<T>,1821		item: NftItemType<T::CrossAccountId>,1822	) -> DispatchResult {1823		let collection_id = collection.id;18241825		let current_index = <ItemListIndex>::get(collection_id)1826			.checked_add(1)1827			.ok_or(Error::<T>::NumOverflow)?;18281829		let item_owner = item.owner.clone();1830		Self::add_token_index(collection, current_index, &item.owner)?;18311832		<ItemListIndex>::insert(collection_id, current_index);1833		<NftItemList<T>>::insert(collection_id, current_index, item);18341835		// Update balance1836		let new_balance = <Balance<T>>::get(collection_id, item_owner.as_sub())1837			.checked_add(1)1838			.ok_or(Error::<T>::NumOverflow)?;1839		<Balance<T>>::insert(collection_id, item_owner.as_sub(), new_balance);18401841		collection.log(ERC721Events::Transfer {1842			from: H160::default(),1843			to: *item_owner.as_eth(),1844			token_id: current_index.into(),1845		})?;1846		Self::deposit_event(RawEvent::ItemCreated(1847			collection_id,1848			current_index,1849			item_owner,1850		));1851		Ok(())1852	}18531854	fn burn_refungible_item(1855		collection: &CollectionHandle<T>,1856		item_id: TokenId,1857		owner: &T::CrossAccountId,1858	) -> DispatchResult {1859		let collection_id = collection.id;18601861		let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id)1862			.ok_or(Error::<T>::TokenNotFound)?;1863		let rft_balance = token1864			.owner1865			.iter()1866			.find(|&i| i.owner == *owner)1867			.ok_or(Error::<T>::TokenNotFound)?;1868		Self::remove_token_index(collection, item_id, owner)?;18691870		// update balance1871		let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.as_sub())1872			.checked_sub(rft_balance.fraction)1873			.ok_or(Error::<T>::NumOverflow)?;1874		<Balance<T>>::insert(collection_id, rft_balance.owner.as_sub(), new_balance);18751876		// Re-create owners list with sender removed1877		let index = token1878			.owner1879			.iter()1880			.position(|i| i.owner == *owner)1881			.expect("owned item is exists");1882		token.owner.remove(index);1883		let owner_count = token.owner.len();18841885		// Burn the token completely if this was the last (only) owner1886		if owner_count == 0 {1887			<ReFungibleItemList<T>>::remove(collection_id, item_id);1888			<VariableMetaDataBasket<T>>::remove(collection_id, item_id);1889		} else {1890			<ReFungibleItemList<T>>::insert(collection_id, item_id, token);1891		}18921893		Ok(())1894	}18951896	fn burn_nft_item(collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1897		let collection_id = collection.id;18981899		let item =1900			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;1901		Self::remove_token_index(collection, item_id, &item.owner)?;19021903		// update balance1904		let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())1905			.checked_sub(1)1906			.ok_or(Error::<T>::NumOverflow)?;1907		<Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);1908		<NftItemList<T>>::remove(collection_id, item_id);1909		<VariableMetaDataBasket<T>>::remove(collection_id, item_id);19101911		collection.log(ERC721Events::Transfer {1912			from: *item.owner.as_eth(),1913			to: H160::default(),1914			token_id: item_id.into(),1915		})?;1916		Self::deposit_event(RawEvent::ItemDestroyed(collection.id, item_id));1917		Ok(())1918	}19191920	fn burn_fungible_item(1921		owner: &T::CrossAccountId,1922		collection: &CollectionHandle<T>,1923		value: u128,1924	) -> DispatchResult {1925		let collection_id = collection.id;19261927		let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());1928		ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);19291930		// update balance1931		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1932			.checked_sub(value)1933			.ok_or(Error::<T>::NumOverflow)?;1934		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);19351936		if balance.value - value > 0 {1937			balance.value -= value;1938			<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);1939		} else {1940			<FungibleItemList<T>>::remove(collection_id, owner.as_sub());1941		}19421943		collection.log(ERC20Events::Transfer {1944			from: *owner.as_eth(),1945			to: H160::default(),1946			value: value.into(),1947		})?;1948		Ok(())1949	}19501951	pub fn get_collection(1952		collection_id: CollectionId,1953	) -> Result<CollectionHandle<T>, sp_runtime::DispatchError> {1954		Ok(<CollectionHandle<T>>::get(collection_id).ok_or(Error::<T>::CollectionNotFound)?)1955	}19561957	fn check_owner_permissions(1958		target_collection: &CollectionHandle<T>,1959		subject: &T::AccountId,1960	) -> DispatchResult {1961		ensure!(1962			*subject == target_collection.owner,1963			Error::<T>::NoPermission1964		);19651966		Ok(())1967	}19681969	fn is_owner_or_admin_permissions(1970		collection: &CollectionHandle<T>,1971		subject: &T::CrossAccountId,1972	) -> Result<bool, DispatchError> {1973		collection.consume_sload()?;1974		Ok(*subject.as_sub() == collection.owner1975			|| <AdminList<T>>::get(collection.id).contains(subject))1976	}19771978	fn check_owner_or_admin_permissions(1979		collection: &CollectionHandle<T>,1980		subject: &T::CrossAccountId,1981	) -> DispatchResult {1982		ensure!(1983			Self::is_owner_or_admin_permissions(collection, subject)?,1984			Error::<T>::NoPermission1985		);19861987		Ok(())1988	}19891990	fn owned_amount(1991		subject: &T::CrossAccountId,1992		collection: &CollectionHandle<T>,1993		item_id: TokenId,1994	) -> Result<Option<u128>, DispatchError> {1995		collection.consume_sload()?;1996		Ok(Self::owned_amount_unchecked(subject, collection, item_id))1997	}19981999	fn owned_amount_unchecked(2000		subject: &T::CrossAccountId,2001		target_collection: &CollectionHandle<T>,2002		item_id: TokenId,2003	) -> Option<u128> {2004		let collection_id = target_collection.id;20052006		match target_collection.mode {2007			CollectionMode::NFT => {2008				(<NftItemList<T>>::get(collection_id, item_id)?.owner == *subject).then(|| 1)2009			}2010			CollectionMode::Fungible(_) => {2011				Some(<FungibleItemList<T>>::get(collection_id, &subject.as_sub()).value)2012			}2013			CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)?2014				.owner2015				.iter()2016				.find(|i| i.owner == *subject)2017				.map(|i| i.fraction),2018			CollectionMode::Invalid => None,2019		}2020	}20212022	fn is_item_owner(2023		subject: &T::CrossAccountId,2024		target_collection: &CollectionHandle<T>,2025		item_id: TokenId,2026	) -> Result<bool, DispatchError> {2027		Ok(match target_collection.mode {2028			CollectionMode::Fungible(_) => true,2029			_ => Self::owned_amount(subject, target_collection, item_id)?.is_some(),2030		})2031	}20322033	fn check_white_list(2034		collection: &CollectionHandle<T>,2035		address: &T::CrossAccountId,2036	) -> DispatchResult {2037		collection.consume_sload()?;2038		ensure!(2039			<WhiteList<T>>::contains_key(collection.id, address.as_sub()),2040			Error::<T>::AddresNotInWhiteList,2041		);2042		Ok(())2043	}20442045	/// Check if token exists. In case of Fungible, check if there is an entry for2046	/// the owner in fungible balances double map2047	fn token_exists(target_collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {2048		let collection_id = target_collection.id;2049		let exists = match target_collection.mode {2050			CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),2051			CollectionMode::Fungible(_) => true,2052			CollectionMode::ReFungible => {2053				<ReFungibleItemList<T>>::contains_key(collection_id, item_id)2054			}2055			_ => false,2056		};20572058		ensure!(exists, Error::<T>::TokenNotFound);2059		Ok(())2060	}20612062	fn transfer_fungible(2063		collection: &CollectionHandle<T>,2064		value: u128,2065		owner: &T::CrossAccountId,2066		recipient: &T::CrossAccountId,2067	) -> DispatchResult {2068		let collection_id = collection.id;20692070		collection.consume_sload()?;2071		collection.consume_sload()?;2072		let mut recipient_balance = <FungibleItemList<T>>::get(collection_id, recipient.as_sub());2073		let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());20742075		recipient_balance.value = recipient_balance2076			.value2077			.checked_add(value)2078			.ok_or(Error::<T>::NumOverflow)?;2079		balance.value = balance2080			.value2081			.checked_sub(value)2082			.ok_or(Error::<T>::TokenValueTooLow)?;20832084		// update balanceOf2085		collection.consume_sstore()?;2086		collection.consume_sstore()?;2087		if balance.value != 0 {2088			<Balance<T>>::insert(collection_id, owner.as_sub(), balance.value);2089		} else {2090			<Balance<T>>::remove(collection_id, owner.as_sub());2091		}2092		<Balance<T>>::insert(collection_id, recipient.as_sub(), recipient_balance.value);20932094		// Reduce or remove sender2095		collection.consume_sstore()?;2096		collection.consume_sstore()?;2097		if balance.value != 0 {2098			<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);2099		} else {2100			<FungibleItemList<T>>::remove(collection_id, owner.as_sub());2101		}2102		<FungibleItemList<T>>::insert(collection_id, recipient.as_sub(), recipient_balance);21032104		collection.log(ERC20Events::Transfer {2105			from: *owner.as_eth(),2106			to: *recipient.as_eth(),2107			value: value.into(),2108		})?;2109		Self::deposit_event(RawEvent::Transfer(2110			collection.id,2111			1,2112			owner.clone(),2113			recipient.clone(),2114			value,2115		));21162117		Ok(())2118	}21192120	fn transfer_refungible(2121		collection: &CollectionHandle<T>,2122		item_id: TokenId,2123		value: u128,2124		owner: T::CrossAccountId,2125		new_owner: T::CrossAccountId,2126	) -> DispatchResult {2127		let collection_id = collection.id;2128		collection.consume_sload()?;2129		let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id)2130			.ok_or(Error::<T>::TokenNotFound)?;21312132		let item = full_item2133			.owner2134			.iter()2135			.find(|i| i.owner == owner)2136			.ok_or(Error::<T>::TokenNotFound)?;2137		let amount = item.fraction;21382139		ensure!(amount >= value, Error::<T>::TokenValueTooLow);21402141		collection.consume_sload()?;2142		// update balance2143		let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2144			.checked_sub(value)2145			.ok_or(Error::<T>::NumOverflow)?;2146		collection.consume_sstore()?;2147		<Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);21482149		collection.consume_sload()?;2150		let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2151			.checked_add(value)2152			.ok_or(Error::<T>::NumOverflow)?;2153		collection.consume_sstore()?;2154		<Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);21552156		let old_owner = item.owner.clone();2157		let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);21582159		let mut new_full_item = full_item.clone();2160		// transfer2161		if amount == value && !new_owner_has_account {2162			// change owner2163			// new owner do not have account2164			new_full_item2165				.owner2166				.iter_mut()2167				.find(|i| i.owner == owner)2168				.expect("old owner does present in refungible")2169				.owner = new_owner.clone();2170			collection.consume_sstore()?;2171			<ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);21722173			// update index collection2174			Self::move_token_index(collection, item_id, &old_owner, &new_owner)?;2175		} else {2176			new_full_item2177				.owner2178				.iter_mut()2179				.find(|i| i.owner == owner)2180				.expect("old owner does present in refungible")2181				.fraction -= value;21822183			// separate amount2184			if new_owner_has_account {2185				// new owner has account2186				new_full_item2187					.owner2188					.iter_mut()2189					.find(|i| i.owner == new_owner)2190					.expect("new owner has account")2191					.fraction += value;2192			} else {2193				// new owner do not have account2194				new_full_item.owner.push(Ownership {2195					owner: new_owner.clone(),2196					fraction: value,2197				});2198				Self::add_token_index(collection, item_id, &new_owner)?;2199			}22002201			collection.consume_sstore()?;2202			<ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2203		}22042205		Self::deposit_event(RawEvent::Transfer(2206			collection.id,2207			item_id,2208			owner,2209			new_owner,2210			amount,2211		));22122213		Ok(())2214	}22152216	fn transfer_nft(2217		collection: &CollectionHandle<T>,2218		item_id: TokenId,2219		sender: T::CrossAccountId,2220		new_owner: T::CrossAccountId,2221	) -> DispatchResult {2222		let collection_id = collection.id;2223		collection.consume_sload()?;2224		let mut item =2225			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;22262227		ensure!(sender == item.owner, Error::<T>::MustBeTokenOwner);22282229		collection.consume_sload()?;2230		// update balance2231		let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2232			.checked_sub(1)2233			.ok_or(Error::<T>::NumOverflow)?;2234		collection.consume_sstore()?;2235		<Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);22362237		collection.consume_sload()?;2238		let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2239			.checked_add(1)2240			.ok_or(Error::<T>::NumOverflow)?;2241		collection.consume_sstore()?;2242		<Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);22432244		// change owner2245		let old_owner = item.owner.clone();2246		item.owner = new_owner.clone();2247		collection.consume_sstore()?;2248		<NftItemList<T>>::insert(collection_id, item_id, item);22492250		// update index collection2251		Self::move_token_index(collection, item_id, &old_owner, &new_owner)?;22522253		collection.log(ERC721Events::Transfer {2254			from: *sender.as_eth(),2255			to: *new_owner.as_eth(),2256			token_id: item_id.into(),2257		})?;2258		Self::deposit_event(RawEvent::Transfer(2259			collection.id,2260			item_id,2261			sender,2262			new_owner,2263			1,2264		));22652266		Ok(())2267	}22682269	fn set_re_fungible_variable_data(2270		collection: &CollectionHandle<T>,2271		item_id: TokenId,2272		data: Vec<u8>,2273	) -> DispatchResult {2274		let collection_id = collection.id;2275		let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id)2276			.ok_or(Error::<T>::TokenNotFound)?;22772278		item.variable_data = data;22792280		<ReFungibleItemList<T>>::insert(collection_id, item_id, item);22812282		Ok(())2283	}22842285	fn set_nft_variable_data(2286		collection: &CollectionHandle<T>,2287		item_id: TokenId,2288		data: Vec<u8>,2289	) -> DispatchResult {2290		let collection_id = collection.id;2291		let mut item =2292			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;22932294		item.variable_data = data;22952296		<NftItemList<T>>::insert(collection_id, item_id, item);22972298		Ok(())2299	}23002301	#[allow(dead_code)]2302	fn init_collection(item: &Collection<T>) {2303		// check params2304		assert!(2305			item.decimal_points <= MAX_DECIMAL_POINTS,2306			"decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2307		);2308		assert!(2309			item.name.len() <= 64,2310			"Collection name can not be longer than 63 char"2311		);2312		assert!(2313			item.name.len() <= 256,2314			"Collection description can not be longer than 255 char"2315		);2316		assert!(2317			item.token_prefix.len() <= 16,2318			"Token prefix can not be longer than 15 char"2319		);23202321		// Generate next collection ID2322		let next_id = CreatedCollectionCount::get().checked_add(1).unwrap();23232324		CreatedCollectionCount::put(next_id);2325	}23262327	#[allow(dead_code)]2328	fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::CrossAccountId>) {2329		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();23302331		Self::add_token_index(2332			&CollectionHandle::get(collection_id).unwrap(),2333			current_index,2334			&item.owner,2335		)2336		.unwrap();23372338		<ItemListIndex>::insert(collection_id, current_index);23392340		// Update balance2341		let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2342			.checked_add(1)2343			.unwrap();2344		<Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2345	}23462347	#[allow(dead_code)]2348	fn init_fungible_token(2349		collection_id: CollectionId,2350		owner: &T::CrossAccountId,2351		item: &FungibleItemType,2352	) {2353		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();23542355		Self::add_token_index(2356			&CollectionHandle::get(collection_id).unwrap(),2357			current_index,2358			owner,2359		)2360		.unwrap();23612362		<ItemListIndex>::insert(collection_id, current_index);23632364		// Update balance2365		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2366			.checked_add(item.value)2367			.unwrap();2368		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2369	}23702371	#[allow(dead_code)]2372	fn init_refungible_token(2373		collection_id: CollectionId,2374		item: &ReFungibleItemType<T::CrossAccountId>,2375	) {2376		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();23772378		let value = item.owner.first().unwrap().fraction;2379		let owner = item.owner.first().unwrap().owner.clone();23802381		Self::add_token_index(2382			&CollectionHandle::get(collection_id).unwrap(),2383			current_index,2384			&owner,2385		)2386		.unwrap();23872388		<ItemListIndex>::insert(collection_id, current_index);23892390		// Update balance2391		let new_balance = <Balance<T>>::get(collection_id, &owner.as_sub())2392			.checked_add(value)2393			.unwrap();2394		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2395	}23962397	fn add_token_index(2398		collection: &CollectionHandle<T>,2399		item_index: TokenId,2400		owner: &T::CrossAccountId,2401	) -> DispatchResult {2402		// add to account limit2403		collection.consume_sload()?;2404		if <AccountItemCount<T>>::contains_key(owner.as_sub()) {2405			// bound Owned tokens by a single address2406			collection.consume_sload()?;2407			let count = <AccountItemCount<T>>::get(owner.as_sub());2408			ensure!(2409				count < ACCOUNT_TOKEN_OWNERSHIP_LIMIT,2410				Error::<T>::AddressOwnershipLimitExceeded2411			);24122413			collection.consume_sstore()?;2414			<AccountItemCount<T>>::insert(2415				owner.as_sub(),2416				count.checked_add(1).ok_or(Error::<T>::NumOverflow)?,2417			);2418		} else {2419			collection.consume_sstore()?;2420			<AccountItemCount<T>>::insert(owner.as_sub(), 1);2421		}24222423		collection.consume_sload()?;2424		let list_exists = <AddressTokens<T>>::contains_key(collection.id, owner.as_sub());2425		if list_exists {2426			collection.consume_sload()?;2427			let mut list = <AddressTokens<T>>::get(collection.id, owner.as_sub());2428			let item_contains = list.contains(&item_index.clone());24292430			if !item_contains {2431				list.push(item_index);2432			}24332434			collection.consume_sstore()?;2435			<AddressTokens<T>>::insert(collection.id, owner.as_sub(), list);2436		} else {2437			let itm = vec![item_index];2438			collection.consume_sstore()?;2439			<AddressTokens<T>>::insert(collection.id, owner.as_sub(), itm);2440		}24412442		Ok(())2443	}24442445	fn remove_token_index(2446		collection: &CollectionHandle<T>,2447		item_index: TokenId,2448		owner: &T::CrossAccountId,2449	) -> DispatchResult {2450		// update counter2451		collection.consume_sload()?;2452		collection.consume_sstore()?;2453		<AccountItemCount<T>>::insert(2454			owner.as_sub(),2455			<AccountItemCount<T>>::get(owner.as_sub())2456				.checked_sub(1)2457				.ok_or(Error::<T>::NumOverflow)?,2458		);24592460		collection.consume_sload()?;2461		let list_exists = <AddressTokens<T>>::contains_key(collection.id, owner.as_sub());2462		if list_exists {2463			collection.consume_sload()?;2464			let mut list = <AddressTokens<T>>::get(collection.id, owner.as_sub());2465			let item_contains = list.contains(&item_index.clone());24662467			if item_contains {2468				list.retain(|&item| item != item_index);2469				collection.consume_sstore()?;2470				<AddressTokens<T>>::insert(collection.id, owner.as_sub(), list);2471			}2472		}24732474		Ok(())2475	}24762477	fn move_token_index(2478		collection: &CollectionHandle<T>,2479		item_index: TokenId,2480		old_owner: &T::CrossAccountId,2481		new_owner: &T::CrossAccountId,2482	) -> DispatchResult {2483		Self::remove_token_index(collection, item_index, old_owner)?;2484		Self::add_token_index(collection, item_index, new_owner)?;24852486		Ok(())2487	}2488}24892490sp_api::decl_runtime_apis! {2491	pub trait NftApi {2492		/// Used for ethereum integration2493		fn eth_contract_code(account: H160) -> Option<Vec<u8>>;2494	}2495}