git.delta.rocks / unique-network / refs/commits / 5192a06a09ff

difftreelog

Merge pull request #186 from UniqueNetwork/test/evm-marketplace

kozyrevdev2021-08-31parents: #8e4698b #51d879b.patch.diff
in: master
EVM matcher contract test

13 files changed

modifiedcrates/evm-coder/tests/a.rsdiffbeforeafterboth
--- a/crates/evm-coder/tests/a.rs
+++ b/crates/evm-coder/tests/a.rs
@@ -2,6 +2,7 @@
 
 use evm_coder::{solidity_interface, types::*, ToLog, execution::Result};
 use evm_coder_macros::solidity;
+use std as sp_std;
 
 struct Impls;
 
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};35use sp_core::H160;36use sp_std::vec;37use sp_runtime::{DispatchError, sp_std::prelude::Vec};38use core::ops::{Deref, DerefMut};39use nft_data_structs::{40	MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, MAX_REFUNGIBLE_PIECES,41	CUSTOM_DATA_LIMIT, COLLECTION_NUMBER_LIMIT, ACCOUNT_TOKEN_OWNERSHIP_LIMIT,42	VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, COLLECTION_ADMINS_LIMIT,43	OFFCHAIN_SCHEMA_LIMIT, AccessMode, Collection, CreateItemData, CollectionLimits, CollectionId,44	CollectionMode, TokenId, SchemaVersion, SponsorshipState, Ownership, NftItemType,45	FungibleItemType, ReFungibleItemType,46};4748#[cfg(test)]49mod mock;5051#[cfg(test)]52mod tests;5354mod default_weights;55mod eth;56mod sponsorship;57pub use sponsorship::NftSponsorshipHandler;58pub use eth::sponsoring::NftEthSponsorshipHandler;5960pub use eth::NftErcSupport;61pub use eth::account::*;62use eth::erc::{ERC20Events, ERC721Events};6364#[cfg(feature = "runtime-benchmarks")]65mod benchmarking;6667pub trait WeightInfo {68	fn create_collection() -> Weight;69	fn destroy_collection() -> Weight;70	fn add_to_white_list() -> Weight;71	fn remove_from_white_list() -> Weight;72	fn set_public_access_mode() -> Weight;73	fn set_mint_permission() -> Weight;74	fn change_collection_owner() -> Weight;75	fn add_collection_admin() -> Weight;76	fn remove_collection_admin() -> Weight;77	fn set_collection_sponsor() -> Weight;78	fn confirm_sponsorship() -> Weight;79	fn remove_collection_sponsor() -> Weight;80	fn create_item(s: usize) -> Weight;81	fn burn_item() -> Weight;82	fn transfer() -> Weight;83	fn approve() -> Weight;84	fn transfer_from() -> Weight;85	fn set_offchain_schema() -> Weight;86	fn set_const_on_chain_schema() -> Weight;87	fn set_variable_on_chain_schema() -> Weight;88	fn set_variable_meta_data() -> Weight;89	fn enable_contract_sponsoring() -> Weight;90	fn set_schema_version() -> Weight;91	fn set_contract_sponsoring_rate_limit() -> Weight;92	fn set_variable_meta_data_sponsoring_rate_limit() -> Weight;93	fn toggle_contract_white_list() -> Weight;94	fn add_to_contract_white_list() -> Weight;95	fn remove_from_contract_white_list() -> Weight;96	fn set_collection_limits() -> Weight;97}9899decl_error! {100	/// Error for non-fungible-token module.101	pub enum Error for Module<T: Config> {102		/// Total collections bound exceeded.103		TotalCollectionsLimitExceeded,104		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.105		CollectionDecimalPointLimitExceeded,106		/// Collection name can not be longer than 63 char.107		CollectionNameLimitExceeded,108		/// Collection description can not be longer than 255 char.109		CollectionDescriptionLimitExceeded,110		/// Token prefix can not be longer than 15 char.111		CollectionTokenPrefixLimitExceeded,112		/// This collection does not exist.113		CollectionNotFound,114		/// Item not exists.115		TokenNotFound,116		/// Admin not found117		AdminNotFound,118		/// Arithmetic calculation overflow.119		NumOverflow,120		/// Account already has admin role.121		AlreadyAdmin,122		/// You do not own this collection.123		NoPermission,124		/// This address is not set as sponsor, use setCollectionSponsor first.125		ConfirmUnsetSponsorFail,126		/// Collection is not in mint mode.127		PublicMintingNotAllowed,128		/// Sender parameter and item owner must be equal.129		MustBeTokenOwner,130		/// Item balance not enough.131		TokenValueTooLow,132		/// Size of item is too large.133		NftSizeLimitExceeded,134		/// No approve found135		ApproveNotFound,136		/// Requested value more than approved.137		TokenValueNotEnough,138		/// Only approved addresses can call this method.139		ApproveRequired,140		/// Address is not in white list.141		AddresNotInWhiteList,142		/// Number of collection admins bound exceeded.143		CollectionAdminsLimitExceeded,144		/// Owned tokens by a single address bound exceeded.145		AddressOwnershipLimitExceeded,146		/// Length of items properties must be greater than 0.147		EmptyArgument,148		/// const_data exceeded data limit.149		TokenConstDataLimitExceeded,150		/// variable_data exceeded data limit.151		TokenVariableDataLimitExceeded,152		/// Not NFT item data used to mint in NFT collection.153		NotNftDataUsedToMintNftCollectionToken,154		/// Not Fungible item data used to mint in Fungible collection.155		NotFungibleDataUsedToMintFungibleCollectionToken,156		/// Not Re Fungible item data used to mint in Re Fungible collection.157		NotReFungibleDataUsedToMintReFungibleCollectionToken,158		/// Unexpected collection type.159		UnexpectedCollectionType,160		/// Can't store metadata in fungible tokens.161		CantStoreMetadataInFungibleTokens,162		/// Collection token limit exceeded163		CollectionTokenLimitExceeded,164		/// Account token limit exceeded per collection165		AccountTokenLimitExceeded,166		/// Collection limit bounds per collection exceeded167		CollectionLimitBoundsExceeded,168		/// Tried to enable permissions which are only permitted to be disabled169		OwnerPermissionsCantBeReverted,170		/// Schema data size limit bound exceeded171		SchemaDataLimitExceeded,172		/// Maximum refungibility exceeded173		WrongRefungiblePieces,174		/// createRefungible should be called with one owner175		BadCreateRefungibleCall,176		/// Gas limit exceeded177		OutOfGas,178		/// Collection settings not allowing items transferring179		TransferNotAllowed,180		/// Can't transfer tokens to ethereum zero address181		AddressIsZero,182	}183}184185#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]186pub struct CollectionHandle<T: Config> {187	pub id: CollectionId,188	collection: Collection<T>,189	recorder: pallet_evm_coder_substrate::SubstrateRecorder<T>,190}191impl<T: Config> CollectionHandle<T> {192	pub fn get_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {193		<CollectionById<T>>::get(id).map(|collection| Self {194			id,195			collection,196			recorder: pallet_evm_coder_substrate::SubstrateRecorder::new(197				eth::collection_id_to_address(id),198				gas_limit,199			),200		})201	}202	pub fn get(id: CollectionId) -> Option<Self> {203		Self::get_with_gas_limit(id, u64::MAX)204	}205	pub fn log(&self, log: impl evm_coder::ToLog) -> DispatchResult {206		self.recorder.log_sub(log)207	}208	#[allow(dead_code)]209	fn consume_gas(&self, gas: u64) -> DispatchResult {210		self.recorder.consume_gas_sub(gas)211	}212	fn consume_sload(&self) -> DispatchResult {213		self.recorder.consume_sload_sub()214	}215	fn consume_sstore(&self) -> DispatchResult {216		self.recorder.consume_sstore_sub()217	}218	pub fn submit_logs(self) -> DispatchResult {219		self.recorder.submit_logs()220	}221	pub fn save(self) -> DispatchResult {222		self.recorder.submit_logs()?;223		<CollectionById<T>>::insert(self.id, self.collection);224		Ok(())225	}226}227impl<T: Config> Deref for CollectionHandle<T> {228	type Target = Collection<T>;229230	fn deref(&self) -> &Self::Target {231		&self.collection232	}233}234235impl<T: Config> DerefMut for CollectionHandle<T> {236	fn deref_mut(&mut self) -> &mut Self::Target {237		&mut self.collection238	}239}240241pub trait Config: system::Config + pallet_evm_coder_substrate::Config + Sized {242	type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;243244	/// Weight information for extrinsics in this pallet.245	type WeightInfo: WeightInfo;246247	type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;248	type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;249250	type CrossAccountId: CrossAccountId<Self::AccountId>;251	type Currency: Currency<Self::AccountId>;252	type CollectionCreationPrice: Get<253		<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,254	>;255	type TreasuryAccountId: Get<Self::AccountId>;256}257258// # Used definitions259//260// ## User control levels261//262// chain-controlled - key is uncontrolled by user263//                    i.e autoincrementing index264//                    can use non-cryptographic hash265// real - key is controlled by user266//        but it is hard to generate enough colliding values, i.e owner of signed txs267//        can use non-cryptographic hash268// controlled - key is completly controlled by users269//              i.e maps with mutable keys270//              should use cryptographic hash271//272// ## User control level downgrade reasons273//274// ?1 - chain-controlled -> controlled275//      collections/tokens can be destroyed, resulting in massive holes276// ?2 - chain-controlled -> controlled277//      same as ?1, but can be only added, resulting in easier exploitation278// ?3 - real -> controlled279//      no confirmation required, so addresses can be easily generated280decl_storage! {281	trait Store for Module<T: Config> as Nft {282283		//#region Private members284		/// Id of next collection285		CreatedCollectionCount: u32;286		/// Used for migrations287		ChainVersion: u64;288		/// Id of last collection token289		/// Collection id (controlled?1)290		ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;291		//#endregion292293		//#region Bound counters294		/// Amount of collections destroyed, used for total amount tracking with295		/// CreatedCollectionCount296		DestroyedCollectionCount: u32;297		/// Total amount of account owned tokens (NFTs + RFTs + unique fungibles)298		/// Account id (real)299		pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;300		//#endregion301302		//#region Basic collections303		/// Collection info304		/// Collection id (controlled?1)305		pub CollectionById get(fn collection_id) config(): map hasher(blake2_128_concat) CollectionId => Option<Collection<T>> = None;306		/// List of collection admins307		/// Collection id (controlled?2)308		pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec<T::CrossAccountId>;309		/// Whitelisted collection users310		/// Collection id (controlled?2), user id (controlled?3)311		pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool;312		//#endregion313314		/// How many of collection items user have315		/// Collection id (controlled?2), account id (real)316		pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128;317318		/// Amount of items which spender can transfer out of owners account (via transferFrom)319		/// Collection id (controlled?2), (token id (controlled ?2) + owner account id (real) + spender account id (controlled?3))320		/// TODO: Off chain worker should remove from this map when token gets removed321		pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128;322323		//#region Item collections324		/// Collection id (controlled?2), token id (controlled?1)325		pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<NftItemType<T::CrossAccountId>>;326		/// Collection id (controlled?2), owner (controlled?2)327		pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType;328		/// Collection id (controlled?2), token id (controlled?1)329		pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<ReFungibleItemType<T::CrossAccountId>>;330		//#endregion331332		//#region Index list333		/// Collection id (controlled?2), tokens owner (controlled?2)334		pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;335		//#endregion336337		//#region Tokens transfer rate limit baskets338		/// (Collection id (controlled?2), who created (real))339		/// TODO: Off chain worker should remove from this map when collection gets removed340		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;341		/// Collection id (controlled?2), token id (controlled?2)342		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;343		/// Collection id (controlled?2), owning user (real)344		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;345		/// Collection id (controlled?2), token id (controlled?2)346		pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;347		//#endregion348349		/// Variable metadata sponsoring350		/// Collection id (controlled?2), token id (controlled?2)351		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;352	}353	add_extra_genesis {354		build(|config: &GenesisConfig<T>| {355			// Modification of storage356			for (_num, _c) in &config.collection_id {357				<Module<T>>::init_collection(_c);358			}359360			for (_num, _c, _i) in &config.nft_item_id {361				<Module<T>>::init_nft_token(*_c, _i);362			}363364			for (collection_id, account_id, fungible_item) in &config.fungible_item_id {365				<Module<T>>::init_fungible_token(*collection_id, &T::CrossAccountId::from_sub(account_id.clone()), fungible_item);366			}367368			for (_num, _c, _i) in &config.refungible_item_id {369				<Module<T>>::init_refungible_token(*_c, _i);370			}371		})372	}373}374375decl_event!(376	pub enum Event<T>377	where378		AccountId = <T as frame_system::Config>::AccountId,379		CrossAccountId = <T as Config>::CrossAccountId,380	{381		/// New collection was created382		///383		/// # Arguments384		///385		/// * collection_id: Globally unique identifier of newly created collection.386		///387		/// * mode: [CollectionMode] converted into u8.388		///389		/// * account_id: Collection owner.390		CollectionCreated(CollectionId, u8, AccountId),391392		/// New item was created.393		///394		/// # Arguments395		///396		/// * collection_id: Id of the collection where item was created.397		///398		/// * item_id: Id of an item. Unique within the collection.399		///400		/// * recipient: Owner of newly created item401		ItemCreated(CollectionId, TokenId, CrossAccountId),402403		/// Collection item was burned.404		///405		/// # Arguments406		///407		/// collection_id.408		///409		/// item_id: Identifier of burned NFT.410		ItemDestroyed(CollectionId, TokenId),411412		/// Item was transferred413		///414		/// * collection_id: Id of collection to which item is belong415		///416		/// * item_id: Id of an item417		///418		/// * sender: Original owner of item419		///420		/// * recipient: New owner of item421		///422		/// * amount: Always 1 for NFT423		Transfer(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),424425		/// * collection_id426		///427		/// * item_id428		///429		/// * sender430		///431		/// * spender432		///433		/// * amount434		Approved(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),435	}436);437438decl_module! {439	pub struct Module<T: Config> for enum Call440	where441		origin: T::Origin442	{443		fn deposit_event() = default;444		const CollectionAdminsLimit: u64 = COLLECTION_ADMINS_LIMIT;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 created_count = CreatedCollectionCount::get();497			let destroyed_count = DestroyedCollectionCount::get();498499			// bound Total number of collections500			ensure!(created_count - destroyed_count < COLLECTION_NUMBER_LIMIT, Error::<T>::TotalCollectionsLimitExceeded);501502			// check params503			ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);504			ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);505			ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);506			ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);507508			// Generate next collection ID509			let next_id = created_count510				.checked_add(1)511				.ok_or(Error::<T>::NumOverflow)?;512513			CreatedCollectionCount::put(next_id);514515			let limits = CollectionLimits {516				sponsored_data_size: CUSTOM_DATA_LIMIT,517				..Default::default()518			};519520			// Create new collection521			let new_collection = Collection {522				owner: who.clone(),523				name: collection_name,524				mode: mode.clone(),525				mint_mode: false,526				access: AccessMode::Normal,527				description: collection_description,528				decimal_points,529				token_prefix,530				offchain_schema: Vec::new(),531				schema_version: SchemaVersion::ImageURL,532				sponsorship: SponsorshipState::Disabled,533				variable_on_chain_schema: Vec::new(),534				const_on_chain_schema: Vec::new(),535				limits,536				transfers_enabled: true,537			};538539			// Add new collection to map540			<CollectionById<T>>::insert(next_id, new_collection);541542			// call event543			Self::deposit_event(RawEvent::CollectionCreated(next_id, mode.id(), who));544545			Ok(())546		}547548		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.549		///550		/// # Permissions551		///552		/// * Collection Owner.553		///554		/// # Arguments555		///556		/// * collection_id: collection to destroy.557		#[weight = <T as Config>::WeightInfo::destroy_collection()]558		#[transactional]559		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {560561			let sender = ensure_signed(origin)?;562			let collection = Self::get_collection(collection_id)?;563			Self::check_owner_permissions(&collection, &sender)?;564			if !collection.limits.owner_can_destroy {565				fail!(Error::<T>::NoPermission);566			}567568			<AddressTokens<T>>::remove_prefix(collection_id, None);569			<Allowances<T>>::remove_prefix(collection_id, None);570			<Balance<T>>::remove_prefix(collection_id, None);571			<ItemListIndex>::remove(collection_id);572			<AdminList<T>>::remove(collection_id);573			<CollectionById<T>>::remove(collection_id);574			<WhiteList<T>>::remove_prefix(collection_id, None);575576			<NftItemList<T>>::remove_prefix(collection_id, None);577			<FungibleItemList<T>>::remove_prefix(collection_id, None);578			<ReFungibleItemList<T>>::remove_prefix(collection_id, None);579580			<NftTransferBasket<T>>::remove_prefix(collection_id, None);581			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);582			<ReFungibleTransferBasket<T>>::remove_prefix(collection_id, None);583584			<VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);585586			DestroyedCollectionCount::put(DestroyedCollectionCount::get()587				.checked_add(1)588				.ok_or(Error::<T>::NumOverflow)?);589590			Ok(())591		}592593		/// Add an address to white list.594		///595		/// # Permissions596		///597		/// * Collection Owner598		/// * Collection Admin599		///600		/// # Arguments601		///602		/// * collection_id.603		///604		/// * address.605		#[weight = <T as Config>::WeightInfo::add_to_white_list()]606		#[transactional]607		pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{608609			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);610			let collection = Self::get_collection(collection_id)?;611612			Self::toggle_white_list_internal(613				&sender,614				&collection,615				&address,616				true,617			)?;618619			Ok(())620		}621622		/// Remove an address from white list.623		///624		/// # Permissions625		///626		/// * Collection Owner627		/// * Collection Admin628		///629		/// # Arguments630		///631		/// * collection_id.632		///633		/// * address.634		#[weight = <T as Config>::WeightInfo::remove_from_white_list()]635		#[transactional]636		pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{637638			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);639			let collection = Self::get_collection(collection_id)?;640641			Self::toggle_white_list_internal(642				&sender,643				&collection,644				&address,645				false,646			)?;647648			Ok(())649		}650651		/// Toggle between normal and white list access for the methods with access for `Anyone`.652		///653		/// # Permissions654		///655		/// * Collection Owner.656		///657		/// # Arguments658		///659		/// * collection_id.660		///661		/// * mode: [AccessMode]662		#[weight = <T as Config>::WeightInfo::set_public_access_mode()]663		#[transactional]664		pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult665		{666			let sender = ensure_signed(origin)?;667668			let mut target_collection = Self::get_collection(collection_id)?;669			Self::check_owner_permissions(&target_collection, &sender)?;670			target_collection.access = mode;671			target_collection.save()672		}673674		/// Allows Anyone to create tokens if:675		/// * White List is enabled, and676		/// * Address is added to white list, and677		/// * This method was called with True parameter678		///679		/// # Permissions680		/// * Collection Owner681		///682		/// # Arguments683		///684		/// * collection_id.685		///686		/// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.687		#[weight = <T as Config>::WeightInfo::set_mint_permission()]688		#[transactional]689		pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult690		{691			let sender = ensure_signed(origin)?;692693			let mut target_collection = Self::get_collection(collection_id)?;694			Self::check_owner_permissions(&target_collection, &sender)?;695			target_collection.mint_mode = mint_permission;696			target_collection.save()697		}698699		/// Change the owner of the collection.700		///701		/// # Permissions702		///703		/// * Collection Owner.704		///705		/// # Arguments706		///707		/// * collection_id.708		///709		/// * new_owner.710		#[weight = <T as Config>::WeightInfo::change_collection_owner()]711		#[transactional]712		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {713714			let sender = ensure_signed(origin)?;715			let mut target_collection = Self::get_collection(collection_id)?;716			Self::check_owner_permissions(&target_collection, &sender)?;717			target_collection.owner = new_owner;718			target_collection.save()719		}720721		/// Adds an admin of the Collection.722		/// 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.723		///724		/// # Permissions725		///726		/// * Collection Owner.727		/// * Collection Admin.728		///729		/// # Arguments730		///731		/// * collection_id: ID of the Collection to add admin for.732		///733		/// * new_admin_id: Address of new admin to add.734		#[weight = <T as Config>::WeightInfo::add_collection_admin()]735		#[transactional]736		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {737			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);738			let collection = Self::get_collection(collection_id)?;739			Self::check_owner_or_admin_permissions(&collection, &sender)?;740			let mut admin_arr = <AdminList<T>>::get(collection_id);741742			match admin_arr.binary_search(&new_admin_id) {743				Ok(_) => {},744				Err(idx) => {745					ensure!(admin_arr.len() < COLLECTION_ADMINS_LIMIT as usize, Error::<T>::CollectionAdminsLimitExceeded);746					admin_arr.insert(idx, new_admin_id);747					<AdminList<T>>::insert(collection_id, admin_arr);748				}749			}750			Ok(())751		}752753		/// 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.754		///755		/// # Permissions756		///757		/// * Collection Owner.758		/// * Collection Admin.759		///760		/// # Arguments761		///762		/// * collection_id: ID of the Collection to remove admin for.763		///764		/// * account_id: Address of admin to remove.765		#[weight = <T as Config>::WeightInfo::remove_collection_admin()]766		#[transactional]767		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {768			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);769			let collection = Self::get_collection(collection_id)?;770			Self::check_owner_or_admin_permissions(&collection, &sender)?;771			let mut admin_arr = <AdminList<T>>::get(collection_id);772773			if let Ok(idx) = admin_arr.binary_search(&account_id) {774				admin_arr.remove(idx);775				<AdminList<T>>::insert(collection_id, admin_arr);776			}777			Ok(())778		}779780		/// # Permissions781		///782		/// * Collection Owner783		///784		/// # Arguments785		///786		/// * collection_id.787		///788		/// * new_sponsor.789		#[weight = <T as Config>::WeightInfo::set_collection_sponsor()]790		#[transactional]791		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {792			let sender = ensure_signed(origin)?;793			let mut target_collection = Self::get_collection(collection_id)?;794			Self::check_owner_permissions(&target_collection, &sender)?;795796			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);797			target_collection.save()798		}799800		/// # Permissions801		///802		/// * Sponsor.803		///804		/// # Arguments805		///806		/// * collection_id.807		#[weight = <T as Config>::WeightInfo::confirm_sponsorship()]808		#[transactional]809		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {810			let sender = ensure_signed(origin)?;811812			let mut target_collection = Self::get_collection(collection_id)?;813			ensure!(814				target_collection.sponsorship.pending_sponsor() == Some(&sender),815				Error::<T>::ConfirmUnsetSponsorFail816			);817818			target_collection.sponsorship = SponsorshipState::Confirmed(sender);819			target_collection.save()820		}821822		/// Switch back to pay-per-own-transaction model.823		///824		/// # Permissions825		///826		/// * Collection owner.827		///828		/// # Arguments829		///830		/// * collection_id.831		#[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]832		#[transactional]833		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {834			let sender = ensure_signed(origin)?;835836			let mut target_collection = Self::get_collection(collection_id)?;837			Self::check_owner_permissions(&target_collection, &sender)?;838839			target_collection.sponsorship = SponsorshipState::Disabled;840			target_collection.save()841		}842843		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.844		///845		/// # Permissions846		///847		/// * Collection Owner.848		/// * Collection Admin.849		/// * Anyone if850		///     * White List is enabled, and851		///     * Address is added to white list, and852		///     * MintPermission is enabled (see SetMintPermission method)853		///854		/// # Arguments855		///856		/// * collection_id: ID of the collection.857		///858		/// * owner: Address, initial owner of the NFT.859		///860		/// * data: Token data to store on chain.861		// #[weight =862		// (130_000_000 as Weight)863		// .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))864		// .saturating_add(RocksDbWeight::get().reads(10 as Weight))865		// .saturating_add(RocksDbWeight::get().writes(8 as Weight))]866867		#[weight = <T as Config>::WeightInfo::create_item(data.data_size())]868		#[transactional]869		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResult {870			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);871			let collection = Self::get_collection(collection_id)?;872873			Self::create_item_internal(&sender, &collection, &owner, data)?;874875			collection.submit_logs()876		}877878		/// This method creates multiple items in a collection created with CreateCollection method.879		///880		/// # Permissions881		///882		/// * Collection Owner.883		/// * Collection Admin.884		/// * Anyone if885		///     * White List is enabled, and886		///     * Address is added to white list, and887		///     * MintPermission is enabled (see SetMintPermission method)888		///889		/// # Arguments890		///891		/// * collection_id: ID of the collection.892		///893		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].894		///895		/// * owner: Address, initial owner of the NFT.896		#[weight = <T as Config>::WeightInfo::create_item(items_data.iter()897							   .map(|data| { data.data_size() })898							   .sum())]899		#[transactional]900		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResult {901902			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);903			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);904			let collection = Self::get_collection(collection_id)?;905906			Self::create_multiple_items_internal(&sender, &collection, &owner, items_data)?;907908			collection.submit_logs()909		}910911		// TODO! transaction weight912913		/// Set transfers_enabled value for particular collection914		///915		/// # Permissions916		///917		/// * Collection Owner.918		///919		/// # Arguments920		///921		/// * collection_id: ID of the collection.922		///923		/// * value: New flag value.924		#[weight = <T as Config>::WeightInfo::burn_item()]925		#[transactional]926		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {927928			let sender = ensure_signed(origin)?;929			let mut target_collection = Self::get_collection(collection_id)?;930931			Self::check_owner_permissions(&target_collection, &sender)?;932933			target_collection.transfers_enabled = value;934			target_collection.save()935		}936937		/// Destroys a concrete instance of NFT.938		///939		/// # Permissions940		///941		/// * Collection Owner.942		/// * Collection Admin.943		/// * Current NFT Owner.944		///945		/// # Arguments946		///947		/// * collection_id: ID of the collection.948		///949		/// * item_id: ID of NFT to burn.950		#[weight = <T as Config>::WeightInfo::burn_item()]951		#[transactional]952		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {953954			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);955			let target_collection = Self::get_collection(collection_id)?;956957			Self::burn_item_internal(&sender, &target_collection, item_id, value)?;958959			target_collection.submit_logs()960		}961962		/// Change ownership of the token.963		///964		/// # Permissions965		///966		/// * Collection Owner967		/// * Collection Admin968		/// * Current NFT owner969		///970		/// # Arguments971		///972		/// * recipient: Address of token recipient.973		///974		/// * collection_id.975		///976		/// * item_id: ID of the item977		///     * Non-Fungible Mode: Required.978		///     * Fungible Mode: Ignored.979		///     * Re-Fungible Mode: Required.980		///981		/// * value: Amount to transfer.982		///     * Non-Fungible Mode: Ignored983		///     * Fungible Mode: Must specify transferred amount984		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)985		#[weight = <T as Config>::WeightInfo::transfer()]986		#[transactional]987		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {988			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);989			let collection = Self::get_collection(collection_id)?;990991			Self::transfer_internal(&sender, &recipient, &collection, item_id, value)?;992993			collection.submit_logs()994		}995996		/// Set, change, or remove approved address to transfer the ownership of the NFT.997		///998		/// # Permissions999		///1000		/// * Collection Owner1001		/// * Collection Admin1002		/// * Current NFT owner1003		///1004		/// # Arguments1005		///1006		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1007		///1008		/// * collection_id.1009		///1010		/// * item_id: ID of the item.1011		#[weight = <T as Config>::WeightInfo::approve()]1012		#[transactional]1013		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {1014			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1015			let collection = Self::get_collection(collection_id)?;10161017			Self::approve_internal(&sender, &spender, &collection, item_id, amount)?;10181019			collection.submit_logs()1020		}10211022		/// 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.1023		///1024		/// # Permissions1025		/// * Collection Owner1026		/// * Collection Admin1027		/// * Current NFT owner1028		/// * Address approved by current NFT owner1029		///1030		/// # Arguments1031		///1032		/// * from: Address that owns token.1033		///1034		/// * recipient: Address of token recipient.1035		///1036		/// * collection_id.1037		///1038		/// * item_id: ID of the item.1039		///1040		/// * value: Amount to transfer.1041		#[weight = <T as Config>::WeightInfo::transfer_from()]1042		#[transactional]1043		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {1044			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1045			let collection = Self::get_collection(collection_id)?;10461047			Self::transfer_from_internal(&sender, &from, &recipient, &collection, item_id, value)?;10481049			collection.submit_logs()1050		}1051		// #[weight = 0]1052		//     // let no_perm_mes = "You do not have permissions to modify this collection";1053		//     // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1054		//     // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1055		//     // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);10561057		//     // // on_nft_received  call10581059		//     // Self::transfer(origin, collection_id, item_id, new_owner)?;10601061		//     Ok(())1062		// }10631064		/// Set off-chain data schema.1065		///1066		/// # Permissions1067		///1068		/// * Collection Owner1069		/// * Collection Admin1070		///1071		/// # Arguments1072		///1073		/// * collection_id.1074		///1075		/// * schema: String representing the offchain data schema.1076		#[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1077		#[transactional]1078		pub fn set_variable_meta_data (1079			origin,1080			collection_id: CollectionId,1081			item_id: TokenId,1082			data: Vec<u8>1083		) -> DispatchResult {1084			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);10851086			let collection = Self::get_collection(collection_id)?;10871088			Self::set_variable_meta_data_internal(&sender, &collection, item_id, data)?;10891090			Ok(())1091		}10921093		/// Set schema standard1094		/// ImageURL1095		/// Unique1096		///1097		/// # Permissions1098		///1099		/// * Collection Owner1100		/// * Collection Admin1101		///1102		/// # Arguments1103		///1104		/// * collection_id.1105		///1106		/// * schema: SchemaVersion: enum1107		#[weight = <T as Config>::WeightInfo::set_schema_version()]1108		#[transactional]1109		pub fn set_schema_version(1110			origin,1111			collection_id: CollectionId,1112			version: SchemaVersion1113		) -> DispatchResult {1114			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1115			let mut target_collection = Self::get_collection(collection_id)?;1116			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;1117			target_collection.schema_version = version;1118			target_collection.save()1119		}11201121		/// Set off-chain data schema.1122		///1123		/// # Permissions1124		///1125		/// * Collection Owner1126		/// * Collection Admin1127		///1128		/// # Arguments1129		///1130		/// * collection_id.1131		///1132		/// * schema: String representing the offchain data schema.1133		#[weight = <T as Config>::WeightInfo::set_offchain_schema()]1134		#[transactional]1135		pub fn set_offchain_schema(1136			origin,1137			collection_id: CollectionId,1138			schema: Vec<u8>1139		) -> DispatchResult {1140			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1141			let mut target_collection = Self::get_collection(collection_id)?;1142			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11431144			// check schema limit1145			ensure!(schema.len() as u32 <= OFFCHAIN_SCHEMA_LIMIT, "");11461147			target_collection.offchain_schema = schema;1148			target_collection.save()1149		}11501151		/// Set const on-chain data schema.1152		///1153		/// # Permissions1154		///1155		/// * Collection Owner1156		/// * Collection Admin1157		///1158		/// # Arguments1159		///1160		/// * collection_id.1161		///1162		/// * schema: String representing the const on-chain data schema.1163		#[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1164		#[transactional]1165		pub fn set_const_on_chain_schema (1166			origin,1167			collection_id: CollectionId,1168			schema: Vec<u8>1169		) -> DispatchResult {1170			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1171			let mut target_collection = Self::get_collection(collection_id)?;1172			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11731174			// check schema limit1175			ensure!(schema.len() as u32 <= CONST_ON_CHAIN_SCHEMA_LIMIT, "");11761177			target_collection.const_on_chain_schema = schema;1178			target_collection.save()1179		}11801181		/// Set variable on-chain data schema.1182		///1183		/// # Permissions1184		///1185		/// * Collection Owner1186		/// * Collection Admin1187		///1188		/// # Arguments1189		///1190		/// * collection_id.1191		///1192		/// * schema: String representing the variable on-chain data schema.1193		#[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1194		#[transactional]1195		pub fn set_variable_on_chain_schema (1196			origin,1197			collection_id: CollectionId,1198			schema: Vec<u8>1199		) -> DispatchResult {1200			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1201			let mut target_collection = Self::get_collection(collection_id)?;1202			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;12031204			// check schema limit1205			ensure!(schema.len() as u32 <= VARIABLE_ON_CHAIN_SCHEMA_LIMIT, "");12061207			target_collection.variable_on_chain_schema = schema;1208			target_collection.save()1209		}12101211		#[weight = <T as Config>::WeightInfo::set_collection_limits()]1212		#[transactional]1213		pub fn set_collection_limits(1214			origin,1215			collection_id: u32,1216			new_limits: CollectionLimits<T::BlockNumber>,1217		) -> DispatchResult {1218			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1219			let mut target_collection = Self::get_collection(collection_id)?;1220			Self::check_owner_permissions(&target_collection, sender.as_sub())?;1221			let old_limits = &target_collection.limits;12221223			// collection bounds1224			ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1225				new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP &&1226				new_limits.sponsored_data_size <= CUSTOM_DATA_LIMIT,1227				Error::<T>::CollectionLimitBoundsExceeded);12281229			// token_limit   check  prev1230			ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1231			ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);12321233			ensure!(1234				(old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1235				(old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1236				Error::<T>::OwnerPermissionsCantBeReverted,1237			);12381239			target_collection.limits = new_limits;12401241			target_collection.save()1242		}1243	}1244}12451246impl<T: Config> Module<T> {1247	pub fn create_item_internal(1248		sender: &T::CrossAccountId,1249		collection: &CollectionHandle<T>,1250		owner: &T::CrossAccountId,1251		data: CreateItemData,1252	) -> DispatchResult {1253		ensure!(1254			owner != &T::CrossAccountId::from_eth(H160([0; 20])),1255			Error::<T>::AddressIsZero1256		);12571258		Self::can_create_items_in_collection(collection, sender, owner, 1)?;1259		Self::validate_create_item_args(collection, &data)?;1260		Self::create_item_no_validation(collection, owner, data)?;12611262		Ok(())1263	}12641265	pub fn transfer_internal(1266		sender: &T::CrossAccountId,1267		recipient: &T::CrossAccountId,1268		target_collection: &CollectionHandle<T>,1269		item_id: TokenId,1270		value: u128,1271	) -> DispatchResult {1272		ensure!(1273			recipient != &T::CrossAccountId::from_eth(H160([0; 20])),1274			Error::<T>::AddressIsZero1275		);12761277		// Limits check1278		Self::is_correct_transfer(target_collection, recipient)?;12791280		// Transfer permissions check1281		ensure!(1282			Self::is_item_owner(sender, target_collection, item_id)?1283				|| Self::is_owner_or_admin_permissions(target_collection, sender)?,1284			Error::<T>::NoPermission1285		);12861287		if target_collection.access == AccessMode::WhiteList {1288			Self::check_white_list(target_collection, sender)?;1289			Self::check_white_list(target_collection, recipient)?;1290		}12911292		match target_collection.mode {1293			CollectionMode::NFT => Self::transfer_nft(1294				target_collection,1295				item_id,1296				sender.clone(),1297				recipient.clone(),1298			)?,1299			CollectionMode::Fungible(_) => {1300				Self::transfer_fungible(target_collection, value, sender, recipient)?1301			}1302			CollectionMode::ReFungible => Self::transfer_refungible(1303				target_collection,1304				item_id,1305				value,1306				sender.clone(),1307				recipient.clone(),1308			)?,1309			_ => (),1310		};13111312		Self::deposit_event(RawEvent::Transfer(1313			target_collection.id,1314			item_id,1315			sender.clone(),1316			recipient.clone(),1317			value,1318		));13191320		Ok(())1321	}13221323	pub fn approve_internal(1324		sender: &T::CrossAccountId,1325		spender: &T::CrossAccountId,1326		collection: &CollectionHandle<T>,1327		item_id: TokenId,1328		amount: u128,1329	) -> DispatchResult {1330		Self::token_exists(collection, item_id)?;13311332		// Transfer permissions check1333		let bypasses_limits = collection.limits.owner_can_transfer1334			&& Self::is_owner_or_admin_permissions(collection, sender)?;13351336		let allowance_limit = if bypasses_limits {1337			None1338		} else if let Some(amount) = Self::owned_amount(sender, collection, item_id)? {1339			Some(amount)1340		} else {1341			fail!(Error::<T>::NoPermission);1342		};13431344		if collection.access == AccessMode::WhiteList {1345			Self::check_white_list(collection, sender)?;1346			Self::check_white_list(collection, spender)?;1347		}13481349		collection.consume_sload()?;1350		let allowance: u128 = amount1351			.checked_add(<Allowances<T>>::get(1352				collection.id,1353				(item_id, sender.as_sub(), spender.as_sub()),1354			))1355			.ok_or(Error::<T>::NumOverflow)?;1356		if let Some(limit) = allowance_limit {1357			ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1358		}1359		collection.consume_sstore()?;1360		<Allowances<T>>::insert(1361			collection.id,1362			(item_id, sender.as_sub(), spender.as_sub()),1363			allowance,1364		);13651366		if matches!(collection.mode, CollectionMode::NFT) {1367			// TODO: NFT: only one owner may exist for token in ERC7211368			collection.log(ERC721Events::Approval {1369				owner: *sender.as_eth(),1370				approved: *spender.as_eth(),1371				token_id: item_id.into(),1372			})?;1373		}13741375		if matches!(collection.mode, CollectionMode::Fungible(_)) {1376			// TODO: NFT: only one owner may exist for token in ERC201377			collection.log(ERC20Events::Approval {1378				owner: *sender.as_eth(),1379				spender: *spender.as_eth(),1380				value: allowance.into(),1381			})?;1382		}13831384		Self::deposit_event(RawEvent::Approved(1385			collection.id,1386			item_id,1387			sender.clone(),1388			spender.clone(),1389			allowance,1390		));1391		Ok(())1392	}13931394	pub fn transfer_from_internal(1395		sender: &T::CrossAccountId,1396		from: &T::CrossAccountId,1397		recipient: &T::CrossAccountId,1398		collection: &CollectionHandle<T>,1399		item_id: TokenId,1400		amount: u128,1401	) -> DispatchResult {1402		// Check approval1403		collection.consume_sload()?;1404		let approval: u128 =1405			<Allowances<T>>::get(collection.id, (item_id, from.as_sub(), sender.as_sub()));14061407		// Limits check1408		Self::is_correct_transfer(collection, recipient)?;14091410		// Transfer permissions check1411		ensure!(1412			approval >= amount1413				|| (collection.limits.owner_can_transfer1414					&& Self::is_owner_or_admin_permissions(collection, sender)?),1415			Error::<T>::NoPermission1416		);14171418		if collection.access == AccessMode::WhiteList {1419			Self::check_white_list(collection, sender)?;1420			Self::check_white_list(collection, recipient)?;1421		}14221423		// Reduce approval by transferred amount or remove if remaining approval drops to 01424		let allowance = approval.saturating_sub(amount);1425		collection.consume_sstore()?;1426		if allowance > 0 {1427			<Allowances<T>>::insert(1428				collection.id,1429				(item_id, from.as_sub(), sender.as_sub()),1430				allowance,1431			);1432		} else {1433			<Allowances<T>>::remove(collection.id, (item_id, from.as_sub(), sender.as_sub()));1434		}14351436		match collection.mode {1437			CollectionMode::NFT => {1438				Self::transfer_nft(collection, item_id, from.clone(), recipient.clone())?1439			}1440			CollectionMode::Fungible(_) => {1441				Self::transfer_fungible(collection, amount, from, recipient)?1442			}1443			CollectionMode::ReFungible => Self::transfer_refungible(1444				collection,1445				item_id,1446				amount,1447				from.clone(),1448				recipient.clone(),1449			)?,1450			_ => (),1451		};14521453		if matches!(collection.mode, CollectionMode::Fungible(_)) {1454			collection.log(ERC20Events::Approval {1455				owner: *from.as_eth(),1456				spender: *sender.as_eth(),1457				value: allowance.into(),1458			})?;1459		}14601461		Ok(())1462	}14631464	pub fn set_variable_meta_data_internal(1465		sender: &T::CrossAccountId,1466		collection: &CollectionHandle<T>,1467		item_id: TokenId,1468		data: Vec<u8>,1469	) -> DispatchResult {1470		Self::token_exists(collection, item_id)?;14711472		ensure!(1473			CUSTOM_DATA_LIMIT >= data.len() as u32,1474			Error::<T>::TokenVariableDataLimitExceeded1475		);14761477		// Modify permissions check1478		ensure!(1479			Self::is_item_owner(sender, collection, item_id)?1480				|| Self::is_owner_or_admin_permissions(collection, sender)?,1481			Error::<T>::NoPermission1482		);14831484		match collection.mode {1485			CollectionMode::NFT => Self::set_nft_variable_data(collection, item_id, data)?,1486			CollectionMode::ReFungible => {1487				Self::set_re_fungible_variable_data(collection, item_id, data)?1488			}1489			CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1490			_ => fail!(Error::<T>::UnexpectedCollectionType),1491		};14921493		Ok(())1494	}14951496	pub fn create_multiple_items_internal(1497		sender: &T::CrossAccountId,1498		collection: &CollectionHandle<T>,1499		owner: &T::CrossAccountId,1500		items_data: Vec<CreateItemData>,1501	) -> DispatchResult {1502		Self::can_create_items_in_collection(collection, sender, owner, items_data.len() as u32)?;15031504		for data in &items_data {1505			Self::validate_create_item_args(collection, data)?;1506		}1507		for data in &items_data {1508			Self::create_item_no_validation(collection, owner, data.clone())?;1509		}15101511		Ok(())1512	}15131514	pub fn burn_item_internal(1515		sender: &T::CrossAccountId,1516		collection: &CollectionHandle<T>,1517		item_id: TokenId,1518		value: u128,1519	) -> DispatchResult {1520		ensure!(1521			Self::is_item_owner(sender, collection, item_id)?1522				|| (collection.limits.owner_can_transfer1523					&& Self::is_owner_or_admin_permissions(collection, sender)?),1524			Error::<T>::NoPermission1525		);15261527		if collection.access == AccessMode::WhiteList {1528			Self::check_white_list(collection, sender)?;1529		}15301531		match collection.mode {1532			CollectionMode::NFT => Self::burn_nft_item(collection, item_id)?,1533			CollectionMode::Fungible(_) => Self::burn_fungible_item(sender, collection, value)?,1534			CollectionMode::ReFungible => Self::burn_refungible_item(collection, item_id, sender)?,1535			_ => (),1536		};15371538		Ok(())1539	}15401541	pub fn toggle_white_list_internal(1542		sender: &T::CrossAccountId,1543		collection: &CollectionHandle<T>,1544		address: &T::CrossAccountId,1545		whitelisted: bool,1546	) -> DispatchResult {1547		Self::check_owner_or_admin_permissions(collection, sender)?;15481549		if whitelisted {1550			<WhiteList<T>>::insert(collection.id, address.as_sub(), true);1551		} else {1552			<WhiteList<T>>::remove(collection.id, address.as_sub());1553		}15541555		Ok(())1556	}15571558	fn is_correct_transfer(1559		collection: &CollectionHandle<T>,1560		recipient: &T::CrossAccountId,1561	) -> DispatchResult {1562		let collection_id = collection.id;15631564		// check token limit and account token limit1565		collection.consume_sload()?;1566		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 !matches!(data, CreateItemData::NFT(_)) {1620					fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1621				}1622			}1623			CollectionMode::Fungible(_) => {1624				if !matches!(data, CreateItemData::Fungible(_)) {1625					fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1626				}1627			}1628			CollectionMode::ReFungible => {1629				if let CreateItemData::ReFungible(data) = data {1630					// Check refungibility limits1631					ensure!(1632						data.pieces <= MAX_REFUNGIBLE_PIECES,1633						Error::<T>::WrongRefungiblePieces1634					);1635					ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1636				} else {1637					fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1638				}1639			}1640			_ => {1641				fail!(Error::<T>::UnexpectedCollectionType);1642			}1643		};16441645		Ok(())1646	}16471648	fn create_item_no_validation(1649		collection: &CollectionHandle<T>,1650		owner: &T::CrossAccountId,1651		data: CreateItemData,1652	) -> DispatchResult {1653		match data {1654			CreateItemData::NFT(data) => {1655				let item = NftItemType {1656					owner: owner.clone(),1657					const_data: data.const_data.into_inner(),1658					variable_data: data.variable_data.into_inner(),1659				};16601661				Self::add_nft_item(collection, item)?;1662			}1663			CreateItemData::Fungible(data) => {1664				Self::add_fungible_item(collection, owner, data.value)?;1665			}1666			CreateItemData::ReFungible(data) => {1667				let owner_list = vec![Ownership {1668					owner: owner.clone(),1669					fraction: data.pieces,1670				}];16711672				let item = ReFungibleItemType {1673					owner: owner_list,1674					const_data: data.const_data.into_inner(),1675					variable_data: data.variable_data.into_inner(),1676				};16771678				Self::add_refungible_item(collection, item)?;1679			}1680		};16811682		Ok(())1683	}16841685	fn add_fungible_item(1686		collection: &CollectionHandle<T>,1687		owner: &T::CrossAccountId,1688		value: u128,1689	) -> DispatchResult {1690		let collection_id = collection.id;16911692		// Does new owner already have an account?1693		collection.consume_sload()?;1694		let balance: u128 = <FungibleItemList<T>>::get(collection_id, owner.as_sub()).value;16951696		// Mint1697		let item = FungibleItemType {1698			value: balance.checked_add(value).ok_or(Error::<T>::NumOverflow)?,1699		};1700		collection.consume_sstore()?;1701		<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), item);17021703		// Update balance1704		collection.consume_sload()?;1705		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1706			.checked_add(value)1707			.ok_or(Error::<T>::NumOverflow)?;1708		collection.consume_sstore()?;1709		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);17101711		collection.log(ERC20Events::Transfer {1712			from: H160::default(),1713			to: *owner.as_eth(),1714			value: value.into(),1715		})?;1716		Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));1717		Ok(())1718	}17191720	fn add_refungible_item(1721		collection: &CollectionHandle<T>,1722		item: ReFungibleItemType<T::CrossAccountId>,1723	) -> DispatchResult {1724		let collection_id = collection.id;17251726		let current_index = <ItemListIndex>::get(collection_id)1727			.checked_add(1)1728			.ok_or(Error::<T>::NumOverflow)?;1729		let itemcopy = item.clone();17301731		ensure!(item.owner.len() == 1, Error::<T>::BadCreateRefungibleCall,);1732		let item_owner = item.owner.first().expect("only one owner is defined");17331734		let value = item_owner.fraction;1735		let owner = item_owner.owner.clone();17361737		Self::add_token_index(collection, current_index, &owner)?;17381739		<ItemListIndex>::insert(collection_id, current_index);1740		<ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);17411742		// Update balance1743		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1744			.checked_add(value)1745			.ok_or(Error::<T>::NumOverflow)?;1746		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);17471748		Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));1749		Ok(())1750	}17511752	fn add_nft_item(1753		collection: &CollectionHandle<T>,1754		item: NftItemType<T::CrossAccountId>,1755	) -> DispatchResult {1756		let collection_id = collection.id;17571758		let current_index = <ItemListIndex>::get(collection_id)1759			.checked_add(1)1760			.ok_or(Error::<T>::NumOverflow)?;17611762		let item_owner = item.owner.clone();1763		Self::add_token_index(collection, current_index, &item.owner)?;17641765		<ItemListIndex>::insert(collection_id, current_index);1766		<NftItemList<T>>::insert(collection_id, current_index, item);17671768		// Update balance1769		let new_balance = <Balance<T>>::get(collection_id, item_owner.as_sub())1770			.checked_add(1)1771			.ok_or(Error::<T>::NumOverflow)?;1772		<Balance<T>>::insert(collection_id, item_owner.as_sub(), new_balance);17731774		collection.log(ERC721Events::Transfer {1775			from: H160::default(),1776			to: *item_owner.as_eth(),1777			token_id: current_index.into(),1778		})?;1779		Self::deposit_event(RawEvent::ItemCreated(1780			collection_id,1781			current_index,1782			item_owner,1783		));1784		Ok(())1785	}17861787	fn burn_refungible_item(1788		collection: &CollectionHandle<T>,1789		item_id: TokenId,1790		owner: &T::CrossAccountId,1791	) -> DispatchResult {1792		let collection_id = collection.id;17931794		let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id)1795			.ok_or(Error::<T>::TokenNotFound)?;1796		let rft_balance = token1797			.owner1798			.iter()1799			.find(|&i| i.owner == *owner)1800			.ok_or(Error::<T>::TokenNotFound)?;1801		Self::remove_token_index(collection, item_id, owner)?;18021803		// update balance1804		let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.as_sub())1805			.checked_sub(rft_balance.fraction)1806			.ok_or(Error::<T>::NumOverflow)?;1807		<Balance<T>>::insert(collection_id, rft_balance.owner.as_sub(), new_balance);18081809		// Re-create owners list with sender removed1810		let index = token1811			.owner1812			.iter()1813			.position(|i| i.owner == *owner)1814			.expect("owned item is exists");1815		token.owner.remove(index);1816		let owner_count = token.owner.len();18171818		// Burn the token completely if this was the last (only) owner1819		if owner_count == 0 {1820			<ReFungibleItemList<T>>::remove(collection_id, item_id);1821			<VariableMetaDataBasket<T>>::remove(collection_id, item_id);1822		} else {1823			<ReFungibleItemList<T>>::insert(collection_id, item_id, token);1824		}18251826		Ok(())1827	}18281829	fn burn_nft_item(collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1830		let collection_id = collection.id;18311832		let item =1833			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;1834		Self::remove_token_index(collection, item_id, &item.owner)?;18351836		// update balance1837		let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())1838			.checked_sub(1)1839			.ok_or(Error::<T>::NumOverflow)?;1840		<Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);1841		<NftItemList<T>>::remove(collection_id, item_id);1842		<VariableMetaDataBasket<T>>::remove(collection_id, item_id);18431844		collection.log(ERC721Events::Transfer {1845			from: *item.owner.as_eth(),1846			to: H160::default(),1847			token_id: item_id.into(),1848		})?;1849		Self::deposit_event(RawEvent::ItemDestroyed(collection.id, item_id));1850		Ok(())1851	}18521853	fn burn_fungible_item(1854		owner: &T::CrossAccountId,1855		collection: &CollectionHandle<T>,1856		value: u128,1857	) -> DispatchResult {1858		let collection_id = collection.id;18591860		let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());1861		ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);18621863		// update balance1864		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1865			.checked_sub(value)1866			.ok_or(Error::<T>::NumOverflow)?;1867		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);18681869		if balance.value - value > 0 {1870			balance.value -= value;1871			<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);1872		} else {1873			<FungibleItemList<T>>::remove(collection_id, owner.as_sub());1874		}18751876		collection.log(ERC20Events::Transfer {1877			from: *owner.as_eth(),1878			to: H160::default(),1879			value: value.into(),1880		})?;1881		Ok(())1882	}18831884	pub fn get_collection(1885		collection_id: CollectionId,1886	) -> Result<CollectionHandle<T>, sp_runtime::DispatchError> {1887		Ok(<CollectionHandle<T>>::get(collection_id).ok_or(Error::<T>::CollectionNotFound)?)1888	}18891890	fn check_owner_permissions(1891		target_collection: &CollectionHandle<T>,1892		subject: &T::AccountId,1893	) -> DispatchResult {1894		ensure!(1895			*subject == target_collection.owner,1896			Error::<T>::NoPermission1897		);18981899		Ok(())1900	}19011902	fn is_owner_or_admin_permissions(1903		collection: &CollectionHandle<T>,1904		subject: &T::CrossAccountId,1905	) -> Result<bool, DispatchError> {1906		collection.consume_sload()?;1907		Ok(*subject.as_sub() == collection.owner1908			|| <AdminList<T>>::get(collection.id).contains(subject))1909	}19101911	fn check_owner_or_admin_permissions(1912		collection: &CollectionHandle<T>,1913		subject: &T::CrossAccountId,1914	) -> DispatchResult {1915		ensure!(1916			Self::is_owner_or_admin_permissions(collection, subject)?,1917			Error::<T>::NoPermission1918		);19191920		Ok(())1921	}19221923	fn owned_amount(1924		subject: &T::CrossAccountId,1925		collection: &CollectionHandle<T>,1926		item_id: TokenId,1927	) -> Result<Option<u128>, DispatchError> {1928		collection.consume_sload()?;1929		Ok(Self::owned_amount_unchecked(subject, collection, item_id))1930	}19311932	fn owned_amount_unchecked(1933		subject: &T::CrossAccountId,1934		target_collection: &CollectionHandle<T>,1935		item_id: TokenId,1936	) -> Option<u128> {1937		let collection_id = target_collection.id;19381939		match target_collection.mode {1940			CollectionMode::NFT => {1941				(<NftItemList<T>>::get(collection_id, item_id)?.owner == *subject).then(|| 1)1942			}1943			CollectionMode::Fungible(_) => {1944				Some(<FungibleItemList<T>>::get(collection_id, &subject.as_sub()).value)1945			}1946			CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)?1947				.owner1948				.iter()1949				.find(|i| i.owner == *subject)1950				.map(|i| i.fraction),1951			CollectionMode::Invalid => None,1952		}1953	}19541955	fn is_item_owner(1956		subject: &T::CrossAccountId,1957		target_collection: &CollectionHandle<T>,1958		item_id: TokenId,1959	) -> Result<bool, DispatchError> {1960		Ok(match target_collection.mode {1961			CollectionMode::Fungible(_) => true,1962			_ => Self::owned_amount(subject, target_collection, item_id)?.is_some(),1963		})1964	}19651966	fn check_white_list(1967		collection: &CollectionHandle<T>,1968		address: &T::CrossAccountId,1969	) -> DispatchResult {1970		collection.consume_sload()?;1971		ensure!(1972			<WhiteList<T>>::contains_key(collection.id, address.as_sub()),1973			Error::<T>::AddresNotInWhiteList,1974		);1975		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		collection.consume_sload()?;2004		collection.consume_sload()?;2005		let mut recipient_balance = <FungibleItemList<T>>::get(collection_id, recipient.as_sub());2006		let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());20072008		recipient_balance.value = recipient_balance2009			.value2010			.checked_add(value)2011			.ok_or(Error::<T>::NumOverflow)?;2012		balance.value = balance2013			.value2014			.checked_sub(value)2015			.ok_or(Error::<T>::TokenValueTooLow)?;20162017		// update balanceOf2018		collection.consume_sstore()?;2019		collection.consume_sstore()?;2020		if balance.value != 0 {2021			<Balance<T>>::insert(collection_id, owner.as_sub(), balance.value);2022		} else {2023			<Balance<T>>::remove(collection_id, owner.as_sub());2024		}2025		<Balance<T>>::insert(collection_id, recipient.as_sub(), recipient_balance.value);20262027		// Reduce or remove sender2028		collection.consume_sstore()?;2029		collection.consume_sstore()?;2030		if balance.value != 0 {2031			<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);2032		} else {2033			<FungibleItemList<T>>::remove(collection_id, owner.as_sub());2034		}2035		<FungibleItemList<T>>::insert(collection_id, recipient.as_sub(), recipient_balance);20362037		collection.log(ERC20Events::Transfer {2038			from: *owner.as_eth(),2039			to: *recipient.as_eth(),2040			value: value.into(),2041		})?;2042		Self::deposit_event(RawEvent::Transfer(2043			collection.id,2044			1,2045			owner.clone(),2046			recipient.clone(),2047			value,2048		));20492050		Ok(())2051	}20522053	fn transfer_refungible(2054		collection: &CollectionHandle<T>,2055		item_id: TokenId,2056		value: u128,2057		owner: T::CrossAccountId,2058		new_owner: T::CrossAccountId,2059	) -> DispatchResult {2060		let collection_id = collection.id;2061		collection.consume_sload()?;2062		let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id)2063			.ok_or(Error::<T>::TokenNotFound)?;20642065		let item = full_item2066			.owner2067			.iter()2068			.find(|i| i.owner == owner)2069			.ok_or(Error::<T>::TokenNotFound)?;2070		let amount = item.fraction;20712072		ensure!(amount >= value, Error::<T>::TokenValueTooLow);20732074		collection.consume_sload()?;2075		// update balance2076		let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2077			.checked_sub(value)2078			.ok_or(Error::<T>::NumOverflow)?;2079		collection.consume_sstore()?;2080		<Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);20812082		collection.consume_sload()?;2083		let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2084			.checked_add(value)2085			.ok_or(Error::<T>::NumOverflow)?;2086		collection.consume_sstore()?;2087		<Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);20882089		let old_owner = item.owner.clone();2090		let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);20912092		let mut new_full_item = full_item.clone();2093		// transfer2094		if amount == value && !new_owner_has_account {2095			// change owner2096			// new owner do not have account2097			new_full_item2098				.owner2099				.iter_mut()2100				.find(|i| i.owner == owner)2101				.expect("old owner does present in refungible")2102				.owner = new_owner.clone();2103			collection.consume_sstore()?;2104			<ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);21052106			// update index collection2107			Self::move_token_index(collection, item_id, &old_owner, &new_owner)?;2108		} else {2109			new_full_item2110				.owner2111				.iter_mut()2112				.find(|i| i.owner == owner)2113				.expect("old owner does present in refungible")2114				.fraction -= value;21152116			// separate amount2117			if new_owner_has_account {2118				// new owner has account2119				new_full_item2120					.owner2121					.iter_mut()2122					.find(|i| i.owner == new_owner)2123					.expect("new owner has account")2124					.fraction += value;2125			} else {2126				// new owner do not have account2127				new_full_item.owner.push(Ownership {2128					owner: new_owner.clone(),2129					fraction: value,2130				});2131				Self::add_token_index(collection, item_id, &new_owner)?;2132			}21332134			collection.consume_sstore()?;2135			<ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2136		}21372138		Self::deposit_event(RawEvent::Transfer(2139			collection.id,2140			item_id,2141			owner,2142			new_owner,2143			amount,2144		));21452146		Ok(())2147	}21482149	fn transfer_nft(2150		collection: &CollectionHandle<T>,2151		item_id: TokenId,2152		sender: T::CrossAccountId,2153		new_owner: T::CrossAccountId,2154	) -> DispatchResult {2155		let collection_id = collection.id;2156		collection.consume_sload()?;2157		let mut item =2158			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;21592160		ensure!(sender == item.owner, Error::<T>::MustBeTokenOwner);21612162		collection.consume_sload()?;2163		// update balance2164		let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2165			.checked_sub(1)2166			.ok_or(Error::<T>::NumOverflow)?;2167		collection.consume_sstore()?;2168		<Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);21692170		collection.consume_sload()?;2171		let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2172			.checked_add(1)2173			.ok_or(Error::<T>::NumOverflow)?;2174		collection.consume_sstore()?;2175		<Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);21762177		// change owner2178		let old_owner = item.owner.clone();2179		item.owner = new_owner.clone();2180		collection.consume_sstore()?;2181		<NftItemList<T>>::insert(collection_id, item_id, item);21822183		// update index collection2184		Self::move_token_index(collection, item_id, &old_owner, &new_owner)?;21852186		collection.log(ERC721Events::Transfer {2187			from: *sender.as_eth(),2188			to: *new_owner.as_eth(),2189			token_id: item_id.into(),2190		})?;2191		Self::deposit_event(RawEvent::Transfer(2192			collection.id,2193			item_id,2194			sender,2195			new_owner,2196			1,2197		));21982199		Ok(())2200	}22012202	fn set_re_fungible_variable_data(2203		collection: &CollectionHandle<T>,2204		item_id: TokenId,2205		data: Vec<u8>,2206	) -> DispatchResult {2207		let collection_id = collection.id;2208		let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id)2209			.ok_or(Error::<T>::TokenNotFound)?;22102211		item.variable_data = data;22122213		<ReFungibleItemList<T>>::insert(collection_id, item_id, item);22142215		Ok(())2216	}22172218	fn set_nft_variable_data(2219		collection: &CollectionHandle<T>,2220		item_id: TokenId,2221		data: Vec<u8>,2222	) -> DispatchResult {2223		let collection_id = collection.id;2224		let mut item =2225			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;22262227		item.variable_data = data;22282229		<NftItemList<T>>::insert(collection_id, item_id, item);22302231		Ok(())2232	}22332234	#[allow(dead_code)]2235	fn init_collection(item: &Collection<T>) {2236		// check params2237		assert!(2238			item.decimal_points <= MAX_DECIMAL_POINTS,2239			"decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2240		);2241		assert!(2242			item.name.len() <= 64,2243			"Collection name can not be longer than 63 char"2244		);2245		assert!(2246			item.name.len() <= 256,2247			"Collection description can not be longer than 255 char"2248		);2249		assert!(2250			item.token_prefix.len() <= 16,2251			"Token prefix can not be longer than 15 char"2252		);22532254		// Generate next collection ID2255		let next_id = CreatedCollectionCount::get().checked_add(1).unwrap();22562257		CreatedCollectionCount::put(next_id);2258	}22592260	#[allow(dead_code)]2261	fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::CrossAccountId>) {2262		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();22632264		Self::add_token_index(2265			&CollectionHandle::get(collection_id).unwrap(),2266			current_index,2267			&item.owner,2268		)2269		.unwrap();22702271		<ItemListIndex>::insert(collection_id, current_index);22722273		// Update balance2274		let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2275			.checked_add(1)2276			.unwrap();2277		<Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2278	}22792280	#[allow(dead_code)]2281	fn init_fungible_token(2282		collection_id: CollectionId,2283		owner: &T::CrossAccountId,2284		item: &FungibleItemType,2285	) {2286		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();22872288		Self::add_token_index(2289			&CollectionHandle::get(collection_id).unwrap(),2290			current_index,2291			owner,2292		)2293		.unwrap();22942295		<ItemListIndex>::insert(collection_id, current_index);22962297		// Update balance2298		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2299			.checked_add(item.value)2300			.unwrap();2301		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2302	}23032304	#[allow(dead_code)]2305	fn init_refungible_token(2306		collection_id: CollectionId,2307		item: &ReFungibleItemType<T::CrossAccountId>,2308	) {2309		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();23102311		let value = item.owner.first().unwrap().fraction;2312		let owner = item.owner.first().unwrap().owner.clone();23132314		Self::add_token_index(2315			&CollectionHandle::get(collection_id).unwrap(),2316			current_index,2317			&owner,2318		)2319		.unwrap();23202321		<ItemListIndex>::insert(collection_id, current_index);23222323		// Update balance2324		let new_balance = <Balance<T>>::get(collection_id, &owner.as_sub())2325			.checked_add(value)2326			.unwrap();2327		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2328	}23292330	fn add_token_index(2331		collection: &CollectionHandle<T>,2332		item_index: TokenId,2333		owner: &T::CrossAccountId,2334	) -> DispatchResult {2335		// add to account limit2336		collection.consume_sload()?;2337		if <AccountItemCount<T>>::contains_key(owner.as_sub()) {2338			// bound Owned tokens by a single address2339			collection.consume_sload()?;2340			let count = <AccountItemCount<T>>::get(owner.as_sub());2341			ensure!(2342				count < ACCOUNT_TOKEN_OWNERSHIP_LIMIT,2343				Error::<T>::AddressOwnershipLimitExceeded2344			);23452346			collection.consume_sstore()?;2347			<AccountItemCount<T>>::insert(2348				owner.as_sub(),2349				count.checked_add(1).ok_or(Error::<T>::NumOverflow)?,2350			);2351		} else {2352			collection.consume_sstore()?;2353			<AccountItemCount<T>>::insert(owner.as_sub(), 1);2354		}23552356		collection.consume_sload()?;2357		let list_exists = <AddressTokens<T>>::contains_key(collection.id, owner.as_sub());2358		if list_exists {2359			collection.consume_sload()?;2360			let mut list = <AddressTokens<T>>::get(collection.id, owner.as_sub());2361			let item_contains = list.contains(&item_index.clone());23622363			if !item_contains {2364				list.push(item_index);2365			}23662367			collection.consume_sstore()?;2368			<AddressTokens<T>>::insert(collection.id, owner.as_sub(), list);2369		} else {2370			let itm = vec![item_index];2371			collection.consume_sstore()?;2372			<AddressTokens<T>>::insert(collection.id, owner.as_sub(), itm);2373		}23742375		Ok(())2376	}23772378	fn remove_token_index(2379		collection: &CollectionHandle<T>,2380		item_index: TokenId,2381		owner: &T::CrossAccountId,2382	) -> DispatchResult {2383		// update counter2384		collection.consume_sload()?;2385		collection.consume_sstore()?;2386		<AccountItemCount<T>>::insert(2387			owner.as_sub(),2388			<AccountItemCount<T>>::get(owner.as_sub())2389				.checked_sub(1)2390				.ok_or(Error::<T>::NumOverflow)?,2391		);23922393		collection.consume_sload()?;2394		let list_exists = <AddressTokens<T>>::contains_key(collection.id, owner.as_sub());2395		if list_exists {2396			collection.consume_sload()?;2397			let mut list = <AddressTokens<T>>::get(collection.id, owner.as_sub());2398			let item_contains = list.contains(&item_index.clone());23992400			if item_contains {2401				list.retain(|&item| item != item_index);2402				collection.consume_sstore()?;2403				<AddressTokens<T>>::insert(collection.id, owner.as_sub(), list);2404			}2405		}24062407		Ok(())2408	}24092410	fn move_token_index(2411		collection: &CollectionHandle<T>,2412		item_index: TokenId,2413		old_owner: &T::CrossAccountId,2414		new_owner: &T::CrossAccountId,2415	) -> DispatchResult {2416		Self::remove_token_index(collection, item_index, old_owner)?;2417		Self::add_token_index(collection, item_index, new_owner)?;24182419		Ok(())2420	}2421}24222423sp_api::decl_runtime_apis! {2424	pub trait NftApi {2425		/// Used for ethereum integration2426		fn eth_contract_code(account: H160) -> Option<Vec<u8>>;2427	}2428}
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};35use sp_core::H160;36use sp_std::vec;37use sp_runtime::{DispatchError, sp_std::prelude::Vec};38use core::ops::{Deref, DerefMut};39use nft_data_structs::{40	MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, MAX_REFUNGIBLE_PIECES,41	CUSTOM_DATA_LIMIT, COLLECTION_NUMBER_LIMIT, ACCOUNT_TOKEN_OWNERSHIP_LIMIT,42	VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, COLLECTION_ADMINS_LIMIT,43	OFFCHAIN_SCHEMA_LIMIT, AccessMode, Collection, CreateItemData, CollectionLimits, CollectionId,44	CollectionMode, TokenId, SchemaVersion, SponsorshipState, Ownership, NftItemType,45	FungibleItemType, ReFungibleItemType,46};4748#[cfg(test)]49mod mock;5051#[cfg(test)]52mod tests;5354mod default_weights;55mod eth;56mod sponsorship;57pub use sponsorship::NftSponsorshipHandler;58pub use eth::sponsoring::NftEthSponsorshipHandler;5960pub use eth::NftErcSupport;61pub use eth::account::*;62use eth::erc::{ERC20Events, ERC721Events};6364#[cfg(feature = "runtime-benchmarks")]65mod benchmarking;6667pub trait WeightInfo {68	fn create_collection() -> Weight;69	fn destroy_collection() -> Weight;70	fn add_to_white_list() -> Weight;71	fn remove_from_white_list() -> Weight;72	fn set_public_access_mode() -> Weight;73	fn set_mint_permission() -> Weight;74	fn change_collection_owner() -> Weight;75	fn add_collection_admin() -> Weight;76	fn remove_collection_admin() -> Weight;77	fn set_collection_sponsor() -> Weight;78	fn confirm_sponsorship() -> Weight;79	fn remove_collection_sponsor() -> Weight;80	fn create_item(s: usize) -> Weight;81	fn burn_item() -> Weight;82	fn transfer() -> Weight;83	fn approve() -> Weight;84	fn transfer_from() -> Weight;85	fn set_offchain_schema() -> Weight;86	fn set_const_on_chain_schema() -> Weight;87	fn set_variable_on_chain_schema() -> Weight;88	fn set_variable_meta_data() -> Weight;89	fn enable_contract_sponsoring() -> Weight;90	fn set_schema_version() -> Weight;91	fn set_contract_sponsoring_rate_limit() -> Weight;92	fn set_variable_meta_data_sponsoring_rate_limit() -> Weight;93	fn toggle_contract_white_list() -> Weight;94	fn add_to_contract_white_list() -> Weight;95	fn remove_from_contract_white_list() -> Weight;96	fn set_collection_limits() -> Weight;97}9899decl_error! {100	/// Error for non-fungible-token module.101	pub enum Error for Module<T: Config> {102		/// Total collections bound exceeded.103		TotalCollectionsLimitExceeded,104		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.105		CollectionDecimalPointLimitExceeded,106		/// Collection name can not be longer than 63 char.107		CollectionNameLimitExceeded,108		/// Collection description can not be longer than 255 char.109		CollectionDescriptionLimitExceeded,110		/// Token prefix can not be longer than 15 char.111		CollectionTokenPrefixLimitExceeded,112		/// This collection does not exist.113		CollectionNotFound,114		/// Item not exists.115		TokenNotFound,116		/// Admin not found117		AdminNotFound,118		/// Arithmetic calculation overflow.119		NumOverflow,120		/// Account already has admin role.121		AlreadyAdmin,122		/// You do not own this collection.123		NoPermission,124		/// This address is not set as sponsor, use setCollectionSponsor first.125		ConfirmUnsetSponsorFail,126		/// Collection is not in mint mode.127		PublicMintingNotAllowed,128		/// Sender parameter and item owner must be equal.129		MustBeTokenOwner,130		/// Item balance not enough.131		TokenValueTooLow,132		/// Size of item is too large.133		NftSizeLimitExceeded,134		/// No approve found135		ApproveNotFound,136		/// Requested value more than approved.137		TokenValueNotEnough,138		/// Only approved addresses can call this method.139		ApproveRequired,140		/// Address is not in white list.141		AddresNotInWhiteList,142		/// Number of collection admins bound exceeded.143		CollectionAdminsLimitExceeded,144		/// Owned tokens by a single address bound exceeded.145		AddressOwnershipLimitExceeded,146		/// Length of items properties must be greater than 0.147		EmptyArgument,148		/// const_data exceeded data limit.149		TokenConstDataLimitExceeded,150		/// variable_data exceeded data limit.151		TokenVariableDataLimitExceeded,152		/// Not NFT item data used to mint in NFT collection.153		NotNftDataUsedToMintNftCollectionToken,154		/// Not Fungible item data used to mint in Fungible collection.155		NotFungibleDataUsedToMintFungibleCollectionToken,156		/// Not Re Fungible item data used to mint in Re Fungible collection.157		NotReFungibleDataUsedToMintReFungibleCollectionToken,158		/// Unexpected collection type.159		UnexpectedCollectionType,160		/// Can't store metadata in fungible tokens.161		CantStoreMetadataInFungibleTokens,162		/// Collection token limit exceeded163		CollectionTokenLimitExceeded,164		/// Account token limit exceeded per collection165		AccountTokenLimitExceeded,166		/// Collection limit bounds per collection exceeded167		CollectionLimitBoundsExceeded,168		/// Tried to enable permissions which are only permitted to be disabled169		OwnerPermissionsCantBeReverted,170		/// Schema data size limit bound exceeded171		SchemaDataLimitExceeded,172		/// Maximum refungibility exceeded173		WrongRefungiblePieces,174		/// createRefungible should be called with one owner175		BadCreateRefungibleCall,176		/// Gas limit exceeded177		OutOfGas,178		/// Collection settings not allowing items transferring179		TransferNotAllowed,180		/// Can't transfer tokens to ethereum zero address181		AddressIsZero,182	}183}184185#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]186pub struct CollectionHandle<T: Config> {187	pub id: CollectionId,188	collection: Collection<T>,189	recorder: pallet_evm_coder_substrate::SubstrateRecorder<T>,190}191impl<T: Config> CollectionHandle<T> {192	pub fn get_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {193		<CollectionById<T>>::get(id).map(|collection| Self {194			id,195			collection,196			recorder: pallet_evm_coder_substrate::SubstrateRecorder::new(197				eth::collection_id_to_address(id),198				gas_limit,199			),200		})201	}202	pub fn get(id: CollectionId) -> Option<Self> {203		Self::get_with_gas_limit(id, u64::MAX)204	}205	pub fn log(&self, log: impl evm_coder::ToLog) -> DispatchResult {206		self.recorder.log_sub(log)207	}208	#[allow(dead_code)]209	fn consume_gas(&self, gas: u64) -> DispatchResult {210		self.recorder.consume_gas_sub(gas)211	}212	fn consume_sload(&self) -> DispatchResult {213		self.recorder.consume_sload_sub()214	}215	fn consume_sstore(&self) -> DispatchResult {216		self.recorder.consume_sstore_sub()217	}218	pub fn submit_logs(self) -> DispatchResult {219		self.recorder.submit_logs()220	}221	pub fn save(self) -> DispatchResult {222		self.recorder.submit_logs()?;223		<CollectionById<T>>::insert(self.id, self.collection);224		Ok(())225	}226}227impl<T: Config> Deref for CollectionHandle<T> {228	type Target = Collection<T>;229230	fn deref(&self) -> &Self::Target {231		&self.collection232	}233}234235impl<T: Config> DerefMut for CollectionHandle<T> {236	fn deref_mut(&mut self) -> &mut Self::Target {237		&mut self.collection238	}239}240241pub trait Config: system::Config + pallet_evm_coder_substrate::Config + Sized {242	type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;243244	/// Weight information for extrinsics in this pallet.245	type WeightInfo: WeightInfo;246247	type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;248	type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;249250	type CrossAccountId: CrossAccountId<Self::AccountId>;251	type Currency: Currency<Self::AccountId>;252	type CollectionCreationPrice: Get<253		<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,254	>;255	type TreasuryAccountId: Get<Self::AccountId>;256}257258// # Used definitions259//260// ## User control levels261//262// chain-controlled - key is uncontrolled by user263//                    i.e autoincrementing index264//                    can use non-cryptographic hash265// real - key is controlled by user266//        but it is hard to generate enough colliding values, i.e owner of signed txs267//        can use non-cryptographic hash268// controlled - key is completly controlled by users269//              i.e maps with mutable keys270//              should use cryptographic hash271//272// ## User control level downgrade reasons273//274// ?1 - chain-controlled -> controlled275//      collections/tokens can be destroyed, resulting in massive holes276// ?2 - chain-controlled -> controlled277//      same as ?1, but can be only added, resulting in easier exploitation278// ?3 - real -> controlled279//      no confirmation required, so addresses can be easily generated280decl_storage! {281	trait Store for Module<T: Config> as Nft {282283		//#region Private members284		/// Id of next collection285		CreatedCollectionCount: u32;286		/// Used for migrations287		ChainVersion: u64;288		/// Id of last collection token289		/// Collection id (controlled?1)290		ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;291		//#endregion292293		//#region Bound counters294		/// Amount of collections destroyed, used for total amount tracking with295		/// CreatedCollectionCount296		DestroyedCollectionCount: u32;297		/// Total amount of account owned tokens (NFTs + RFTs + unique fungibles)298		/// Account id (real)299		pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;300		//#endregion301302		//#region Basic collections303		/// Collection info304		/// Collection id (controlled?1)305		pub CollectionById get(fn collection_id) config(): map hasher(blake2_128_concat) CollectionId => Option<Collection<T>> = None;306		/// List of collection admins307		/// Collection id (controlled?2)308		pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec<T::CrossAccountId>;309		/// Whitelisted collection users310		/// Collection id (controlled?2), user id (controlled?3)311		pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool;312		//#endregion313314		/// How many of collection items user have315		/// Collection id (controlled?2), account id (real)316		pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128;317318		/// Amount of items which spender can transfer out of owners account (via transferFrom)319		/// Collection id (controlled?2), (token id (controlled ?2) + owner account id (real) + spender account id (controlled?3))320		/// TODO: Off chain worker should remove from this map when token gets removed321		pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128;322323		//#region Item collections324		/// Collection id (controlled?2), token id (controlled?1)325		pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<NftItemType<T::CrossAccountId>>;326		/// Collection id (controlled?2), owner (controlled?2)327		pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType;328		/// Collection id (controlled?2), token id (controlled?1)329		pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<ReFungibleItemType<T::CrossAccountId>>;330		//#endregion331332		//#region Index list333		/// Collection id (controlled?2), tokens owner (controlled?2)334		pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;335		//#endregion336337		//#region Tokens transfer rate limit baskets338		/// (Collection id (controlled?2), who created (real))339		/// TODO: Off chain worker should remove from this map when collection gets removed340		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;341		/// Collection id (controlled?2), token id (controlled?2)342		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;343		/// Collection id (controlled?2), owning user (real)344		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;345		/// Collection id (controlled?2), token id (controlled?2)346		pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;347		//#endregion348349		/// Variable metadata sponsoring350		/// Collection id (controlled?2), token id (controlled?2)351		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;352	}353	add_extra_genesis {354		build(|config: &GenesisConfig<T>| {355			// Modification of storage356			for (_num, _c) in &config.collection_id {357				<Module<T>>::init_collection(_c);358			}359360			for (_num, _c, _i) in &config.nft_item_id {361				<Module<T>>::init_nft_token(*_c, _i);362			}363364			for (collection_id, account_id, fungible_item) in &config.fungible_item_id {365				<Module<T>>::init_fungible_token(*collection_id, &T::CrossAccountId::from_sub(account_id.clone()), fungible_item);366			}367368			for (_num, _c, _i) in &config.refungible_item_id {369				<Module<T>>::init_refungible_token(*_c, _i);370			}371		})372	}373}374375decl_event!(376	pub enum Event<T>377	where378		AccountId = <T as frame_system::Config>::AccountId,379		CrossAccountId = <T as Config>::CrossAccountId,380	{381		/// New collection was created382		///383		/// # Arguments384		///385		/// * collection_id: Globally unique identifier of newly created collection.386		///387		/// * mode: [CollectionMode] converted into u8.388		///389		/// * account_id: Collection owner.390		CollectionCreated(CollectionId, u8, AccountId),391392		/// New item was created.393		///394		/// # Arguments395		///396		/// * collection_id: Id of the collection where item was created.397		///398		/// * item_id: Id of an item. Unique within the collection.399		///400		/// * recipient: Owner of newly created item401		ItemCreated(CollectionId, TokenId, CrossAccountId),402403		/// Collection item was burned.404		///405		/// # Arguments406		///407		/// collection_id.408		///409		/// item_id: Identifier of burned NFT.410		ItemDestroyed(CollectionId, TokenId),411412		/// Item was transferred413		///414		/// * collection_id: Id of collection to which item is belong415		///416		/// * item_id: Id of an item417		///418		/// * sender: Original owner of item419		///420		/// * recipient: New owner of item421		///422		/// * amount: Always 1 for NFT423		Transfer(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),424425		/// * collection_id426		///427		/// * item_id428		///429		/// * sender430		///431		/// * spender432		///433		/// * amount434		Approved(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),435	}436);437438decl_module! {439	pub struct Module<T: Config> for enum Call440	where441		origin: T::Origin442	{443		fn deposit_event() = default;444		const CollectionAdminsLimit: u64 = COLLECTION_ADMINS_LIMIT;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 created_count = CreatedCollectionCount::get();497			let destroyed_count = DestroyedCollectionCount::get();498499			// bound Total number of collections500			ensure!(created_count - destroyed_count < COLLECTION_NUMBER_LIMIT, Error::<T>::TotalCollectionsLimitExceeded);501502			// check params503			ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);504			ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);505			ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);506			ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);507508			// Generate next collection ID509			let next_id = created_count510				.checked_add(1)511				.ok_or(Error::<T>::NumOverflow)?;512513			CreatedCollectionCount::put(next_id);514515			let limits = CollectionLimits {516				sponsored_data_size: CUSTOM_DATA_LIMIT,517				..Default::default()518			};519520			// Create new collection521			let new_collection = Collection {522				owner: who.clone(),523				name: collection_name,524				mode: mode.clone(),525				mint_mode: false,526				access: AccessMode::Normal,527				description: collection_description,528				decimal_points,529				token_prefix,530				offchain_schema: Vec::new(),531				schema_version: SchemaVersion::ImageURL,532				sponsorship: SponsorshipState::Disabled,533				variable_on_chain_schema: Vec::new(),534				const_on_chain_schema: Vec::new(),535				limits,536				transfers_enabled: true,537			};538539			// Add new collection to map540			<CollectionById<T>>::insert(next_id, new_collection);541542			// call event543			Self::deposit_event(RawEvent::CollectionCreated(next_id, mode.id(), who));544545			Ok(())546		}547548		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.549		///550		/// # Permissions551		///552		/// * Collection Owner.553		///554		/// # Arguments555		///556		/// * collection_id: collection to destroy.557		#[weight = <T as Config>::WeightInfo::destroy_collection()]558		#[transactional]559		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {560561			let sender = ensure_signed(origin)?;562			let collection = Self::get_collection(collection_id)?;563			Self::check_owner_permissions(&collection, &sender)?;564			if !collection.limits.owner_can_destroy {565				fail!(Error::<T>::NoPermission);566			}567568			<AddressTokens<T>>::remove_prefix(collection_id, None);569			<Allowances<T>>::remove_prefix(collection_id, None);570			<Balance<T>>::remove_prefix(collection_id, None);571			<ItemListIndex>::remove(collection_id);572			<AdminList<T>>::remove(collection_id);573			<CollectionById<T>>::remove(collection_id);574			<WhiteList<T>>::remove_prefix(collection_id, None);575576			<NftItemList<T>>::remove_prefix(collection_id, None);577			<FungibleItemList<T>>::remove_prefix(collection_id, None);578			<ReFungibleItemList<T>>::remove_prefix(collection_id, None);579580			<NftTransferBasket<T>>::remove_prefix(collection_id, None);581			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);582			<ReFungibleTransferBasket<T>>::remove_prefix(collection_id, None);583584			<VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);585586			DestroyedCollectionCount::put(DestroyedCollectionCount::get()587				.checked_add(1)588				.ok_or(Error::<T>::NumOverflow)?);589590			Ok(())591		}592593		/// Add an address to white list.594		///595		/// # Permissions596		///597		/// * Collection Owner598		/// * Collection Admin599		///600		/// # Arguments601		///602		/// * collection_id.603		///604		/// * address.605		#[weight = <T as Config>::WeightInfo::add_to_white_list()]606		#[transactional]607		pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{608609			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);610			let collection = Self::get_collection(collection_id)?;611612			Self::toggle_white_list_internal(613				&sender,614				&collection,615				&address,616				true,617			)?;618619			Ok(())620		}621622		/// Remove an address from white list.623		///624		/// # Permissions625		///626		/// * Collection Owner627		/// * Collection Admin628		///629		/// # Arguments630		///631		/// * collection_id.632		///633		/// * address.634		#[weight = <T as Config>::WeightInfo::remove_from_white_list()]635		#[transactional]636		pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{637638			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);639			let collection = Self::get_collection(collection_id)?;640641			Self::toggle_white_list_internal(642				&sender,643				&collection,644				&address,645				false,646			)?;647648			Ok(())649		}650651		/// Toggle between normal and white list access for the methods with access for `Anyone`.652		///653		/// # Permissions654		///655		/// * Collection Owner.656		///657		/// # Arguments658		///659		/// * collection_id.660		///661		/// * mode: [AccessMode]662		#[weight = <T as Config>::WeightInfo::set_public_access_mode()]663		#[transactional]664		pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult665		{666			let sender = ensure_signed(origin)?;667668			let mut target_collection = Self::get_collection(collection_id)?;669			Self::check_owner_permissions(&target_collection, &sender)?;670			target_collection.access = mode;671			target_collection.save()672		}673674		/// Allows Anyone to create tokens if:675		/// * White List is enabled, and676		/// * Address is added to white list, and677		/// * This method was called with True parameter678		///679		/// # Permissions680		/// * Collection Owner681		///682		/// # Arguments683		///684		/// * collection_id.685		///686		/// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.687		#[weight = <T as Config>::WeightInfo::set_mint_permission()]688		#[transactional]689		pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult690		{691			let sender = ensure_signed(origin)?;692693			let mut target_collection = Self::get_collection(collection_id)?;694			Self::check_owner_permissions(&target_collection, &sender)?;695			target_collection.mint_mode = mint_permission;696			target_collection.save()697		}698699		/// Change the owner of the collection.700		///701		/// # Permissions702		///703		/// * Collection Owner.704		///705		/// # Arguments706		///707		/// * collection_id.708		///709		/// * new_owner.710		#[weight = <T as Config>::WeightInfo::change_collection_owner()]711		#[transactional]712		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {713714			let sender = ensure_signed(origin)?;715			let mut target_collection = Self::get_collection(collection_id)?;716			Self::check_owner_permissions(&target_collection, &sender)?;717			target_collection.owner = new_owner;718			target_collection.save()719		}720721		/// Adds an admin of the Collection.722		/// 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.723		///724		/// # Permissions725		///726		/// * Collection Owner.727		/// * Collection Admin.728		///729		/// # Arguments730		///731		/// * collection_id: ID of the Collection to add admin for.732		///733		/// * new_admin_id: Address of new admin to add.734		#[weight = <T as Config>::WeightInfo::add_collection_admin()]735		#[transactional]736		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {737			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);738			let collection = Self::get_collection(collection_id)?;739			Self::check_owner_or_admin_permissions(&collection, &sender)?;740			let mut admin_arr = <AdminList<T>>::get(collection_id);741742			match admin_arr.binary_search(&new_admin_id) {743				Ok(_) => {},744				Err(idx) => {745					ensure!(admin_arr.len() < COLLECTION_ADMINS_LIMIT as usize, Error::<T>::CollectionAdminsLimitExceeded);746					admin_arr.insert(idx, new_admin_id);747					<AdminList<T>>::insert(collection_id, admin_arr);748				}749			}750			Ok(())751		}752753		/// 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.754		///755		/// # Permissions756		///757		/// * Collection Owner.758		/// * Collection Admin.759		///760		/// # Arguments761		///762		/// * collection_id: ID of the Collection to remove admin for.763		///764		/// * account_id: Address of admin to remove.765		#[weight = <T as Config>::WeightInfo::remove_collection_admin()]766		#[transactional]767		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {768			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);769			let collection = Self::get_collection(collection_id)?;770			Self::check_owner_or_admin_permissions(&collection, &sender)?;771			let mut admin_arr = <AdminList<T>>::get(collection_id);772773			if let Ok(idx) = admin_arr.binary_search(&account_id) {774				admin_arr.remove(idx);775				<AdminList<T>>::insert(collection_id, admin_arr);776			}777			Ok(())778		}779780		/// # Permissions781		///782		/// * Collection Owner783		///784		/// # Arguments785		///786		/// * collection_id.787		///788		/// * new_sponsor.789		#[weight = <T as Config>::WeightInfo::set_collection_sponsor()]790		#[transactional]791		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {792			let sender = ensure_signed(origin)?;793			let mut target_collection = Self::get_collection(collection_id)?;794			Self::check_owner_permissions(&target_collection, &sender)?;795796			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);797			target_collection.save()798		}799800		/// # Permissions801		///802		/// * Sponsor.803		///804		/// # Arguments805		///806		/// * collection_id.807		#[weight = <T as Config>::WeightInfo::confirm_sponsorship()]808		#[transactional]809		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {810			let sender = ensure_signed(origin)?;811812			let mut target_collection = Self::get_collection(collection_id)?;813			ensure!(814				target_collection.sponsorship.pending_sponsor() == Some(&sender),815				Error::<T>::ConfirmUnsetSponsorFail816			);817818			target_collection.sponsorship = SponsorshipState::Confirmed(sender);819			target_collection.save()820		}821822		/// Switch back to pay-per-own-transaction model.823		///824		/// # Permissions825		///826		/// * Collection owner.827		///828		/// # Arguments829		///830		/// * collection_id.831		#[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]832		#[transactional]833		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {834			let sender = ensure_signed(origin)?;835836			let mut target_collection = Self::get_collection(collection_id)?;837			Self::check_owner_permissions(&target_collection, &sender)?;838839			target_collection.sponsorship = SponsorshipState::Disabled;840			target_collection.save()841		}842843		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.844		///845		/// # Permissions846		///847		/// * Collection Owner.848		/// * Collection Admin.849		/// * Anyone if850		///     * White List is enabled, and851		///     * Address is added to white list, and852		///     * MintPermission is enabled (see SetMintPermission method)853		///854		/// # Arguments855		///856		/// * collection_id: ID of the collection.857		///858		/// * owner: Address, initial owner of the NFT.859		///860		/// * data: Token data to store on chain.861		// #[weight =862		// (130_000_000 as Weight)863		// .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))864		// .saturating_add(RocksDbWeight::get().reads(10 as Weight))865		// .saturating_add(RocksDbWeight::get().writes(8 as Weight))]866867		#[weight = <T as Config>::WeightInfo::create_item(data.data_size())]868		#[transactional]869		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResult {870			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);871			let collection = Self::get_collection(collection_id)?;872873			Self::create_item_internal(&sender, &collection, &owner, data)?;874875			collection.submit_logs()876		}877878		/// This method creates multiple items in a collection created with CreateCollection method.879		///880		/// # Permissions881		///882		/// * Collection Owner.883		/// * Collection Admin.884		/// * Anyone if885		///     * White List is enabled, and886		///     * Address is added to white list, and887		///     * MintPermission is enabled (see SetMintPermission method)888		///889		/// # Arguments890		///891		/// * collection_id: ID of the collection.892		///893		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].894		///895		/// * owner: Address, initial owner of the NFT.896		#[weight = <T as Config>::WeightInfo::create_item(items_data.iter()897							   .map(|data| { data.data_size() })898							   .sum())]899		#[transactional]900		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResult {901902			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);903			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);904			let collection = Self::get_collection(collection_id)?;905906			Self::create_multiple_items_internal(&sender, &collection, &owner, items_data)?;907908			collection.submit_logs()909		}910911		// TODO! transaction weight912913		/// Set transfers_enabled value for particular collection914		///915		/// # Permissions916		///917		/// * Collection Owner.918		///919		/// # Arguments920		///921		/// * collection_id: ID of the collection.922		///923		/// * value: New flag value.924		#[weight = <T as Config>::WeightInfo::burn_item()]925		#[transactional]926		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {927928			let sender = ensure_signed(origin)?;929			let mut target_collection = Self::get_collection(collection_id)?;930931			Self::check_owner_permissions(&target_collection, &sender)?;932933			target_collection.transfers_enabled = value;934			target_collection.save()935		}936937		/// Destroys a concrete instance of NFT.938		///939		/// # Permissions940		///941		/// * Collection Owner.942		/// * Collection Admin.943		/// * Current NFT Owner.944		///945		/// # Arguments946		///947		/// * collection_id: ID of the collection.948		///949		/// * item_id: ID of NFT to burn.950		#[weight = <T as Config>::WeightInfo::burn_item()]951		#[transactional]952		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {953954			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);955			let target_collection = Self::get_collection(collection_id)?;956957			Self::burn_item_internal(&sender, &target_collection, item_id, value)?;958959			target_collection.submit_logs()960		}961962		/// Change ownership of the token.963		///964		/// # Permissions965		///966		/// * Collection Owner967		/// * Collection Admin968		/// * Current NFT owner969		///970		/// # Arguments971		///972		/// * recipient: Address of token recipient.973		///974		/// * collection_id.975		///976		/// * item_id: ID of the item977		///     * Non-Fungible Mode: Required.978		///     * Fungible Mode: Ignored.979		///     * Re-Fungible Mode: Required.980		///981		/// * value: Amount to transfer.982		///     * Non-Fungible Mode: Ignored983		///     * Fungible Mode: Must specify transferred amount984		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)985		#[weight = <T as Config>::WeightInfo::transfer()]986		#[transactional]987		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {988			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);989			let collection = Self::get_collection(collection_id)?;990991			Self::transfer_internal(&sender, &recipient, &collection, item_id, value)?;992993			collection.submit_logs()994		}995996		/// Set, change, or remove approved address to transfer the ownership of the NFT.997		///998		/// # Permissions999		///1000		/// * Collection Owner1001		/// * Collection Admin1002		/// * Current NFT owner1003		///1004		/// # Arguments1005		///1006		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1007		///1008		/// * collection_id.1009		///1010		/// * item_id: ID of the item.1011		#[weight = <T as Config>::WeightInfo::approve()]1012		#[transactional]1013		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {1014			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1015			let collection = Self::get_collection(collection_id)?;10161017			Self::approve_internal(&sender, &spender, &collection, item_id, amount)?;10181019			collection.submit_logs()1020		}10211022		/// 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.1023		///1024		/// # Permissions1025		/// * Collection Owner1026		/// * Collection Admin1027		/// * Current NFT owner1028		/// * Address approved by current NFT owner1029		///1030		/// # Arguments1031		///1032		/// * from: Address that owns token.1033		///1034		/// * recipient: Address of token recipient.1035		///1036		/// * collection_id.1037		///1038		/// * item_id: ID of the item.1039		///1040		/// * value: Amount to transfer.1041		#[weight = <T as Config>::WeightInfo::transfer_from()]1042		#[transactional]1043		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {1044			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1045			let collection = Self::get_collection(collection_id)?;10461047			Self::transfer_from_internal(&sender, &from, &recipient, &collection, item_id, value)?;10481049			collection.submit_logs()1050		}1051		// #[weight = 0]1052		//     // let no_perm_mes = "You do not have permissions to modify this collection";1053		//     // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1054		//     // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1055		//     // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);10561057		//     // // on_nft_received  call10581059		//     // Self::transfer(origin, collection_id, item_id, new_owner)?;10601061		//     Ok(())1062		// }10631064		/// Set off-chain data schema.1065		///1066		/// # Permissions1067		///1068		/// * Collection Owner1069		/// * Collection Admin1070		///1071		/// # Arguments1072		///1073		/// * collection_id.1074		///1075		/// * schema: String representing the offchain data schema.1076		#[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1077		#[transactional]1078		pub fn set_variable_meta_data (1079			origin,1080			collection_id: CollectionId,1081			item_id: TokenId,1082			data: Vec<u8>1083		) -> DispatchResult {1084			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);10851086			let collection = Self::get_collection(collection_id)?;10871088			Self::set_variable_meta_data_internal(&sender, &collection, item_id, data)?;10891090			Ok(())1091		}10921093		/// Set schema standard1094		/// ImageURL1095		/// Unique1096		///1097		/// # Permissions1098		///1099		/// * Collection Owner1100		/// * Collection Admin1101		///1102		/// # Arguments1103		///1104		/// * collection_id.1105		///1106		/// * schema: SchemaVersion: enum1107		#[weight = <T as Config>::WeightInfo::set_schema_version()]1108		#[transactional]1109		pub fn set_schema_version(1110			origin,1111			collection_id: CollectionId,1112			version: SchemaVersion1113		) -> DispatchResult {1114			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1115			let mut target_collection = Self::get_collection(collection_id)?;1116			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;1117			target_collection.schema_version = version;1118			target_collection.save()1119		}11201121		/// Set off-chain data schema.1122		///1123		/// # Permissions1124		///1125		/// * Collection Owner1126		/// * Collection Admin1127		///1128		/// # Arguments1129		///1130		/// * collection_id.1131		///1132		/// * schema: String representing the offchain data schema.1133		#[weight = <T as Config>::WeightInfo::set_offchain_schema()]1134		#[transactional]1135		pub fn set_offchain_schema(1136			origin,1137			collection_id: CollectionId,1138			schema: Vec<u8>1139		) -> DispatchResult {1140			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1141			let mut target_collection = Self::get_collection(collection_id)?;1142			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11431144			// check schema limit1145			ensure!(schema.len() as u32 <= OFFCHAIN_SCHEMA_LIMIT, "");11461147			target_collection.offchain_schema = schema;1148			target_collection.save()1149		}11501151		/// Set const on-chain data schema.1152		///1153		/// # Permissions1154		///1155		/// * Collection Owner1156		/// * Collection Admin1157		///1158		/// # Arguments1159		///1160		/// * collection_id.1161		///1162		/// * schema: String representing the const on-chain data schema.1163		#[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1164		#[transactional]1165		pub fn set_const_on_chain_schema (1166			origin,1167			collection_id: CollectionId,1168			schema: Vec<u8>1169		) -> DispatchResult {1170			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1171			let mut target_collection = Self::get_collection(collection_id)?;1172			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11731174			// check schema limit1175			ensure!(schema.len() as u32 <= CONST_ON_CHAIN_SCHEMA_LIMIT, "");11761177			target_collection.const_on_chain_schema = schema;1178			target_collection.save()1179		}11801181		/// Set variable on-chain data schema.1182		///1183		/// # Permissions1184		///1185		/// * Collection Owner1186		/// * Collection Admin1187		///1188		/// # Arguments1189		///1190		/// * collection_id.1191		///1192		/// * schema: String representing the variable on-chain data schema.1193		#[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1194		#[transactional]1195		pub fn set_variable_on_chain_schema (1196			origin,1197			collection_id: CollectionId,1198			schema: Vec<u8>1199		) -> DispatchResult {1200			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1201			let mut target_collection = Self::get_collection(collection_id)?;1202			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;12031204			// check schema limit1205			ensure!(schema.len() as u32 <= VARIABLE_ON_CHAIN_SCHEMA_LIMIT, "");12061207			target_collection.variable_on_chain_schema = schema;1208			target_collection.save()1209		}12101211		#[weight = <T as Config>::WeightInfo::set_collection_limits()]1212		#[transactional]1213		pub fn set_collection_limits(1214			origin,1215			collection_id: u32,1216			new_limits: CollectionLimits<T::BlockNumber>,1217		) -> DispatchResult {1218			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1219			let mut target_collection = Self::get_collection(collection_id)?;1220			Self::check_owner_permissions(&target_collection, sender.as_sub())?;1221			let old_limits = &target_collection.limits;12221223			// collection bounds1224			ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1225				new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP &&1226				new_limits.sponsored_data_size <= CUSTOM_DATA_LIMIT,1227				Error::<T>::CollectionLimitBoundsExceeded);12281229			// token_limit   check  prev1230			ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1231			ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);12321233			ensure!(1234				(old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1235				(old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1236				Error::<T>::OwnerPermissionsCantBeReverted,1237			);12381239			target_collection.limits = new_limits;12401241			target_collection.save()1242		}1243	}1244}12451246impl<T: Config> Module<T> {1247	pub fn create_item_internal(1248		sender: &T::CrossAccountId,1249		collection: &CollectionHandle<T>,1250		owner: &T::CrossAccountId,1251		data: CreateItemData,1252	) -> DispatchResult {1253		ensure!(1254			owner != &T::CrossAccountId::from_eth(H160([0; 20])),1255			Error::<T>::AddressIsZero1256		);12571258		Self::can_create_items_in_collection(collection, sender, owner, 1)?;1259		Self::validate_create_item_args(collection, &data)?;1260		Self::create_item_no_validation(collection, owner, data)?;12611262		Ok(())1263	}12641265	pub fn transfer_internal(1266		sender: &T::CrossAccountId,1267		recipient: &T::CrossAccountId,1268		target_collection: &CollectionHandle<T>,1269		item_id: TokenId,1270		value: u128,1271	) -> DispatchResult {1272		ensure!(1273			recipient != &T::CrossAccountId::from_eth(H160([0; 20])),1274			Error::<T>::AddressIsZero1275		);12761277		// Limits check1278		Self::is_correct_transfer(target_collection, recipient)?;12791280		// Transfer permissions check1281		ensure!(1282			Self::is_item_owner(sender, target_collection, item_id)?1283				|| Self::is_owner_or_admin_permissions(target_collection, sender)?,1284			Error::<T>::NoPermission1285		);12861287		if target_collection.access == AccessMode::WhiteList {1288			Self::check_white_list(target_collection, sender)?;1289			Self::check_white_list(target_collection, recipient)?;1290		}12911292		match target_collection.mode {1293			CollectionMode::NFT => Self::transfer_nft(1294				target_collection,1295				item_id,1296				sender.clone(),1297				recipient.clone(),1298			)?,1299			CollectionMode::Fungible(_) => {1300				Self::transfer_fungible(target_collection, value, sender, recipient)?1301			}1302			CollectionMode::ReFungible => Self::transfer_refungible(1303				target_collection,1304				item_id,1305				value,1306				sender.clone(),1307				recipient.clone(),1308			)?,1309			_ => (),1310		};13111312		Self::deposit_event(RawEvent::Transfer(1313			target_collection.id,1314			item_id,1315			sender.clone(),1316			recipient.clone(),1317			value,1318		));13191320		Ok(())1321	}13221323	pub fn approve_internal(1324		sender: &T::CrossAccountId,1325		spender: &T::CrossAccountId,1326		collection: &CollectionHandle<T>,1327		item_id: TokenId,1328		amount: u128,1329	) -> DispatchResult {1330		Self::token_exists(collection, item_id)?;13311332		// Transfer permissions check1333		let bypasses_limits = collection.limits.owner_can_transfer1334			&& Self::is_owner_or_admin_permissions(collection, sender)?;13351336		let allowance_limit = if bypasses_limits {1337			None1338		} else if let Some(amount) = Self::owned_amount(sender, collection, item_id)? {1339			Some(amount)1340		} else {1341			fail!(Error::<T>::NoPermission);1342		};13431344		if collection.access == AccessMode::WhiteList {1345			Self::check_white_list(collection, sender)?;1346			Self::check_white_list(collection, spender)?;1347		}13481349		collection.consume_sload()?;1350		let allowance: u128 = amount1351			.checked_add(<Allowances<T>>::get(1352				collection.id,1353				(item_id, sender.as_sub(), spender.as_sub()),1354			))1355			.ok_or(Error::<T>::NumOverflow)?;1356		if let Some(limit) = allowance_limit {1357			ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1358		}1359		collection.consume_sstore()?;1360		<Allowances<T>>::insert(1361			collection.id,1362			(item_id, sender.as_sub(), spender.as_sub()),1363			allowance,1364		);13651366		if matches!(collection.mode, CollectionMode::NFT) {1367			// TODO: NFT: only one owner may exist for token in ERC7211368			collection.log(ERC721Events::Approval {1369				owner: *sender.as_eth(),1370				approved: *spender.as_eth(),1371				token_id: item_id.into(),1372			})?;1373		}13741375		if matches!(collection.mode, CollectionMode::Fungible(_)) {1376			// TODO: NFT: only one owner may exist for token in ERC201377			collection.log(ERC20Events::Approval {1378				owner: *sender.as_eth(),1379				spender: *spender.as_eth(),1380				value: allowance.into(),1381			})?;1382		}13831384		Self::deposit_event(RawEvent::Approved(1385			collection.id,1386			item_id,1387			sender.clone(),1388			spender.clone(),1389			allowance,1390		));1391		Ok(())1392	}13931394	pub fn transfer_from_internal(1395		sender: &T::CrossAccountId,1396		from: &T::CrossAccountId,1397		recipient: &T::CrossAccountId,1398		collection: &CollectionHandle<T>,1399		item_id: TokenId,1400		amount: u128,1401	) -> DispatchResult {1402		if sender == from {1403			// Transfer by `from`, because it is either equal to sender, or derived from him1404			return Self::transfer_internal(from, recipient, collection, item_id, amount);1405		}14061407		// Check approval1408		collection.consume_sload()?;1409		let approval: u128 =1410			<Allowances<T>>::get(collection.id, (item_id, from.as_sub(), sender.as_sub()));14111412		// Limits check1413		Self::is_correct_transfer(collection, recipient)?;14141415		// Transfer permissions check1416		ensure!(1417			approval >= amount1418				|| (collection.limits.owner_can_transfer1419					&& Self::is_owner_or_admin_permissions(collection, sender)?),1420			Error::<T>::NoPermission1421		);14221423		if collection.access == AccessMode::WhiteList {1424			Self::check_white_list(collection, sender)?;1425			Self::check_white_list(collection, recipient)?;1426		}14271428		// Reduce approval by transferred amount or remove if remaining approval drops to 01429		let allowance = approval.saturating_sub(amount);1430		collection.consume_sstore()?;1431		if allowance > 0 {1432			<Allowances<T>>::insert(1433				collection.id,1434				(item_id, from.as_sub(), sender.as_sub()),1435				allowance,1436			);1437		} else {1438			<Allowances<T>>::remove(collection.id, (item_id, from.as_sub(), sender.as_sub()));1439		}14401441		match collection.mode {1442			CollectionMode::NFT => {1443				Self::transfer_nft(collection, item_id, from.clone(), recipient.clone())?1444			}1445			CollectionMode::Fungible(_) => {1446				Self::transfer_fungible(collection, amount, from, recipient)?1447			}1448			CollectionMode::ReFungible => Self::transfer_refungible(1449				collection,1450				item_id,1451				amount,1452				from.clone(),1453				recipient.clone(),1454			)?,1455			_ => (),1456		};14571458		if matches!(collection.mode, CollectionMode::Fungible(_)) {1459			collection.log(ERC20Events::Approval {1460				owner: *from.as_eth(),1461				spender: *sender.as_eth(),1462				value: allowance.into(),1463			})?;1464		}14651466		Ok(())1467	}14681469	pub fn set_variable_meta_data_internal(1470		sender: &T::CrossAccountId,1471		collection: &CollectionHandle<T>,1472		item_id: TokenId,1473		data: Vec<u8>,1474	) -> DispatchResult {1475		Self::token_exists(collection, item_id)?;14761477		ensure!(1478			CUSTOM_DATA_LIMIT >= data.len() as u32,1479			Error::<T>::TokenVariableDataLimitExceeded1480		);14811482		// Modify permissions check1483		ensure!(1484			Self::is_item_owner(sender, collection, item_id)?1485				|| Self::is_owner_or_admin_permissions(collection, sender)?,1486			Error::<T>::NoPermission1487		);14881489		match collection.mode {1490			CollectionMode::NFT => Self::set_nft_variable_data(collection, item_id, data)?,1491			CollectionMode::ReFungible => {1492				Self::set_re_fungible_variable_data(collection, item_id, data)?1493			}1494			CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1495			_ => fail!(Error::<T>::UnexpectedCollectionType),1496		};14971498		Ok(())1499	}15001501	pub fn create_multiple_items_internal(1502		sender: &T::CrossAccountId,1503		collection: &CollectionHandle<T>,1504		owner: &T::CrossAccountId,1505		items_data: Vec<CreateItemData>,1506	) -> DispatchResult {1507		Self::can_create_items_in_collection(collection, sender, owner, items_data.len() as u32)?;15081509		for data in &items_data {1510			Self::validate_create_item_args(collection, data)?;1511		}1512		for data in &items_data {1513			Self::create_item_no_validation(collection, owner, data.clone())?;1514		}15151516		Ok(())1517	}15181519	pub fn burn_item_internal(1520		sender: &T::CrossAccountId,1521		collection: &CollectionHandle<T>,1522		item_id: TokenId,1523		value: u128,1524	) -> DispatchResult {1525		ensure!(1526			Self::is_item_owner(sender, collection, item_id)?1527				|| (collection.limits.owner_can_transfer1528					&& Self::is_owner_or_admin_permissions(collection, sender)?),1529			Error::<T>::NoPermission1530		);15311532		if collection.access == AccessMode::WhiteList {1533			Self::check_white_list(collection, sender)?;1534		}15351536		match collection.mode {1537			CollectionMode::NFT => Self::burn_nft_item(collection, item_id)?,1538			CollectionMode::Fungible(_) => Self::burn_fungible_item(sender, collection, value)?,1539			CollectionMode::ReFungible => Self::burn_refungible_item(collection, item_id, sender)?,1540			_ => (),1541		};15421543		Ok(())1544	}15451546	pub fn toggle_white_list_internal(1547		sender: &T::CrossAccountId,1548		collection: &CollectionHandle<T>,1549		address: &T::CrossAccountId,1550		whitelisted: bool,1551	) -> DispatchResult {1552		Self::check_owner_or_admin_permissions(collection, sender)?;15531554		if whitelisted {1555			<WhiteList<T>>::insert(collection.id, address.as_sub(), true);1556		} else {1557			<WhiteList<T>>::remove(collection.id, address.as_sub());1558		}15591560		Ok(())1561	}15621563	fn is_correct_transfer(1564		collection: &CollectionHandle<T>,1565		recipient: &T::CrossAccountId,1566	) -> DispatchResult {1567		let collection_id = collection.id;15681569		// check token limit and account token limit1570		collection.consume_sload()?;1571		let account_items: u32 =1572			<AddressTokens<T>>::get(collection_id, recipient.as_sub()).len() as u32;1573		ensure!(1574			collection.limits.account_token_ownership_limit > account_items,1575			Error::<T>::AccountTokenLimitExceeded1576		);15771578		// preliminary transfer check1579		ensure!(collection.transfers_enabled, Error::<T>::TransferNotAllowed);15801581		Ok(())1582	}15831584	fn can_create_items_in_collection(1585		collection: &CollectionHandle<T>,1586		sender: &T::CrossAccountId,1587		owner: &T::CrossAccountId,1588		amount: u32,1589	) -> DispatchResult {1590		let collection_id = collection.id;15911592		// check token limit and account token limit1593		let total_items: u32 = ItemListIndex::get(collection_id)1594			.checked_add(amount)1595			.ok_or(Error::<T>::CollectionTokenLimitExceeded)?;1596		let account_items: u32 = (<AddressTokens<T>>::get(collection_id, owner.as_sub()).len()1597			as u32)1598			.checked_add(amount)1599			.ok_or(Error::<T>::AccountTokenLimitExceeded)?;1600		ensure!(1601			collection.limits.token_limit >= total_items,1602			Error::<T>::CollectionTokenLimitExceeded1603		);1604		ensure!(1605			collection.limits.account_token_ownership_limit >= account_items,1606			Error::<T>::AccountTokenLimitExceeded1607		);16081609		if !Self::is_owner_or_admin_permissions(collection, sender)? {1610			ensure!(collection.mint_mode, Error::<T>::PublicMintingNotAllowed);1611			Self::check_white_list(collection, owner)?;1612			Self::check_white_list(collection, sender)?;1613		}16141615		Ok(())1616	}16171618	fn validate_create_item_args(1619		target_collection: &CollectionHandle<T>,1620		data: &CreateItemData,1621	) -> DispatchResult {1622		match target_collection.mode {1623			CollectionMode::NFT => {1624				if !matches!(data, CreateItemData::NFT(_)) {1625					fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1626				}1627			}1628			CollectionMode::Fungible(_) => {1629				if !matches!(data, CreateItemData::Fungible(_)) {1630					fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1631				}1632			}1633			CollectionMode::ReFungible => {1634				if let CreateItemData::ReFungible(data) = data {1635					// Check refungibility limits1636					ensure!(1637						data.pieces <= MAX_REFUNGIBLE_PIECES,1638						Error::<T>::WrongRefungiblePieces1639					);1640					ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1641				} else {1642					fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1643				}1644			}1645			_ => {1646				fail!(Error::<T>::UnexpectedCollectionType);1647			}1648		};16491650		Ok(())1651	}16521653	fn create_item_no_validation(1654		collection: &CollectionHandle<T>,1655		owner: &T::CrossAccountId,1656		data: CreateItemData,1657	) -> DispatchResult {1658		match data {1659			CreateItemData::NFT(data) => {1660				let item = NftItemType {1661					owner: owner.clone(),1662					const_data: data.const_data.into_inner(),1663					variable_data: data.variable_data.into_inner(),1664				};16651666				Self::add_nft_item(collection, item)?;1667			}1668			CreateItemData::Fungible(data) => {1669				Self::add_fungible_item(collection, owner, data.value)?;1670			}1671			CreateItemData::ReFungible(data) => {1672				let owner_list = vec![Ownership {1673					owner: owner.clone(),1674					fraction: data.pieces,1675				}];16761677				let item = ReFungibleItemType {1678					owner: owner_list,1679					const_data: data.const_data.into_inner(),1680					variable_data: data.variable_data.into_inner(),1681				};16821683				Self::add_refungible_item(collection, item)?;1684			}1685		};16861687		Ok(())1688	}16891690	fn add_fungible_item(1691		collection: &CollectionHandle<T>,1692		owner: &T::CrossAccountId,1693		value: u128,1694	) -> DispatchResult {1695		let collection_id = collection.id;16961697		// Does new owner already have an account?1698		collection.consume_sload()?;1699		let balance: u128 = <FungibleItemList<T>>::get(collection_id, owner.as_sub()).value;17001701		// Mint1702		let item = FungibleItemType {1703			value: balance.checked_add(value).ok_or(Error::<T>::NumOverflow)?,1704		};1705		collection.consume_sstore()?;1706		<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), item);17071708		// Update balance1709		collection.consume_sload()?;1710		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1711			.checked_add(value)1712			.ok_or(Error::<T>::NumOverflow)?;1713		collection.consume_sstore()?;1714		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);17151716		collection.log(ERC20Events::Transfer {1717			from: H160::default(),1718			to: *owner.as_eth(),1719			value: value.into(),1720		})?;1721		Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));1722		Ok(())1723	}17241725	fn add_refungible_item(1726		collection: &CollectionHandle<T>,1727		item: ReFungibleItemType<T::CrossAccountId>,1728	) -> DispatchResult {1729		let collection_id = collection.id;17301731		let current_index = <ItemListIndex>::get(collection_id)1732			.checked_add(1)1733			.ok_or(Error::<T>::NumOverflow)?;1734		let itemcopy = item.clone();17351736		ensure!(item.owner.len() == 1, Error::<T>::BadCreateRefungibleCall,);1737		let item_owner = item.owner.first().expect("only one owner is defined");17381739		let value = item_owner.fraction;1740		let owner = item_owner.owner.clone();17411742		Self::add_token_index(collection, current_index, &owner)?;17431744		<ItemListIndex>::insert(collection_id, current_index);1745		<ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);17461747		// Update balance1748		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1749			.checked_add(value)1750			.ok_or(Error::<T>::NumOverflow)?;1751		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);17521753		Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));1754		Ok(())1755	}17561757	fn add_nft_item(1758		collection: &CollectionHandle<T>,1759		item: NftItemType<T::CrossAccountId>,1760	) -> DispatchResult {1761		let collection_id = collection.id;17621763		let current_index = <ItemListIndex>::get(collection_id)1764			.checked_add(1)1765			.ok_or(Error::<T>::NumOverflow)?;17661767		let item_owner = item.owner.clone();1768		Self::add_token_index(collection, current_index, &item.owner)?;17691770		<ItemListIndex>::insert(collection_id, current_index);1771		<NftItemList<T>>::insert(collection_id, current_index, item);17721773		// Update balance1774		let new_balance = <Balance<T>>::get(collection_id, item_owner.as_sub())1775			.checked_add(1)1776			.ok_or(Error::<T>::NumOverflow)?;1777		<Balance<T>>::insert(collection_id, item_owner.as_sub(), new_balance);17781779		collection.log(ERC721Events::Transfer {1780			from: H160::default(),1781			to: *item_owner.as_eth(),1782			token_id: current_index.into(),1783		})?;1784		Self::deposit_event(RawEvent::ItemCreated(1785			collection_id,1786			current_index,1787			item_owner,1788		));1789		Ok(())1790	}17911792	fn burn_refungible_item(1793		collection: &CollectionHandle<T>,1794		item_id: TokenId,1795		owner: &T::CrossAccountId,1796	) -> DispatchResult {1797		let collection_id = collection.id;17981799		let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id)1800			.ok_or(Error::<T>::TokenNotFound)?;1801		let rft_balance = token1802			.owner1803			.iter()1804			.find(|&i| i.owner == *owner)1805			.ok_or(Error::<T>::TokenNotFound)?;1806		Self::remove_token_index(collection, item_id, owner)?;18071808		// update balance1809		let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.as_sub())1810			.checked_sub(rft_balance.fraction)1811			.ok_or(Error::<T>::NumOverflow)?;1812		<Balance<T>>::insert(collection_id, rft_balance.owner.as_sub(), new_balance);18131814		// Re-create owners list with sender removed1815		let index = token1816			.owner1817			.iter()1818			.position(|i| i.owner == *owner)1819			.expect("owned item is exists");1820		token.owner.remove(index);1821		let owner_count = token.owner.len();18221823		// Burn the token completely if this was the last (only) owner1824		if owner_count == 0 {1825			<ReFungibleItemList<T>>::remove(collection_id, item_id);1826			<VariableMetaDataBasket<T>>::remove(collection_id, item_id);1827		} else {1828			<ReFungibleItemList<T>>::insert(collection_id, item_id, token);1829		}18301831		Ok(())1832	}18331834	fn burn_nft_item(collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1835		let collection_id = collection.id;18361837		let item =1838			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;1839		Self::remove_token_index(collection, item_id, &item.owner)?;18401841		// update balance1842		let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())1843			.checked_sub(1)1844			.ok_or(Error::<T>::NumOverflow)?;1845		<Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);1846		<NftItemList<T>>::remove(collection_id, item_id);1847		<VariableMetaDataBasket<T>>::remove(collection_id, item_id);18481849		collection.log(ERC721Events::Transfer {1850			from: *item.owner.as_eth(),1851			to: H160::default(),1852			token_id: item_id.into(),1853		})?;1854		Self::deposit_event(RawEvent::ItemDestroyed(collection.id, item_id));1855		Ok(())1856	}18571858	fn burn_fungible_item(1859		owner: &T::CrossAccountId,1860		collection: &CollectionHandle<T>,1861		value: u128,1862	) -> DispatchResult {1863		let collection_id = collection.id;18641865		let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());1866		ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);18671868		// update balance1869		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1870			.checked_sub(value)1871			.ok_or(Error::<T>::NumOverflow)?;1872		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);18731874		if balance.value - value > 0 {1875			balance.value -= value;1876			<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);1877		} else {1878			<FungibleItemList<T>>::remove(collection_id, owner.as_sub());1879		}18801881		collection.log(ERC20Events::Transfer {1882			from: *owner.as_eth(),1883			to: H160::default(),1884			value: value.into(),1885		})?;1886		Ok(())1887	}18881889	pub fn get_collection(1890		collection_id: CollectionId,1891	) -> Result<CollectionHandle<T>, sp_runtime::DispatchError> {1892		Ok(<CollectionHandle<T>>::get(collection_id).ok_or(Error::<T>::CollectionNotFound)?)1893	}18941895	fn check_owner_permissions(1896		target_collection: &CollectionHandle<T>,1897		subject: &T::AccountId,1898	) -> DispatchResult {1899		ensure!(1900			*subject == target_collection.owner,1901			Error::<T>::NoPermission1902		);19031904		Ok(())1905	}19061907	fn is_owner_or_admin_permissions(1908		collection: &CollectionHandle<T>,1909		subject: &T::CrossAccountId,1910	) -> Result<bool, DispatchError> {1911		collection.consume_sload()?;1912		Ok(*subject.as_sub() == collection.owner1913			|| <AdminList<T>>::get(collection.id).contains(subject))1914	}19151916	fn check_owner_or_admin_permissions(1917		collection: &CollectionHandle<T>,1918		subject: &T::CrossAccountId,1919	) -> DispatchResult {1920		ensure!(1921			Self::is_owner_or_admin_permissions(collection, subject)?,1922			Error::<T>::NoPermission1923		);19241925		Ok(())1926	}19271928	fn owned_amount(1929		subject: &T::CrossAccountId,1930		collection: &CollectionHandle<T>,1931		item_id: TokenId,1932	) -> Result<Option<u128>, DispatchError> {1933		collection.consume_sload()?;1934		Ok(Self::owned_amount_unchecked(subject, collection, item_id))1935	}19361937	fn owned_amount_unchecked(1938		subject: &T::CrossAccountId,1939		target_collection: &CollectionHandle<T>,1940		item_id: TokenId,1941	) -> Option<u128> {1942		let collection_id = target_collection.id;19431944		match target_collection.mode {1945			CollectionMode::NFT => {1946				(<NftItemList<T>>::get(collection_id, item_id)?.owner == *subject).then(|| 1)1947			}1948			CollectionMode::Fungible(_) => {1949				Some(<FungibleItemList<T>>::get(collection_id, &subject.as_sub()).value)1950			}1951			CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)?1952				.owner1953				.iter()1954				.find(|i| i.owner == *subject)1955				.map(|i| i.fraction),1956			CollectionMode::Invalid => None,1957		}1958	}19591960	fn is_item_owner(1961		subject: &T::CrossAccountId,1962		target_collection: &CollectionHandle<T>,1963		item_id: TokenId,1964	) -> Result<bool, DispatchError> {1965		Ok(match target_collection.mode {1966			CollectionMode::Fungible(_) => true,1967			_ => Self::owned_amount(subject, target_collection, item_id)?.is_some(),1968		})1969	}19701971	fn check_white_list(1972		collection: &CollectionHandle<T>,1973		address: &T::CrossAccountId,1974	) -> DispatchResult {1975		collection.consume_sload()?;1976		ensure!(1977			<WhiteList<T>>::contains_key(collection.id, address.as_sub()),1978			Error::<T>::AddresNotInWhiteList,1979		);1980		Ok(())1981	}19821983	/// Check if token exists. In case of Fungible, check if there is an entry for1984	/// the owner in fungible balances double map1985	fn token_exists(target_collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1986		let collection_id = target_collection.id;1987		let exists = match target_collection.mode {1988			CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),1989			CollectionMode::Fungible(_) => true,1990			CollectionMode::ReFungible => {1991				<ReFungibleItemList<T>>::contains_key(collection_id, item_id)1992			}1993			_ => false,1994		};19951996		ensure!(exists, Error::<T>::TokenNotFound);1997		Ok(())1998	}19992000	fn transfer_fungible(2001		collection: &CollectionHandle<T>,2002		value: u128,2003		owner: &T::CrossAccountId,2004		recipient: &T::CrossAccountId,2005	) -> DispatchResult {2006		let collection_id = collection.id;20072008		collection.consume_sload()?;2009		collection.consume_sload()?;2010		let mut recipient_balance = <FungibleItemList<T>>::get(collection_id, recipient.as_sub());2011		let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());20122013		recipient_balance.value = recipient_balance2014			.value2015			.checked_add(value)2016			.ok_or(Error::<T>::NumOverflow)?;2017		balance.value = balance2018			.value2019			.checked_sub(value)2020			.ok_or(Error::<T>::TokenValueTooLow)?;20212022		// update balanceOf2023		collection.consume_sstore()?;2024		collection.consume_sstore()?;2025		if balance.value != 0 {2026			<Balance<T>>::insert(collection_id, owner.as_sub(), balance.value);2027		} else {2028			<Balance<T>>::remove(collection_id, owner.as_sub());2029		}2030		<Balance<T>>::insert(collection_id, recipient.as_sub(), recipient_balance.value);20312032		// Reduce or remove sender2033		collection.consume_sstore()?;2034		collection.consume_sstore()?;2035		if balance.value != 0 {2036			<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);2037		} else {2038			<FungibleItemList<T>>::remove(collection_id, owner.as_sub());2039		}2040		<FungibleItemList<T>>::insert(collection_id, recipient.as_sub(), recipient_balance);20412042		collection.log(ERC20Events::Transfer {2043			from: *owner.as_eth(),2044			to: *recipient.as_eth(),2045			value: value.into(),2046		})?;2047		Self::deposit_event(RawEvent::Transfer(2048			collection.id,2049			1,2050			owner.clone(),2051			recipient.clone(),2052			value,2053		));20542055		Ok(())2056	}20572058	fn transfer_refungible(2059		collection: &CollectionHandle<T>,2060		item_id: TokenId,2061		value: u128,2062		owner: T::CrossAccountId,2063		new_owner: T::CrossAccountId,2064	) -> DispatchResult {2065		let collection_id = collection.id;2066		collection.consume_sload()?;2067		let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id)2068			.ok_or(Error::<T>::TokenNotFound)?;20692070		let item = full_item2071			.owner2072			.iter()2073			.find(|i| i.owner == owner)2074			.ok_or(Error::<T>::TokenNotFound)?;2075		let amount = item.fraction;20762077		ensure!(amount >= value, Error::<T>::TokenValueTooLow);20782079		collection.consume_sload()?;2080		// update balance2081		let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2082			.checked_sub(value)2083			.ok_or(Error::<T>::NumOverflow)?;2084		collection.consume_sstore()?;2085		<Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);20862087		collection.consume_sload()?;2088		let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2089			.checked_add(value)2090			.ok_or(Error::<T>::NumOverflow)?;2091		collection.consume_sstore()?;2092		<Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);20932094		let old_owner = item.owner.clone();2095		let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);20962097		let mut new_full_item = full_item.clone();2098		// transfer2099		if amount == value && !new_owner_has_account {2100			// change owner2101			// new owner do not have account2102			new_full_item2103				.owner2104				.iter_mut()2105				.find(|i| i.owner == owner)2106				.expect("old owner does present in refungible")2107				.owner = new_owner.clone();2108			collection.consume_sstore()?;2109			<ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);21102111			// update index collection2112			Self::move_token_index(collection, item_id, &old_owner, &new_owner)?;2113		} else {2114			new_full_item2115				.owner2116				.iter_mut()2117				.find(|i| i.owner == owner)2118				.expect("old owner does present in refungible")2119				.fraction -= value;21202121			// separate amount2122			if new_owner_has_account {2123				// new owner has account2124				new_full_item2125					.owner2126					.iter_mut()2127					.find(|i| i.owner == new_owner)2128					.expect("new owner has account")2129					.fraction += value;2130			} else {2131				// new owner do not have account2132				new_full_item.owner.push(Ownership {2133					owner: new_owner.clone(),2134					fraction: value,2135				});2136				Self::add_token_index(collection, item_id, &new_owner)?;2137			}21382139			collection.consume_sstore()?;2140			<ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2141		}21422143		Self::deposit_event(RawEvent::Transfer(2144			collection.id,2145			item_id,2146			owner,2147			new_owner,2148			amount,2149		));21502151		Ok(())2152	}21532154	fn transfer_nft(2155		collection: &CollectionHandle<T>,2156		item_id: TokenId,2157		sender: T::CrossAccountId,2158		new_owner: T::CrossAccountId,2159	) -> DispatchResult {2160		let collection_id = collection.id;2161		collection.consume_sload()?;2162		let mut item =2163			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;21642165		ensure!(sender == item.owner, Error::<T>::MustBeTokenOwner);21662167		collection.consume_sload()?;2168		// update balance2169		let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2170			.checked_sub(1)2171			.ok_or(Error::<T>::NumOverflow)?;2172		collection.consume_sstore()?;2173		<Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);21742175		collection.consume_sload()?;2176		let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2177			.checked_add(1)2178			.ok_or(Error::<T>::NumOverflow)?;2179		collection.consume_sstore()?;2180		<Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);21812182		// change owner2183		let old_owner = item.owner.clone();2184		item.owner = new_owner.clone();2185		collection.consume_sstore()?;2186		<NftItemList<T>>::insert(collection_id, item_id, item);21872188		// update index collection2189		Self::move_token_index(collection, item_id, &old_owner, &new_owner)?;21902191		collection.log(ERC721Events::Transfer {2192			from: *sender.as_eth(),2193			to: *new_owner.as_eth(),2194			token_id: item_id.into(),2195		})?;2196		Self::deposit_event(RawEvent::Transfer(2197			collection.id,2198			item_id,2199			sender,2200			new_owner,2201			1,2202		));22032204		Ok(())2205	}22062207	fn set_re_fungible_variable_data(2208		collection: &CollectionHandle<T>,2209		item_id: TokenId,2210		data: Vec<u8>,2211	) -> DispatchResult {2212		let collection_id = collection.id;2213		let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id)2214			.ok_or(Error::<T>::TokenNotFound)?;22152216		item.variable_data = data;22172218		<ReFungibleItemList<T>>::insert(collection_id, item_id, item);22192220		Ok(())2221	}22222223	fn set_nft_variable_data(2224		collection: &CollectionHandle<T>,2225		item_id: TokenId,2226		data: Vec<u8>,2227	) -> DispatchResult {2228		let collection_id = collection.id;2229		let mut item =2230			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;22312232		item.variable_data = data;22332234		<NftItemList<T>>::insert(collection_id, item_id, item);22352236		Ok(())2237	}22382239	#[allow(dead_code)]2240	fn init_collection(item: &Collection<T>) {2241		// check params2242		assert!(2243			item.decimal_points <= MAX_DECIMAL_POINTS,2244			"decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2245		);2246		assert!(2247			item.name.len() <= 64,2248			"Collection name can not be longer than 63 char"2249		);2250		assert!(2251			item.name.len() <= 256,2252			"Collection description can not be longer than 255 char"2253		);2254		assert!(2255			item.token_prefix.len() <= 16,2256			"Token prefix can not be longer than 15 char"2257		);22582259		// Generate next collection ID2260		let next_id = CreatedCollectionCount::get().checked_add(1).unwrap();22612262		CreatedCollectionCount::put(next_id);2263	}22642265	#[allow(dead_code)]2266	fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::CrossAccountId>) {2267		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();22682269		Self::add_token_index(2270			&CollectionHandle::get(collection_id).unwrap(),2271			current_index,2272			&item.owner,2273		)2274		.unwrap();22752276		<ItemListIndex>::insert(collection_id, current_index);22772278		// Update balance2279		let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2280			.checked_add(1)2281			.unwrap();2282		<Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2283	}22842285	#[allow(dead_code)]2286	fn init_fungible_token(2287		collection_id: CollectionId,2288		owner: &T::CrossAccountId,2289		item: &FungibleItemType,2290	) {2291		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();22922293		Self::add_token_index(2294			&CollectionHandle::get(collection_id).unwrap(),2295			current_index,2296			owner,2297		)2298		.unwrap();22992300		<ItemListIndex>::insert(collection_id, current_index);23012302		// Update balance2303		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2304			.checked_add(item.value)2305			.unwrap();2306		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2307	}23082309	#[allow(dead_code)]2310	fn init_refungible_token(2311		collection_id: CollectionId,2312		item: &ReFungibleItemType<T::CrossAccountId>,2313	) {2314		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();23152316		let value = item.owner.first().unwrap().fraction;2317		let owner = item.owner.first().unwrap().owner.clone();23182319		Self::add_token_index(2320			&CollectionHandle::get(collection_id).unwrap(),2321			current_index,2322			&owner,2323		)2324		.unwrap();23252326		<ItemListIndex>::insert(collection_id, current_index);23272328		// Update balance2329		let new_balance = <Balance<T>>::get(collection_id, &owner.as_sub())2330			.checked_add(value)2331			.unwrap();2332		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2333	}23342335	fn add_token_index(2336		collection: &CollectionHandle<T>,2337		item_index: TokenId,2338		owner: &T::CrossAccountId,2339	) -> DispatchResult {2340		// add to account limit2341		collection.consume_sload()?;2342		if <AccountItemCount<T>>::contains_key(owner.as_sub()) {2343			// bound Owned tokens by a single address2344			collection.consume_sload()?;2345			let count = <AccountItemCount<T>>::get(owner.as_sub());2346			ensure!(2347				count < ACCOUNT_TOKEN_OWNERSHIP_LIMIT,2348				Error::<T>::AddressOwnershipLimitExceeded2349			);23502351			collection.consume_sstore()?;2352			<AccountItemCount<T>>::insert(2353				owner.as_sub(),2354				count.checked_add(1).ok_or(Error::<T>::NumOverflow)?,2355			);2356		} else {2357			collection.consume_sstore()?;2358			<AccountItemCount<T>>::insert(owner.as_sub(), 1);2359		}23602361		collection.consume_sload()?;2362		let list_exists = <AddressTokens<T>>::contains_key(collection.id, owner.as_sub());2363		if list_exists {2364			collection.consume_sload()?;2365			let mut list = <AddressTokens<T>>::get(collection.id, owner.as_sub());2366			let item_contains = list.contains(&item_index.clone());23672368			if !item_contains {2369				list.push(item_index);2370			}23712372			collection.consume_sstore()?;2373			<AddressTokens<T>>::insert(collection.id, owner.as_sub(), list);2374		} else {2375			let itm = vec![item_index];2376			collection.consume_sstore()?;2377			<AddressTokens<T>>::insert(collection.id, owner.as_sub(), itm);2378		}23792380		Ok(())2381	}23822383	fn remove_token_index(2384		collection: &CollectionHandle<T>,2385		item_index: TokenId,2386		owner: &T::CrossAccountId,2387	) -> DispatchResult {2388		// update counter2389		collection.consume_sload()?;2390		collection.consume_sstore()?;2391		<AccountItemCount<T>>::insert(2392			owner.as_sub(),2393			<AccountItemCount<T>>::get(owner.as_sub())2394				.checked_sub(1)2395				.ok_or(Error::<T>::NumOverflow)?,2396		);23972398		collection.consume_sload()?;2399		let list_exists = <AddressTokens<T>>::contains_key(collection.id, owner.as_sub());2400		if list_exists {2401			collection.consume_sload()?;2402			let mut list = <AddressTokens<T>>::get(collection.id, owner.as_sub());2403			let item_contains = list.contains(&item_index.clone());24042405			if item_contains {2406				list.retain(|&item| item != item_index);2407				collection.consume_sstore()?;2408				<AddressTokens<T>>::insert(collection.id, owner.as_sub(), list);2409			}2410		}24112412		Ok(())2413	}24142415	fn move_token_index(2416		collection: &CollectionHandle<T>,2417		item_index: TokenId,2418		old_owner: &T::CrossAccountId,2419		new_owner: &T::CrossAccountId,2420	) -> DispatchResult {2421		Self::remove_token_index(collection, item_index, old_owner)?;2422		Self::add_token_index(collection, item_index, new_owner)?;24232424		Ok(())2425	}2426}24272428sp_api::decl_runtime_apis! {2429	pub trait NftApi {2430		/// Used for ethereum integration2431		fn eth_contract_code(account: H160) -> Option<Vec<u8>>;2432	}2433}
modifiedpallets/nft/src/mock.rsdiffbeforeafterboth
--- a/pallets/nft/src/mock.rs
+++ b/pallets/nft/src/mock.rs
@@ -129,8 +129,11 @@
 	fn as_sub(&self) -> &u64 {
 		&self.0
 	}
-	fn from_eth(_eth: sp_core::H160) -> Self {
-		unimplemented!()
+	fn from_eth(eth: sp_core::H160) -> Self {
+		let mut sub_raw = [0; 8];
+		sub_raw.copy_from_slice(&eth.0[0..8]);
+		let sub = u64::from_be_bytes(sub_raw);
+		Self(sub, eth)
 	}
 	fn as_eth(&self) -> &sp_core::H160 {
 		&self.1
modifiedtests/package.jsondiffbeforeafterboth
--- a/tests/package.json
+++ b/tests/package.json
@@ -24,8 +24,8 @@
     "lint": "eslint --ext .ts,.js src/",
     "fix": "eslint --ext .ts,.js src/ --fix",
     "test": "mocha --timeout 9999999 -r ts-node/register './**/*.test.ts'",
-    "testEth": "mocha --timeout 9999999 -r ts-node/register ./**/eth/**/*.test.ts",
-    "load": "mocha --timeout 9999999 -r ts-node/register ./**/*.load.ts",
+    "testEth": "mocha --timeout 9999999 -r ts-node/register './**/eth/**/*.test.ts'",
+    "load": "mocha --timeout 9999999 -r ts-node/register './**/*.load.ts'",
     "loadTransfer": "ts-node src/transfer.nload.ts",
     "testCollision": "mocha --timeout 9999999 -r ts-node/register ./src/collision-tests/*.test.ts",
     "testAddCollectionAdmin": "mocha --timeout 9999999 -r ts-node/register ./**/addCollectionAdmin.test.ts",
@@ -85,4 +85,4 @@
       "artifacts"
     ]
   }
-}
\ No newline at end of file
+}
addedtests/src/eth/marketplace/MarketPlaceKSM.abidiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/marketplace/MarketPlaceKSM.abi
@@ -0,0 +1 @@
+[{"inputs":[{"internalType":"address","name":"_escrow","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"asks","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"asksbySeller","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"balanceKSM","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_idCollection","type":"address"},{"internalType":"uint256","name":"_idNFT","type":"uint256"}],"name":"buy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"uint256","name":"_currencyCode","type":"uint256"},{"internalType":"address","name":"_sender","type":"address"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_currencyCode","type":"uint256"},{"internalType":"address","name":"_sender","type":"address"}],"name":"escrowBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"offers","outputs":[{"internalType":"uint256","name":"idNFT","type":"uint256"},{"internalType":"uint256","name":"currencyCode","type":"uint256"},{"internalType":"uint256","name":"price","type":"uint256"},{"internalType":"uint256","name":"time","type":"uint256"},{"internalType":"address","name":"idCollection","type":"address"},{"internalType":"address","name":"userAddr","type":"address"},{"internalType":"uint8","name":"flagActive","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_price","type":"uint256"},{"internalType":"uint256","name":"_currencyCode","type":"uint256"},{"internalType":"address","name":"_idCollection","type":"address"},{"internalType":"uint256","name":"_idNFT","type":"uint256"},{"internalType":"uint8","name":"_active","type":"uint8"}],"name":"setAsk","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newEscrow","type":"address"}],"name":"setEscrow","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newEscrow","type":"address"}],"name":"setowner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"uint256","name":"_currencyCode","type":"uint256"},{"internalType":"address","name":"_sender","type":"address"}],"name":"withdraw","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]
\ No newline at end of file
addedtests/src/eth/marketplace/MarketPlaceKSM.bindiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/marketplace/MarketPlaceKSM.bin
@@ -0,0 +1 @@
+608060405234801561001057600080fd5b50604051610e27380380610e2783398101604081905261002f9161005d565b600480546001600160a01b039092166001600160a01b0319928316179055600580549091163317905561008d565b60006020828403121561006f57600080fd5b81516001600160a01b038116811461008657600080fd5b9392505050565b610d8b8061009c6000396000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c806399f2d4eb1161007157806399f2d4eb1461018e5780639df55f52146101a1578063c10c35461461010f578063cce7ec13146101b4578063db87db52146101c7578063e22d4f7d146101fa57600080fd5b80630ad58d2f146100ae57806310a5ff17146100d6578063592bd7051461010f5780638a72ea6a146101245780638dbdbe6d1461017b575b600080fd5b6100c16100bc366004610c4d565b610225565b60405190151581526020015b60405180910390f35b6101016100e4366004610bd8565b600160209081526000928352604080842090915290825290205481565b6040519081526020016100cd565b61012261011d366004610b97565b6102d8565b005b610137610132366004610c04565b610345565b6040805197885260208801969096529486019390935260608501919091526001600160a01b0390811660808501521660a083015260ff1660c082015260e0016100cd565b610122610189366004610c4d565b6103a4565b61010161019c366004610bd8565b610449565b6101226101af366004610c86565b61047a565b6101226101c2366004610bd8565b610984565b6101016101d5366004610c1d565b6001600160a01b03166000908152600160209081526040808320938352929052205490565b610101610208366004610bd8565b600260209081526000928352604080842090915290825290205481565b6004546000906001600160a01b031633146102795760405162461bcd60e51b815260206004820152600f60248201526e27b7363c9032b9b1b937bb9031b0b760891b60448201526064015b60405180910390fd5b6001600160a01b03821660009081526001602090815260408083208684529091529020546102a8908590610cfa565b6001600160a01b039290921660009081526001602081815260408084209684529590529390209190915550919050565b6005546001600160a01b031633146103235760405162461bcd60e51b815260206004820152600e60248201526d27b7363c9037bbb732b91031b0b760911b6044820152606401610270565b600480546001600160a01b0319166001600160a01b0392909216919091179055565b6000818154811061035557600080fd5b6000918252602090912060069091020180546001820154600283015460038401546004850154600590950154939550919390926001600160a01b0391821691811690600160a01b900460ff1687565b6004546001600160a01b031633146103f05760405162461bcd60e51b815260206004820152600f60248201526e27b7363c9032b9b1b937bb9031b0b760891b6044820152606401610270565b6001600160a01b038116600090815260016020908152604080832085845290915290205461041f908490610ce2565b6001600160a01b039091166000908152600160209081526040808320948352939052919091205550565b6003602052816000526040600020818154811061046557600080fd5b90600052602060002001600091509150505481565b6040516331a9108f60e11b81526004810183905233906001600160a01b03851690636352211e9060240160206040518083038186803b1580156104bc57600080fd5b505afa1580156104d0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104f49190610bbb565b6001600160a01b0316146105425760405162461bcd60e51b81526020600482015260156024820152742737ba103934b3b43a103a37b5b2b71037bbb732b960591b6044820152606401610270565b6001600160a01b0383166000908152600260209081526040808320858452909152812054905415806105ac575060006001600160a01b03166000828154811061058d5761058d610d27565b60009182526020909120600460069092020101546001600160a01b0316145b15610793576040805160e0810182528481526020810187815291810188815242606083019081526001600160a01b03808916608085019081523360a0860190815260ff808a1660c088019081526000805460018181018355828052995160069091027f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56381019190915599517f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5648b015596517f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5658a015594517f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56689015591517f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e567880180549185166001600160a01b0319909216919091179055517f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56890960180549351909116600160a01b026001600160a81b031990931695909116949094171790925590546107399190610cfa565b6001600160a01b038516600090815260026020908152604080832087845282528083209390935533825260039052908120905461077890600190610cfa565b81546001810183556000928352602090922090910155610916565b6040805160e0810182526001600160a01b03861660009081526002602090815283822087835290529182205482549192839290919081106107d6576107d6610d27565b60009182526020808320600690920290910154835282810189905260408084018b90524260608501526001600160a01b0389168352600282528083208884529091528120548154608090930192811061083157610831610d27565b60009182526020808320600460069093020191909101546001600160a01b039081168452338483015260ff871660409485015288168252600281528282208783529052908120548154811061088857610888610d27565b60009182526020918290208351600690920201908155908201516001820155604082015160028201556060820151600382015560808201516004820180546001600160a01b039283166001600160a01b031990911617905560a08301516005909201805460c09094015160ff16600160a01b026001600160a81b031990941692909116919091179190911790555b6040516323b872dd60e01b8152336004820152306024820152604481018490526001600160a01b038516906323b872dd90606401600060405180830381600087803b15801561096457600080fd5b505af1158015610978573d6000803e3d6000fd5b50505050505050505050565b6001600160a01b0382166000908152600260209081526040808320848452909152812054815482919081106109bb576109bb610d27565b600091825260208083206040805160e0810182526006949094029091018054845260018082015485850190815260028301548685018190526003840154606088015260048401546001600160a01b03908116608089015260059094015493841660a0880152600160a01b90930460ff1660c08701523387529084528286209051865290925290922054909250610a519190610cfa565b33600090815260016020818152604080842086830180518652908352818520959095558581015160a08701516001600160a01b031685529282528084209451845293905291902054610aa39190610ce2565b60a08201516001600160a01b0390811660009081526001602090815260408083208287015184528252808320949094559186168152600282528281208582529091529081205481548291908110610afc57610afc610d27565b60009182526020909120600560069092020101805460ff92909216600160a01b0260ff60a01b199092169190911790556040516323b872dd60e01b8152306004820152336024820152604481018390526001600160a01b038416906323b872dd90606401600060405180830381600087803b158015610b7a57600080fd5b505af1158015610b8e573d6000803e3d6000fd5b50505050505050565b600060208284031215610ba957600080fd5b8135610bb481610d3d565b9392505050565b600060208284031215610bcd57600080fd5b8151610bb481610d3d565b60008060408385031215610beb57600080fd5b8235610bf681610d3d565b946020939093013593505050565b600060208284031215610c1657600080fd5b5035919050565b60008060408385031215610c3057600080fd5b823591506020830135610c4281610d3d565b809150509250929050565b600080600060608486031215610c6257600080fd5b83359250602084013591506040840135610c7b81610d3d565b809150509250925092565b600080600080600060a08688031215610c9e57600080fd5b85359450602086013593506040860135610cb781610d3d565b925060608601359150608086013560ff81168114610cd457600080fd5b809150509295509295909350565b60008219821115610cf557610cf5610d11565b500190565b600082821015610d0c57610d0c610d11565b500390565b634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052603260045260246000fd5b6001600160a01b0381168114610d5257600080fd5b5056fea2646970667358221220d098ae4e0cc7bdbb7de1061c03402a667836f903122c1c606ca2c65db1ab78e964736f6c63430008070033
\ No newline at end of file
addedtests/src/eth/marketplace/MarketPlaceKSM.soldiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/marketplace/MarketPlaceKSM.sol
@@ -0,0 +1,179 @@
+// SPDX-License-Identifier:  Apache License
+pragma solidity >=0.8.0;
+
+interface IERC721 {
+    event Transfer(
+        address indexed from,
+        address indexed to,
+        uint256 indexed tokenId
+    );
+    event Approval(
+        address indexed owner,
+        address indexed approved,
+        uint256 indexed tokenId
+    );
+
+    function ownerOf(uint256 tokenId) external view returns (address owner);
+
+    function transferFrom(
+        address from,
+        address to,
+        uint256 tokenId
+    ) external;
+
+    function debug(string memory value) external;
+}
+
+interface IERC20 {
+    event Transfer(address indexed from, address indexed to, uint256 value);
+    event Approval(
+        address indexed owner,
+        address indexed spender,
+        uint256 value
+    );
+
+    function transfer(address recipient, uint256 amount)
+        external
+        returns (bool);
+
+    function transferFrom(
+        address sender,
+        address recipient,
+        uint256 amount
+    ) external returns (bool);
+}
+
+contract MarketPlaceKSM {
+    struct Offer {
+        uint256 idNFT;
+        uint256 currencyCode;
+        uint256 price;
+        uint256 time;
+        address idCollection;
+        address userAddr;
+        uint8 flagActive;
+    }
+    Offer[] public offers;
+
+    mapping(address => mapping(uint256 => uint256)) public balanceKSM; //  [userAddr] => [KSMs]
+    mapping(address => mapping(uint256 => uint256)) public asks; // [buyer][idCollection][idNFT] => idOffer
+
+    mapping(address => uint256[]) public asksbySeller; // [addressSeller] =>idOffer
+
+    address escrow;
+    address owner;
+
+    constructor(address _escrow) {
+        escrow = _escrow;
+        owner = msg.sender;
+    }
+
+    function setowner(address _newEscrow) public onlyOwner {
+        escrow = _newEscrow;
+    }
+
+    function setEscrow(address _newEscrow) public onlyOwner {
+        escrow = _newEscrow;
+    }
+
+    modifier onlyEscrow() {
+        require(msg.sender == escrow, "Only escrow can");
+        _;
+    }
+
+    modifier onlyOwner() {
+        require(msg.sender == owner, "Only owner can");
+        _;
+    }
+
+    /**
+     * Make bids (offers) to sell NFTs
+     */
+    function setAsk(
+        uint256 _price,
+        uint256 _currencyCode,
+        address _idCollection,
+        uint256 _idNFT,
+        uint8 _active
+    ) public {
+        require(
+            IERC721(_idCollection).ownerOf(_idNFT) == msg.sender,
+            "Not right token owner"
+        );
+        uint256 offerID = asks[_idCollection][_idNFT];
+        if (offers.length == 0 || offers[offerID].idCollection == address(0)) {
+            offers.push(
+                Offer(
+                    _idNFT,
+                    _currencyCode,
+                    _price,
+                    block.timestamp,
+                    _idCollection,
+                    msg.sender,
+                    _active
+                )
+            );
+            asks[_idCollection][_idNFT] = offers.length - 1;
+            asksbySeller[msg.sender].push(offers.length - 1);
+        }
+        //edit existing offer
+        else {
+            offers[asks[_idCollection][_idNFT]] = Offer(
+                offers[asks[_idCollection][_idNFT]].idNFT,
+                _currencyCode,
+                _price,
+                block.timestamp,
+                offers[asks[_idCollection][_idNFT]].idCollection,
+                msg.sender,
+                _active
+            );
+        }
+
+        IERC721(_idCollection).transferFrom(msg.sender, address(this), _idNFT);
+    }
+
+    function deposit(
+        uint256 _amount,
+        uint256 _currencyCode,
+        address _sender
+    ) public onlyEscrow {
+        balanceKSM[_sender][_currencyCode] =
+            balanceKSM[_sender][_currencyCode] +
+            _amount;
+    }
+
+    function buy(address _idCollection, uint256 _idNFT) public {
+        Offer memory offer = offers[asks[_idCollection][_idNFT]];
+        // 1. reduce balance
+        balanceKSM[msg.sender][offer.currencyCode] =
+            balanceKSM[msg.sender][offer.currencyCode] -
+            offer.price;
+        // 2. increase balance
+        balanceKSM[offer.userAddr][offer.currencyCode] =
+            balanceKSM[offer.userAddr][offer.currencyCode] +
+            offer.price;
+        // 3. close offer
+        offers[asks[_idCollection][_idNFT]].flagActive = 0;
+        // 4. transfer NFT to buyer
+        IERC721(_idCollection).transferFrom(address(this), msg.sender, _idNFT);
+    }
+
+    function withdraw(
+        uint256 _amount,
+        uint256 _currencyCode,
+        address _sender
+    ) public onlyEscrow returns (bool) {
+        balanceKSM[_sender][_currencyCode] =
+            balanceKSM[_sender][_currencyCode] -
+            _amount;
+        return true;
+    }
+
+    function escrowBalance(uint256 _currencyCode, address _sender)
+        public
+        view
+        returns (uint256)
+    {
+        return balanceKSM[_sender][_currencyCode];
+    }
+}
addedtests/src/eth/marketplace/MarketPlaceUNQ.abidiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/marketplace/MarketPlaceUNQ.abi
@@ -0,0 +1 @@
+[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"asks","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"asksbySeller","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_idCollection","type":"address"},{"internalType":"uint256","name":"_idNFT","type":"uint256"},{"internalType":"address","name":"_currencyCode","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"buy","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"_idCollection","type":"address"},{"internalType":"uint256","name":"_idNFT","type":"uint256"}],"name":"buy","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"offers","outputs":[{"internalType":"uint256","name":"idNFT","type":"uint256"},{"internalType":"address","name":"currencyCode","type":"address"},{"internalType":"uint256","name":"price","type":"uint256"},{"internalType":"uint256","name":"time","type":"uint256"},{"internalType":"address","name":"idCollection","type":"address"},{"internalType":"address","name":"userAddr","type":"address"},{"internalType":"uint8","name":"flagActive","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_price","type":"uint256"},{"internalType":"address","name":"_currencyCode","type":"address"},{"internalType":"address","name":"_idCollection","type":"address"},{"internalType":"uint256","name":"_idNFT","type":"uint256"},{"internalType":"uint8","name":"_active","type":"uint8"}],"name":"setAsk","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newOwner","type":"address"}],"name":"setowner","outputs":[],"stateMutability":"nonpayable","type":"function"}]
\ No newline at end of file
addedtests/src/eth/marketplace/MarketPlaceUNQ.bindiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/marketplace/MarketPlaceUNQ.bin
@@ -0,0 +1 @@
+608060405234801561001057600080fd5b50600380546001600160a01b03191633179055610f2d806100326000396000f3fe6080604052600436106100705760003560e01c806399f2d4eb1161004e57806399f2d4eb14610120578063b3ffb7601461014e578063cce7ec1314610161578063e22d4f7d1461017457600080fd5b8063592bd705146100755780637b1d6001146100975780638a72ea6a146100b7575b600080fd5b34801561008157600080fd5b50610095610090366004610d2b565b6101ac565b005b3480156100a357600080fd5b506100956100b2366004610e1b565b61021e565b3480156100c357600080fd5b506100d76100d2366004610e02565b61073d565b604080519788526001600160a01b03968716602089015287019490945260608601929092528316608085015290911660a083015260ff1660c082015260e0015b60405180910390f35b34801561012c57600080fd5b5061014061013b366004610d6c565b6107a0565b604051908152602001610117565b61009561015c366004610d98565b6107d1565b61009561016f366004610d6c565b610b0b565b34801561018057600080fd5b5061014061018f366004610d6c565b600160209081526000928352604080842090915290825290205481565b6003546001600160a01b031633146101fc5760405162461bcd60e51b815260206004820152600e60248201526d27b7363c9037bbb732b91031b0b760911b60448201526064015b60405180910390fd5b600380546001600160a01b0319166001600160a01b0392909216919091179055565b6040516331a9108f60e11b81526004810183905233906001600160a01b03851690636352211e9060240160206040518083038186803b15801561026057600080fd5b505afa158015610274573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102989190610d4f565b6001600160a01b0316146102e65760405162461bcd60e51b81526020600482015260156024820152742737ba103934b3b43a103a37b5b2b71037bbb732b960591b60448201526064016101f3565b6001600160a01b038316600090815260016020908152604080832085845290915281205490541580610350575060006001600160a01b03166000828154811061033157610331610ec9565b60009182526020909120600460069092020101546001600160a01b0316145b15610542576040805160e0810182528481526001600160a01b03878116602083019081529282018981524260608401908152888316608085019081523360a0860190815260ff89811660c08801908152600080546001808201835582805299517f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56360069092029182015599517f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5648b0180546001600160a01b0319908116928b1692909217905596517f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5658b015594517f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5668a015592517f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5678901805490961690871617909455517f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56890960180549151969094166001600160a81b031990911617600160a01b959092169490940217905590546104e99190610ea4565b6001600160a01b0385166000908152600160208181526040808420888552825280842094909455338352600290529181209054909161052791610ea4565b815460018101835560009283526020909220909101556106d3565b6040805160e0810182526001600160a01b038616600090815260016020908152838220878352905291822054825491928392909190811061058557610585610ec9565b6000918252602080832060069092029091015483526001600160a01b03808a168483015260408085018c9052426060860152908916835260018252808320888452909152812054815460809093019281106105e2576105e2610ec9565b60009182526020808320600460069093020191909101546001600160a01b039081168452338483015260ff871660409485015288168252600181528282208783529052908120548154811061063957610639610ec9565b60009182526020918290208351600690920201908155908201516001820180546001600160a01b039283166001600160a01b031991821617909155604084015160028401556060840151600384015560808401516004840180549184169190921617905560a08301516005909201805460c09094015160ff16600160a01b026001600160a81b031990941692909116919091179190911790555b6040516323b872dd60e01b81526001600160a01b038516906323b872dd9061070390339030908890600401610e80565b600060405180830381600087803b15801561071d57600080fd5b505af1158015610731573d6000803e3d6000fd5b50505050505050505050565b6000818154811061074d57600080fd5b60009182526020909120600690910201805460018201546002830154600384015460048501546005909501549395506001600160a01b03928316949193909291821691811690600160a01b900460ff1687565b600260205281600052604060002081815481106107bc57600080fd5b90600052602060002001600091509150505481565b6001600160a01b03841660009081526001602090815260408083208684529091528120548154829190811061080857610808610ec9565b60009182526020918290206040805160e0810182526006939093029091018054835260018101546001600160a01b03908116948401949094526002810154918301829052600381015460608401526004810154841660808401526005015492831660a0830152600160a01b90920460ff1660c08201529150821480156108a35750826001600160a01b031681602001516001600160a01b0316145b6109245760405162461bcd60e51b815260206004820152604660248201527f4e6f7420726967687420616d6f756e74206f722063757272656e63792073656e60448201527f742c206861766520746f20626520657175616c2063757272656e637920616e6460648201526520707269636560d01b608482015260a4016101f3565b80602001516001600160a01b03166323b872dd333084604001516040518463ffffffff1660e01b815260040161095c93929190610e80565b602060405180830381600087803b15801561097657600080fd5b505af115801561098a573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109ae9190610de0565b50602081015160a0820151604080840151905163a9059cbb60e01b81526001600160a01b039283166004820152602481019190915291169063a9059cbb90604401602060405180830381600087803b158015610a0957600080fd5b505af1158015610a1d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a419190610de0565b506001600160a01b038516600090815260016020908152604080832087845290915281205481548291908110610a7957610a79610ec9565b906000526020600020906006020160050160146101000a81548160ff021916908360ff160217905550846001600160a01b03166323b872dd3033876040518463ffffffff1660e01b8152600401610ad293929190610e80565b600060405180830381600087803b158015610aec57600080fd5b505af1158015610b00573d6000803e3d6000fd5b505050505050505050565b6001600160a01b038216600090815260016020908152604080832084845290915281205481548291908110610b4257610b42610ec9565b60009182526020918290206040805160e0810182526006939093029091018054835260018101546001600160a01b03908116948401949094526002810154918301829052600381015460608401526004810154841660808401526005015492831660a0830152600160a01b90920460ff1660c082015291503414610c225760405162461bcd60e51b815260206004820152603160248201527f4e6f7420726967687420616d6f756e7420554e512073656e742c206861766520604482015270746f20626520657175616c20707269636560781b60648201526084016101f3565b8060a001516001600160a01b03166108fc82604001519081150290604051600060405180830381858888f19350505050158015610c63573d6000803e3d6000fd5b506001600160a01b038316600090815260016020908152604080832085845290915281205481548291908110610c9b57610c9b610ec9565b906000526020600020906006020160050160146101000a81548160ff021916908360ff160217905550826001600160a01b03166323b872dd3033856040518463ffffffff1660e01b8152600401610cf493929190610e80565b600060405180830381600087803b158015610d0e57600080fd5b505af1158015610d22573d6000803e3d6000fd5b50505050505050565b600060208284031215610d3d57600080fd5b8135610d4881610edf565b9392505050565b600060208284031215610d6157600080fd5b8151610d4881610edf565b60008060408385031215610d7f57600080fd5b8235610d8a81610edf565b946020939093013593505050565b60008060008060808587031215610dae57600080fd5b8435610db981610edf565b9350602085013592506040850135610dd081610edf565b9396929550929360600135925050565b600060208284031215610df257600080fd5b81518015158114610d4857600080fd5b600060208284031215610e1457600080fd5b5035919050565b600080600080600060a08688031215610e3357600080fd5b853594506020860135610e4581610edf565b93506040860135610e5581610edf565b925060608601359150608086013560ff81168114610e7257600080fd5b809150509295509295909350565b6001600160a01b039384168152919092166020820152604081019190915260600190565b600082821015610ec457634e487b7160e01b600052601160045260246000fd5b500390565b634e487b7160e01b600052603260045260246000fd5b6001600160a01b0381168114610ef457600080fd5b5056fea2646970667358221220fbf33eb1e471c428b86ad6d948b8699fd08a127b923d5fe27ec1e1ec5f59627564736f6c63430008070033
\ No newline at end of file
addedtests/src/eth/marketplace/MarketPlaceUNQ.soldiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/marketplace/MarketPlaceUNQ.sol
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier:  Apache License
+pragma solidity >=0.8.0;
+
+interface IERC721 {
+    event Transfer(
+        address indexed from,
+        address indexed to,
+        uint256 indexed tokenId
+    );
+    event Approval(
+        address indexed owner,
+        address indexed approved,
+        uint256 indexed tokenId
+    );
+
+    function ownerOf(uint256 tokenId) external view returns (address owner);
+
+    function transferFrom(
+        address from,
+        address to,
+        uint256 tokenId
+    ) external;
+
+    function debug(string memory value) external;
+}
+
+interface IERC20 {
+    event Transfer(address indexed from, address indexed to, uint256 value);
+    event Approval(
+        address indexed owner,
+        address indexed spender,
+        uint256 value
+    );
+
+    function transfer(address recipient, uint256 amount)
+        external
+        returns (bool);
+
+    function transferFrom(
+        address sender,
+        address recipient,
+        uint256 amount
+    ) external returns (bool);
+}
+
+contract MarketPlaceUNQ {
+    struct Offer {
+        uint256 idNFT;
+        address currencyCode; //address of currency  token, = address(0) for UNQ
+        uint256 price;
+        uint256 time;
+        address idCollection;
+        address userAddr;
+        uint8 flagActive;
+    }
+    Offer[] public offers;
+
+    mapping(address => mapping(uint256 => uint256)) public asks; // [idCollection][idNFT] => idOffer
+
+    mapping(address => uint256[]) public asksbySeller; // [addressSeller] =>idOffer
+
+    address owner;
+
+    constructor() {
+        owner = msg.sender;
+    }
+
+    function setowner(address _newOwner) public onlyOwner {
+        owner = _newOwner;
+    }
+
+    modifier onlyOwner() {
+        require(msg.sender == owner, "Only owner can");
+        _;
+    }
+
+    /**
+     * Make bids (offers) to sell NFTs
+     */
+    function setAsk(
+        uint256 _price,
+        address _currencyCode,
+        address _idCollection,
+        uint256 _idNFT,
+        uint8 _active
+    ) public {
+        require(
+            IERC721(_idCollection).ownerOf(_idNFT) == msg.sender,
+            "Not right token owner"
+        );
+        uint256 offerID = asks[_idCollection][_idNFT];
+        if (offers.length == 0 || offers[offerID].idCollection == address(0)) {
+            offers.push(
+                Offer(
+                    _idNFT,
+                    _currencyCode,
+                    _price,
+                    block.timestamp,
+                    _idCollection,
+                    msg.sender,
+                    _active
+                )
+            );
+            asks[_idCollection][_idNFT] = offers.length - 1;
+            asksbySeller[msg.sender].push(offers.length - 1);
+        }
+        //edit existing offer
+        else {
+            offers[asks[_idCollection][_idNFT]] = Offer(
+                offers[asks[_idCollection][_idNFT]].idNFT,
+                _currencyCode,
+                _price,
+                block.timestamp,
+                offers[asks[_idCollection][_idNFT]].idCollection,
+                msg.sender,
+                _active
+            );
+        }
+
+        IERC721(_idCollection).transferFrom(msg.sender, address(this), _idNFT);
+    }
+
+    function buy(address _idCollection, uint256 _idNFT) public payable {
+        //buing for UNQ like as ethers
+        Offer memory offer = offers[asks[_idCollection][_idNFT]];
+        //1. check sent amount and send to seller
+        require(
+            msg.value == offer.price,
+            "Not right amount UNQ sent, have to be equal price"
+        );
+        payable(offer.userAddr).transfer(offer.price);
+        // 2. close offer
+        offers[asks[_idCollection][_idNFT]].flagActive = 0;
+        // 3. transfer NFT to buyer
+        IERC721(_idCollection).transferFrom(address(this), msg.sender, _idNFT);
+    }
+
+    function buy(
+        address _idCollection,
+        uint256 _idNFT,
+        address _currencyCode,
+        uint256 _amount
+    ) public payable {
+        Offer memory offer = offers[asks[_idCollection][_idNFT]];
+        //1. check sent amount and transfer from buyer to seller
+        require(
+            offer.price == _amount && offer.currencyCode == _currencyCode,
+            "Not right amount or currency sent, have to be equal currency and price"
+        );
+        // !!! transfer have to be approved to marketplace!
+        IERC20(offer.currencyCode).transferFrom(
+            msg.sender,
+            address(this),
+            offer.price
+        );
+        //to not disclojure buyer's address
+        IERC20(offer.currencyCode).transfer(offer.userAddr, offer.price);
+        // 2. close offer
+        offers[asks[_idCollection][_idNFT]].flagActive = 0;
+        // 3. transfer NFT to buyer
+        IERC721(_idCollection).transferFrom(address(this), msg.sender, _idNFT);
+    }
+}
addedtests/src/eth/marketplace/marketplace.test.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/marketplace/marketplace.test.ts
@@ -0,0 +1,198 @@
+import { readFile } from 'fs/promises';
+import { getBalanceSingle, transferBalanceExpectSuccess } from '../../substrate/get-balance';
+import privateKey from '../../substrate/privateKey';
+import { createCollectionExpectSuccess, createFungibleItemExpectSuccess, createItemExpectSuccess, queryNftOwner, transferExpectSuccess, transferFromExpectSuccess } from '../../util/helpers';
+import { collectionIdToAddress, createEthAccountWithBalance, executeEthTxOnSub, GAS_ARGS, itWeb3, subToEth, subToEthLowercase } from '../util/helpers';
+import { evmToAddress } from '@polkadot/util-crypto';
+import nonFungibleAbi from '../nonFungibleAbi.json';
+import fungibleAbi from '../fungibleAbi.json';
+import waitNewBlocks from '../../substrate/wait-new-blocks';
+import { expect } from 'chai';
+
+const PRICE = 2000n;
+
+describe('Matcher contract usage', () => {
+  itWeb3('With UNQ', async ({api, web3}) => {
+    const matcherOwner = await createEthAccountWithBalance(api, web3);
+    const Matcher = new web3.eth.Contract(JSON.parse((await readFile(`${__dirname}/MarketPlaceUNQ.abi`)).toString()), undefined, {
+      from: matcherOwner,
+      ...GAS_ARGS,
+    });
+    const matcher = await Matcher.deploy({ data: (await readFile(`${__dirname}/MarketPlaceUNQ.bin`)).toString() }).send({ from: matcherOwner });
+
+    const alice = privateKey('//Alice');
+    const collectionId = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+    const evmCollection = new web3.eth.Contract(nonFungibleAbi as any, collectionIdToAddress(collectionId), { from: matcherOwner });
+
+    const seller = privateKey('//Bob');
+  
+    const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', seller.address);
+
+    // To transfer item to matcher it first needs to be transfered to EVM account of bob
+    await transferExpectSuccess(collectionId, tokenId, seller, {ethereum: subToEth(seller.address)});
+    // Fees will be paid from EVM account, so we should have some balance here
+    await transferBalanceExpectSuccess(api, seller, evmToAddress(subToEth(seller.address)), 10n ** 18n);
+    await transferBalanceExpectSuccess(api, alice, evmToAddress(subToEth(alice.address)), 10n ** 18n);
+    await waitNewBlocks(api, 1);
+
+    // Token is owned by seller initially
+    expect(await queryNftOwner(api, collectionId, tokenId)).to.be.deep.equal({ ethereum: subToEthLowercase(seller.address) });
+
+    // Ask
+    {
+      await executeEthTxOnSub(api, seller, evmCollection, m => m.approve(matcher.options.address, tokenId));
+      await executeEthTxOnSub(api, seller, matcher, m => m.setAsk(PRICE, '0x0000000000000000000000000000000000000000', evmCollection.options.address, tokenId, 1));
+    }
+      
+    // Token is transferred to matcher
+    expect(await queryNftOwner(api, collectionId, tokenId)).to.be.deep.equal({ ethereum: matcher.options.address.toLowerCase() });
+
+    // Buy
+    {
+      const sellerBalanceBeforePurchase = await getBalanceSingle(api, seller.address);
+      // There is two functions named 'buy', so we should provide full signature
+      await executeEthTxOnSub(api, alice, matcher, m => m['buy(address,uint256)'](evmCollection.options.address, tokenId), { value: PRICE });
+      expect(await getBalanceSingle(api, seller.address) - sellerBalanceBeforePurchase === PRICE);
+    }
+
+    // Token is transferred to evm account of alice
+    expect(await queryNftOwner(api, collectionId, tokenId)).to.be.deep.equal({ ethereum: subToEthLowercase(alice.address) });
+
+  
+    // Transfer token to substrate side of alice
+    await transferFromExpectSuccess(collectionId, tokenId, alice, { ethereum: subToEth(alice.address) }, { substrate: alice.address });
+    
+    // Token is transferred to substrate account of alice, seller received funds
+    expect(await queryNftOwner(api, collectionId, tokenId)).to.be.deep.equal({ substrate: alice.address });
+  });
+
+  itWeb3('With custom ERC20', async ({api, web3}) => {
+    const matcherOwner = await createEthAccountWithBalance(api, web3);
+    const Matcher = new web3.eth.Contract(JSON.parse((await readFile(`${__dirname}/MarketPlaceUNQ.abi`)).toString()), undefined, {
+      from: matcherOwner,
+      ...GAS_ARGS,
+    });
+    const matcher = await Matcher.deploy({ data: (await readFile(`${__dirname}/MarketPlaceUNQ.bin`)).toString() }).send({ from: matcherOwner });
+
+    const alice = privateKey('//Alice');
+    const collectionId = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+    const evmCollection = new web3.eth.Contract(nonFungibleAbi as any, collectionIdToAddress(collectionId), { from: matcherOwner });
+
+    const fungibleId = await createCollectionExpectSuccess({ mode: { type: 'Fungible', decimalPoints: 0 } });
+    const evmFungible = new web3.eth.Contract(fungibleAbi as any, collectionIdToAddress(fungibleId), { from: matcherOwner, ...GAS_ARGS });
+    await createFungibleItemExpectSuccess(alice, fungibleId, { Value: PRICE }, { ethereum: subToEth(alice.address) });
+
+    const seller = privateKey('//Bob');
+  
+    const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', seller.address);
+
+    // To transfer item to matcher it first needs to be transfered to EVM account of bob
+    await transferExpectSuccess(collectionId, tokenId, seller, {ethereum: subToEth(seller.address)});
+    // Fees will be paid from EVM account, so we should have some balance here
+    await transferBalanceExpectSuccess(api, seller, evmToAddress(subToEth(seller.address)), 10n ** 18n);
+    await transferBalanceExpectSuccess(api, alice, evmToAddress(subToEth(alice.address)), 10n ** 18n);
+    await waitNewBlocks(api, 1);
+
+    // Token is owned by seller initially
+    expect(await queryNftOwner(api, collectionId, tokenId)).to.be.deep.equal({ ethereum: subToEthLowercase(seller.address) });
+
+    // Ask
+    {
+      await executeEthTxOnSub(api, seller, evmCollection, m => m.approve(matcher.options.address, tokenId));
+      await executeEthTxOnSub(api, seller, matcher, m => m.setAsk(PRICE, evmFungible.options.address, evmCollection.options.address, tokenId, 1));
+    }
+      
+    // Token is transferred to matcher
+    expect(await queryNftOwner(api, collectionId, tokenId)).to.be.deep.equal({ ethereum: matcher.options.address.toLowerCase() });
+
+    // Buy
+    {
+      const sellerBalanceBeforePurchase = BigInt(await evmFungible.methods.balanceOf(subToEth(seller.address)).call());
+      const buyerBalanceBeforePurchase = BigInt(await evmFungible.methods.balanceOf(subToEth(alice.address)).call());
+
+      await executeEthTxOnSub(api, alice, evmFungible, m => m.approve(matcher.options.address, PRICE));
+      // There is two functions named 'buy', so we should provide full signature
+      await executeEthTxOnSub(api, alice, matcher, m =>
+        m['buy(address,uint256,address,uint256)'](evmCollection.options.address, tokenId, evmFungible.options.address, PRICE));
+
+      // Approved price is removed from buyer balance, and added to seller
+      expect(BigInt(await evmFungible.methods.balanceOf(subToEth(seller.address)).call()) - sellerBalanceBeforePurchase === PRICE);
+      expect(buyerBalanceBeforePurchase - BigInt(await evmFungible.methods.balanceOf(subToEth(alice.address)).call()) === PRICE);
+    }
+
+    // Token is transferred to evm account of alice
+    expect(await queryNftOwner(api, collectionId, tokenId)).to.be.deep.equal({ ethereum: subToEthLowercase(alice.address) });
+
+  
+    // Transfer token to substrate side of alice
+    await transferFromExpectSuccess(collectionId, tokenId, alice, { ethereum: subToEth(alice.address) }, { substrate: alice.address });
+    
+    // Token is transferred to substrate account of alice
+    expect(await queryNftOwner(api, collectionId, tokenId)).to.be.deep.equal({ substrate: alice.address });
+  });
+
+  itWeb3('With escrow', async ({ api, web3 }) => {
+    const matcherOwner = await createEthAccountWithBalance(api, web3);
+    const escrow = await createEthAccountWithBalance(api, web3);
+    const Matcher = new web3.eth.Contract(JSON.parse((await readFile(`${__dirname}/MarketPlaceKSM.abi`)).toString()), undefined, {
+      from: matcherOwner,
+      ...GAS_ARGS,
+    });
+    const matcher = await Matcher.deploy({ data: (await readFile(`${__dirname}/MarketPlaceKSM.bin`)).toString(), arguments: [escrow] }).send({ from: matcherOwner });
+    
+    const ksmToken = 11;
+
+    const alice = privateKey('//Alice');
+    const collectionId = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+    const evmCollection = new web3.eth.Contract(nonFungibleAbi as any, collectionIdToAddress(collectionId), { from: matcherOwner });
+
+    const seller = privateKey('//Bob');
+  
+    const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', seller.address);
+
+    // To transfer item to matcher it first needs to be transfered to EVM account of bob
+    await transferExpectSuccess(collectionId, tokenId, seller, {ethereum: subToEth(seller.address)});
+    // Fees will be paid from EVM account, so we should have some balance here
+    await transferBalanceExpectSuccess(api, seller, evmToAddress(subToEth(seller.address)), 10n ** 18n);
+    await transferBalanceExpectSuccess(api, alice, evmToAddress(subToEth(alice.address)), 10n ** 18n);
+    await waitNewBlocks(api, 1);
+
+    // Token is owned by seller initially
+    expect(await queryNftOwner(api, collectionId, tokenId)).to.be.deep.equal({ ethereum: subToEthLowercase(seller.address) });
+
+    // Ask
+    {
+      await executeEthTxOnSub(api, seller, evmCollection, m => m.approve(matcher.options.address, tokenId));
+      await executeEthTxOnSub(api, seller, matcher, m => m.setAsk(PRICE, ksmToken, evmCollection.options.address, tokenId, 1));
+    }
+      
+    // Token is transferred to matcher
+    expect(await queryNftOwner(api, collectionId, tokenId)).to.be.deep.equal({ ethereum: matcher.options.address.toLowerCase() });
+
+    // Give buyer KSM
+    await matcher.methods.deposit(PRICE, ksmToken, subToEth(alice.address)).send({ from: escrow });
+    await waitNewBlocks(api, 1);
+  
+    // Buy
+    {
+      expect(await matcher.methods.escrowBalance(ksmToken, subToEth(seller.address)).call()).to.be.equal('0');
+      expect(await matcher.methods.escrowBalance(ksmToken, subToEth(alice.address)).call()).to.be.equal(PRICE.toString());
+
+      await executeEthTxOnSub(api, alice, matcher, m => m.buy(evmCollection.options.address, tokenId));
+      await waitNewBlocks(api, 1);
+
+      // Price is removed from buyer balance, and added to seller
+      expect(await matcher.methods.escrowBalance(ksmToken, subToEth(alice.address)).call()).to.be.equal('0');
+      expect(await matcher.methods.escrowBalance(ksmToken, subToEth(seller.address)).call()).to.be.equal(PRICE.toString());
+    }
+
+    // Token is transferred to evm account of alice
+    expect(await queryNftOwner(api, collectionId, tokenId)).to.be.deep.equal({ ethereum: subToEthLowercase(alice.address) });
+  
+    // Transfer token to substrate side of alice
+    await transferFromExpectSuccess(collectionId, tokenId, alice, { ethereum: subToEth(alice.address) }, { substrate: alice.address });
+    
+    // Token is transferred to substrate account of alice, seller received funds
+    expect(await queryNftOwner(api, collectionId, tokenId)).to.be.deep.equal({ substrate: alice.address });
+  });
+});
modifiedtests/src/eth/util/helpers.tsdiffbeforeafterboth
--- a/tests/src/eth/util/helpers.ts
+++ b/tests/src/eth/util/helpers.ts
@@ -143,10 +143,13 @@
   return normalizeEvents(out);
 }
 
+export function subToEthLowercase(eth: string): string {
+  const bytes = addressToEvm(eth);
+  return '0x' + Buffer.from(bytes).toString('hex');
+}
+
 export function subToEth(eth: string): string {
-  const bytes = addressToEvm(eth);
-  const string = '0x' + Buffer.from(bytes).toString('hex');
-  return Web3.utils.toChecksumAddress(string);
+  return Web3.utils.toChecksumAddress(subToEthLowercase(eth));
 }
 
 export function compileContract(name: string, src: string) {
@@ -236,6 +239,20 @@
   return new web3.eth.Contract(contractHelpersAbi as any, '0x842899ECF380553E8a4de75bF534cdf6fBF64049', {from: caller, ...GAS_ARGS});
 }
 
+export async function executeEthTxOnSub(api: ApiPromise, from: IKeyringPair, to: any, mkTx: (methods: any) => any, { value = 0 }: {value?: bigint | number} = { }) {
+  const tx = api.tx.evm.call(
+    subToEth(from.address),
+    to.options.address,
+    mkTx(to.methods).encodeABI(),
+    value,
+    GAS_ARGS.gas,
+    GAS_ARGS.gasPrice,
+    null,
+  );
+  const events = await submitTransactionAsync(from, tx);
+  expect(events.find(({ event: {section, method}})=>section === 'evm' && method === 'Executed')).to.be.not.undefined;
+}
+
 export async function ethBalanceViaSub(api: ApiPromise, address: string): Promise<bigint> {
   return (await getBalance(api, [evmToAddress(address)]))[0];
 }
modifiedtests/src/util/helpers.tsdiffbeforeafterboth
--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -1168,3 +1168,7 @@
     return (await api.query.nft.collectionById(collectionId)).toJSON() as unknown as ICollectionInterface;
   });
 }
+
+export async function queryNftOwner(api: ApiPromise, collectionId: number, tokenId: number): Promise<CrossAccountId> {
+  return normalizeAccountId((await api.query.nft.nftItemList(collectionId, tokenId) as any).toJSON().Owner);
+}
\ No newline at end of file