git.delta.rocks / unique-network / refs/commits / 281677a9f1f9

difftreelog

source

pallets/nft/src/lib.rs73.7 KiBsourcehistory
1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]7#![cfg_attr(not(feature = "std"), no_std)]8#![allow(9	clippy::too_many_arguments,10	clippy::unnecessary_mut_passed,11	clippy::unused_unit12)]1314extern crate alloc;1516pub use serde::{Serialize, Deserialize};1718pub use frame_support::{19	construct_runtime, decl_event, decl_module, decl_storage, decl_error,20	dispatch::DispatchResult,21	ensure, fail, parameter_types,22	traits::{23		Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,24		Randomness, IsSubType, WithdrawReasons,25	},26	weights::{27		constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},28		DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,29		WeightToFeePolynomial, DispatchClass,30	},31	StorageValue, transactional,32};3334use frame_system::{self as system, ensure_signed};35use sp_core::H160;36use sp_std::vec;37use sp_runtime::{DispatchError, sp_std::prelude::Vec};38use core::ops::{Deref, DerefMut};39use nft_data_structs::{40	MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, MAX_REFUNGIBLE_PIECES,41	CUSTOM_DATA_LIMIT, COLLECTION_NUMBER_LIMIT, ACCOUNT_TOKEN_OWNERSHIP_LIMIT,42	VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, COLLECTION_ADMINS_LIMIT,43	OFFCHAIN_SCHEMA_LIMIT, MAX_TOKEN_PREFIX_LENGTH, MAX_COLLECTION_NAME_LENGTH,44	MAX_COLLECTION_DESCRIPTION_LENGTH, AccessMode, Collection, CreateItemData, CollectionLimits,45	CollectionId, CollectionMode, TokenId, SchemaVersion, SponsorshipState, Ownership, NftItemType,46	FungibleItemType, ReFungibleItemType,47};4849#[cfg(test)]50mod mock;5152#[cfg(test)]53mod tests;5455mod eth;56mod sponsorship;57pub use sponsorship::NftSponsorshipHandler;58pub use eth::sponsoring::NftEthSponsorshipHandler;5960pub use eth::NftErcSupport;61pub use eth::account::*;62use eth::erc::{ERC20Events, ERC721Events};6364#[cfg(feature = "runtime-benchmarks")]65mod benchmarking;66pub mod weights;67use weights::WeightInfo;6869decl_error! {70	/// Error for non-fungible-token module.71	pub enum Error for Module<T: Config> {72		/// Total collections bound exceeded.73		TotalCollectionsLimitExceeded,74		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.75		CollectionDecimalPointLimitExceeded,76		/// Collection name can not be longer than 63 char.77		CollectionNameLimitExceeded,78		/// Collection description can not be longer than 255 char.79		CollectionDescriptionLimitExceeded,80		/// Token prefix can not be longer than 15 char.81		CollectionTokenPrefixLimitExceeded,82		/// This collection does not exist.83		CollectionNotFound,84		/// Item not exists.85		TokenNotFound,86		/// Admin not found87		AdminNotFound,88		/// Arithmetic calculation overflow.89		NumOverflow,90		/// Account already has admin role.91		AlreadyAdmin,92		/// You do not own this collection.93		NoPermission,94		/// This address is not set as sponsor, use setCollectionSponsor first.95		ConfirmUnsetSponsorFail,96		/// Collection is not in mint mode.97		PublicMintingNotAllowed,98		/// Sender parameter and item owner must be equal.99		MustBeTokenOwner,100		/// Item balance not enough.101		TokenValueTooLow,102		/// Size of item is too large.103		NftSizeLimitExceeded,104		/// No approve found105		ApproveNotFound,106		/// Requested value more than approved.107		TokenValueNotEnough,108		/// Only approved addresses can call this method.109		ApproveRequired,110		/// Address is not in white list.111		AddresNotInWhiteList,112		/// Number of collection admins bound exceeded.113		CollectionAdminsLimitExceeded,114		/// Owned tokens by a single address bound exceeded.115		AddressOwnershipLimitExceeded,116		/// Length of items properties must be greater than 0.117		EmptyArgument,118		/// const_data exceeded data limit.119		TokenConstDataLimitExceeded,120		/// variable_data exceeded data limit.121		TokenVariableDataLimitExceeded,122		/// Not NFT item data used to mint in NFT collection.123		NotNftDataUsedToMintNftCollectionToken,124		/// Not Fungible item data used to mint in Fungible collection.125		NotFungibleDataUsedToMintFungibleCollectionToken,126		/// Not Re Fungible item data used to mint in Re Fungible collection.127		NotReFungibleDataUsedToMintReFungibleCollectionToken,128		/// Unexpected collection type.129		UnexpectedCollectionType,130		/// Can't store metadata in fungible tokens.131		CantStoreMetadataInFungibleTokens,132		/// Collection token limit exceeded133		CollectionTokenLimitExceeded,134		/// Account token limit exceeded per collection135		AccountTokenLimitExceeded,136		/// Collection limit bounds per collection exceeded137		CollectionLimitBoundsExceeded,138		/// Tried to enable permissions which are only permitted to be disabled139		OwnerPermissionsCantBeReverted,140		/// Schema data size limit bound exceeded141		SchemaDataLimitExceeded,142		/// Maximum refungibility exceeded143		WrongRefungiblePieces,144		/// createRefungible should be called with one owner145		BadCreateRefungibleCall,146		/// Gas limit exceeded147		OutOfGas,148		/// Metadata update denied by collection settings149		MetadataUpdateDenied,150		/// Metadata update flag become unmutable with None option151		MetadataFlagFrozen,152		/// Collection settings not allowing items transferring153		TransferNotAllowed,154		/// Can't transfer tokens to ethereum zero address155		AddressIsZero,156	}157}158159#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]160pub struct CollectionHandle<T: Config> {161	pub id: CollectionId,162	collection: Collection<T>,163	recorder: pallet_evm_coder_substrate::SubstrateRecorder<T>,164}165impl<T: Config> CollectionHandle<T> {166	pub fn get_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {167		<CollectionById<T>>::get(id).map(|collection| Self {168			id,169			collection,170			recorder: pallet_evm_coder_substrate::SubstrateRecorder::new(171				eth::collection_id_to_address(id),172				gas_limit,173			),174		})175	}176	pub fn get(id: CollectionId) -> Option<Self> {177		Self::get_with_gas_limit(id, u64::MAX)178	}179	pub fn log(&self, log: impl evm_coder::ToLog) -> DispatchResult {180		self.recorder.log_sub(log)181	}182	#[allow(dead_code)]183	fn consume_gas(&self, gas: u64) -> DispatchResult {184		self.recorder.consume_gas_sub(gas)185	}186	fn consume_sload(&self) -> DispatchResult {187		self.recorder.consume_sload_sub()188	}189	fn consume_sstore(&self) -> DispatchResult {190		self.recorder.consume_sstore_sub()191	}192	pub fn submit_logs(self) -> DispatchResult {193		self.recorder.submit_logs()194	}195	pub fn save(self) -> DispatchResult {196		self.recorder.submit_logs()?;197		<CollectionById<T>>::insert(self.id, self.collection);198		Ok(())199	}200}201impl<T: Config> Deref for CollectionHandle<T> {202	type Target = Collection<T>;203204	fn deref(&self) -> &Self::Target {205		&self.collection206	}207}208209impl<T: Config> DerefMut for CollectionHandle<T> {210	fn deref_mut(&mut self) -> &mut Self::Target {211		&mut self.collection212	}213}214215pub trait Config: system::Config + pallet_evm_coder_substrate::Config + Sized {216	type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;217218	/// Weight information for extrinsics in this pallet.219	type WeightInfo: WeightInfo;220221	type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;222	type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;223224	type CrossAccountId: CrossAccountId<Self::AccountId>;225	type Currency: Currency<Self::AccountId>;226	type CollectionCreationPrice: Get<227		<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,228	>;229	type TreasuryAccountId: Get<Self::AccountId>;230}231232type SelfWeightOf<T> = <T as Config>::WeightInfo;233234trait WeightInfoHelpers: WeightInfo {235	fn transfer() -> Weight {236		Self::transfer_nft()237			.max(Self::transfer_fungible())238			.max(Self::transfer_refungible())239	}240	fn transfer_from() -> Weight {241		Self::transfer_from_nft()242			.max(Self::transfer_from_fungible())243			.max(Self::transfer_from_refungible())244	}245	fn approve() -> Weight {246		// TODO: refungible, fungible247		Self::approve_nft()248	}249	fn set_variable_meta_data(data: u32) -> Weight {250		// TODO: refungible251		Self::set_variable_meta_data_nft(data)252	}253	fn create_item(data: u32) -> Weight {254		Self::create_item_nft(data)255			.max(Self::create_item_fungible())256			.max(Self::create_item_refungible(data))257	}258	fn create_multiple_items(amount: u32) -> Weight {259		Self::create_multiple_items_nft(amount)260			.max(Self::create_multiple_items_fungible(amount))261			.max(Self::create_multiple_items_refungible(amount))262	}263	fn burn_item() -> Weight {264		// TODO: refungible, fungible265		Self::burn_item_nft()266	}267}268impl<T: WeightInfo> WeightInfoHelpers for T {}269270// # Used definitions271//272// ## User control levels273//274// chain-controlled - key is uncontrolled by user275//                    i.e autoincrementing index276//                    can use non-cryptographic hash277// real - key is controlled by user278//        but it is hard to generate enough colliding values, i.e owner of signed txs279//        can use non-cryptographic hash280// controlled - key is completly controlled by users281//              i.e maps with mutable keys282//              should use cryptographic hash283//284// ## User control level downgrade reasons285//286// ?1 - chain-controlled -> controlled287//      collections/tokens can be destroyed, resulting in massive holes288// ?2 - chain-controlled -> controlled289//      same as ?1, but can be only added, resulting in easier exploitation290// ?3 - real -> controlled291//      no confirmation required, so addresses can be easily generated292decl_storage! {293	trait Store for Module<T: Config> as Nft {294295		//#region Private members296		/// Id of next collection297		CreatedCollectionCount: u32;298		/// Used for migrations299		ChainVersion: u64;300		/// Id of last collection token301		/// Collection id (controlled?1)302		ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;303		//#endregion304305		//#region Bound counters306		/// Amount of collections destroyed, used for total amount tracking with307		/// CreatedCollectionCount308		DestroyedCollectionCount: u32;309		/// Total amount of account owned tokens (NFTs + RFTs + unique fungibles)310		/// Account id (real)311		pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;312		//#endregion313314		//#region Basic collections315		/// Collection info316		/// Collection id (controlled?1)317		pub CollectionById get(fn collection_id) config(): map hasher(blake2_128_concat) CollectionId => Option<Collection<T>> = None;318		/// List of collection admins319		/// Collection id (controlled?2)320		pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec<T::CrossAccountId>;321		/// Whitelisted collection users322		/// Collection id (controlled?2), user id (controlled?3)323		pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool;324		//#endregion325326		/// How many of collection items user have327		/// Collection id (controlled?2), account id (real)328		pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128;329330		/// Amount of items which spender can transfer out of owners account (via transferFrom)331		/// Collection id (controlled?2), (token id (controlled ?2) + owner account id (real) + spender account id (controlled?3))332		/// TODO: Off chain worker should remove from this map when token gets removed333		pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128;334335		//#region Item collections336		/// Collection id (controlled?2), token id (controlled?1)337		pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<NftItemType<T::CrossAccountId>>;338		/// Collection id (controlled?2), owner (controlled?2)339		pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType;340		/// Collection id (controlled?2), token id (controlled?1)341		pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<ReFungibleItemType<T::CrossAccountId>>;342		//#endregion343344		//#region Index list345		/// Collection id (controlled?2), tokens owner (controlled?2)346		pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;347		//#endregion348349		//#region Tokens transfer rate limit baskets350		/// (Collection id (controlled?2), who created (real))351		/// TODO: Off chain worker should remove from this map when collection gets removed352		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;353		/// Collection id (controlled?2), token id (controlled?2)354		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;355		/// Collection id (controlled?2), owning user (real)356		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;357		/// Collection id (controlled?2), token id (controlled?2)358		pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;359		//#endregion360361		/// Variable metadata sponsoring362		/// Collection id (controlled?2), token id (controlled?2)363		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;364	}365	add_extra_genesis {366		build(|config: &GenesisConfig<T>| {367			// Modification of storage368			for (_num, _c) in &config.collection_id {369				<Module<T>>::init_collection(_c);370			}371372			for (_num, _c, _i) in &config.nft_item_id {373				<Module<T>>::init_nft_token(*_c, _i);374			}375376			for (collection_id, account_id, fungible_item) in &config.fungible_item_id {377				<Module<T>>::init_fungible_token(*collection_id, &T::CrossAccountId::from_sub(account_id.clone()), fungible_item);378			}379380			for (_num, _c, _i) in &config.refungible_item_id {381				<Module<T>>::init_refungible_token(*_c, _i);382			}383		})384	}385}386387decl_event!(388	pub enum Event<T>389	where390		AccountId = <T as frame_system::Config>::AccountId,391		CrossAccountId = <T as Config>::CrossAccountId,392	{393		/// New collection was created394		///395		/// # Arguments396		///397		/// * collection_id: Globally unique identifier of newly created collection.398		///399		/// * mode: [CollectionMode] converted into u8.400		///401		/// * account_id: Collection owner.402		CollectionCreated(CollectionId, u8, AccountId),403404		/// New item was created.405		///406		/// # Arguments407		///408		/// * collection_id: Id of the collection where item was created.409		///410		/// * item_id: Id of an item. Unique within the collection.411		///412		/// * recipient: Owner of newly created item413		ItemCreated(CollectionId, TokenId, CrossAccountId),414415		/// Collection item was burned.416		///417		/// # Arguments418		///419		/// collection_id.420		///421		/// item_id: Identifier of burned NFT.422		ItemDestroyed(CollectionId, TokenId),423424		/// Item was transferred425		///426		/// * collection_id: Id of collection to which item is belong427		///428		/// * item_id: Id of an item429		///430		/// * sender: Original owner of item431		///432		/// * recipient: New owner of item433		///434		/// * amount: Always 1 for NFT435		Transfer(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),436437		/// * collection_id438		///439		/// * item_id440		///441		/// * sender442		///443		/// * spender444		///445		/// * amount446		Approved(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),447	}448);449450decl_module! {451	pub struct Module<T: Config> for enum Call452	where453		origin: T::Origin454	{455		fn deposit_event() = default;456		const CollectionAdminsLimit: u64 = COLLECTION_ADMINS_LIMIT;457		type Error = Error<T>;458459		fn on_initialize(_now: T::BlockNumber) -> Weight {460			0461		}462463		/// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.464		///465		/// # Permissions466		///467		/// * Anyone.468		///469		/// # Arguments470		///471		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.472		///473		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.474		///475		/// * token_prefix: UTF-8 string with token prefix.476		///477		/// * mode: [CollectionMode] collection type and type dependent data.478		// returns collection ID479		#[weight = <SelfWeightOf<T>>::create_collection()]480		#[transactional]481		pub fn create_collection(origin,482								 collection_name: Vec<u16>,483								 collection_description: Vec<u16>,484								 token_prefix: Vec<u8>,485								 mode: CollectionMode) -> DispatchResult {486487			// Anyone can create a collection488			let who = ensure_signed(origin)?;489490			// Take a (non-refundable) deposit of collection creation491			let mut imbalance = <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();492			imbalance.subsume(<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(493				&T::TreasuryAccountId::get(),494				T::CollectionCreationPrice::get(),495			));496			<T as Config>::Currency::settle(497				&who,498				imbalance,499				WithdrawReasons::TRANSFER,500				ExistenceRequirement::KeepAlive,501			).map_err(|_| Error::<T>::NoPermission)?;502503			let decimal_points = match mode {504				CollectionMode::Fungible(points) => points,505				_ => 0506			};507508			let created_count = CreatedCollectionCount::get();509			let destroyed_count = DestroyedCollectionCount::get();510511			// bound Total number of collections512			ensure!(created_count - destroyed_count < COLLECTION_NUMBER_LIMIT, Error::<T>::TotalCollectionsLimitExceeded);513514			// check params515			ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);516			ensure!(collection_name.len() <= MAX_COLLECTION_NAME_LENGTH, Error::<T>::CollectionNameLimitExceeded);517			ensure!(collection_description.len() <= MAX_COLLECTION_DESCRIPTION_LENGTH, Error::<T>::CollectionDescriptionLimitExceeded);518			ensure!(token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH, Error::<T>::CollectionTokenPrefixLimitExceeded);519520			// Generate next collection ID521			let next_id = created_count522				.checked_add(1)523				.ok_or(Error::<T>::NumOverflow)?;524525			CreatedCollectionCount::put(next_id);526527			let limits = CollectionLimits {528				sponsored_data_size: CUSTOM_DATA_LIMIT,529				..Default::default()530			};531532			// Create new collection533			let new_collection = Collection {534				owner: who.clone(),535				name: collection_name,536				mode: mode.clone(),537				mint_mode: false,538				access: AccessMode::Normal,539				description: collection_description,540				decimal_points,541				token_prefix,542				offchain_schema: Vec::new(),543				schema_version: SchemaVersion::ImageURL,544				sponsorship: SponsorshipState::Disabled,545				variable_on_chain_schema: Vec::new(),546				const_on_chain_schema: Vec::new(),547				limits,548				meta_update_permission: MetaUpdatePermission::default(),549				transfers_enabled: true,550			};551552			// Add new collection to map553			<CollectionById<T>>::insert(next_id, new_collection);554555			// call event556			Self::deposit_event(RawEvent::CollectionCreated(next_id, mode.id(), who));557558			Ok(())559		}560561		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.562		///563		/// # Permissions564		///565		/// * Collection Owner.566		///567		/// # Arguments568		///569		/// * collection_id: collection to destroy.570		#[weight = <SelfWeightOf<T>>::destroy_collection()]571		#[transactional]572		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {573574			let sender = ensure_signed(origin)?;575			let collection = Self::get_collection(collection_id)?;576			Self::check_owner_permissions(&collection, &sender)?;577			if !collection.limits.owner_can_destroy {578				fail!(Error::<T>::NoPermission);579			}580581			<AddressTokens<T>>::remove_prefix(collection_id, None);582			<Allowances<T>>::remove_prefix(collection_id, None);583			<Balance<T>>::remove_prefix(collection_id, None);584			<ItemListIndex>::remove(collection_id);585			<AdminList<T>>::remove(collection_id);586			<CollectionById<T>>::remove(collection_id);587			<WhiteList<T>>::remove_prefix(collection_id, None);588589			<NftItemList<T>>::remove_prefix(collection_id, None);590			<FungibleItemList<T>>::remove_prefix(collection_id, None);591			<ReFungibleItemList<T>>::remove_prefix(collection_id, None);592593			<NftTransferBasket<T>>::remove_prefix(collection_id, None);594			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);595			<ReFungibleTransferBasket<T>>::remove_prefix(collection_id, None);596597			<VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);598599			DestroyedCollectionCount::put(DestroyedCollectionCount::get()600				.checked_add(1)601				.ok_or(Error::<T>::NumOverflow)?);602603			Ok(())604		}605606		/// Add an address to white list.607		///608		/// # Permissions609		///610		/// * Collection Owner611		/// * Collection Admin612		///613		/// # Arguments614		///615		/// * collection_id.616		///617		/// * address.618		#[weight = <SelfWeightOf<T>>::add_to_white_list()]619		#[transactional]620		pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{621622			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);623			let collection = Self::get_collection(collection_id)?;624625			Self::toggle_white_list_internal(626				&sender,627				&collection,628				&address,629				true,630			)?;631632			Ok(())633		}634635		/// Remove an address from white list.636		///637		/// # Permissions638		///639		/// * Collection Owner640		/// * Collection Admin641		///642		/// # Arguments643		///644		/// * collection_id.645		///646		/// * address.647		#[weight = <SelfWeightOf<T>>::remove_from_white_list()]648		#[transactional]649		pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{650651			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);652			let collection = Self::get_collection(collection_id)?;653654			Self::toggle_white_list_internal(655				&sender,656				&collection,657				&address,658				false,659			)?;660661			Ok(())662		}663664		/// Toggle between normal and white list access for the methods with access for `Anyone`.665		///666		/// # Permissions667		///668		/// * Collection Owner.669		///670		/// # Arguments671		///672		/// * collection_id.673		///674		/// * mode: [AccessMode]675		#[weight = <SelfWeightOf<T>>::set_public_access_mode()]676		#[transactional]677		pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult678		{679			let sender = ensure_signed(origin)?;680681			let mut target_collection = Self::get_collection(collection_id)?;682			Self::check_owner_permissions(&target_collection, &sender)?;683			target_collection.access = mode;684			target_collection.save()685		}686687		/// Allows Anyone to create tokens if:688		/// * White List is enabled, and689		/// * Address is added to white list, and690		/// * This method was called with True parameter691		///692		/// # Permissions693		/// * Collection Owner694		///695		/// # Arguments696		///697		/// * collection_id.698		///699		/// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.700		#[weight = <SelfWeightOf<T>>::set_mint_permission()]701		#[transactional]702		pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult703		{704			let sender = ensure_signed(origin)?;705706			let mut target_collection = Self::get_collection(collection_id)?;707			Self::check_owner_permissions(&target_collection, &sender)?;708			target_collection.mint_mode = mint_permission;709			target_collection.save()710		}711712		/// Change the owner of the collection.713		///714		/// # Permissions715		///716		/// * Collection Owner.717		///718		/// # Arguments719		///720		/// * collection_id.721		///722		/// * new_owner.723		#[weight = <SelfWeightOf<T>>::change_collection_owner()]724		#[transactional]725		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {726727			let sender = ensure_signed(origin)?;728			let mut target_collection = Self::get_collection(collection_id)?;729			Self::check_owner_permissions(&target_collection, &sender)?;730			target_collection.owner = new_owner;731			target_collection.save()732		}733734		/// Adds an admin of the Collection.735		/// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership.736		///737		/// # Permissions738		///739		/// * Collection Owner.740		/// * Collection Admin.741		///742		/// # Arguments743		///744		/// * collection_id: ID of the Collection to add admin for.745		///746		/// * new_admin_id: Address of new admin to add.747		#[weight = <SelfWeightOf<T>>::add_collection_admin()]748		#[transactional]749		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {750			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);751			let collection = Self::get_collection(collection_id)?;752			Self::check_owner_or_admin_permissions(&collection, &sender)?;753			let mut admin_arr = <AdminList<T>>::get(collection_id);754755			match admin_arr.binary_search(&new_admin_id) {756				Ok(_) => {},757				Err(idx) => {758					ensure!(admin_arr.len() < COLLECTION_ADMINS_LIMIT as usize, Error::<T>::CollectionAdminsLimitExceeded);759					admin_arr.insert(idx, new_admin_id);760					<AdminList<T>>::insert(collection_id, admin_arr);761				}762			}763			Ok(())764		}765766		/// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.767		///768		/// # Permissions769		///770		/// * Collection Owner.771		/// * Collection Admin.772		///773		/// # Arguments774		///775		/// * collection_id: ID of the Collection to remove admin for.776		///777		/// * account_id: Address of admin to remove.778		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]779		#[transactional]780		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {781			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);782			let collection = Self::get_collection(collection_id)?;783			Self::check_owner_or_admin_permissions(&collection, &sender)?;784			let mut admin_arr = <AdminList<T>>::get(collection_id);785786			if let Ok(idx) = admin_arr.binary_search(&account_id) {787				admin_arr.remove(idx);788				<AdminList<T>>::insert(collection_id, admin_arr);789			}790			Ok(())791		}792793		/// # Permissions794		///795		/// * Collection Owner796		///797		/// # Arguments798		///799		/// * collection_id.800		///801		/// * new_sponsor.802		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]803		#[transactional]804		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {805			let sender = ensure_signed(origin)?;806			let mut target_collection = Self::get_collection(collection_id)?;807			Self::check_owner_permissions(&target_collection, &sender)?;808809			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);810			target_collection.save()811		}812813		/// # Permissions814		///815		/// * Sponsor.816		///817		/// # Arguments818		///819		/// * collection_id.820		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]821		#[transactional]822		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {823			let sender = ensure_signed(origin)?;824825			let mut target_collection = Self::get_collection(collection_id)?;826			ensure!(827				target_collection.sponsorship.pending_sponsor() == Some(&sender),828				Error::<T>::ConfirmUnsetSponsorFail829			);830831			target_collection.sponsorship = SponsorshipState::Confirmed(sender);832			target_collection.save()833		}834835		/// Switch back to pay-per-own-transaction model.836		///837		/// # Permissions838		///839		/// * Collection owner.840		///841		/// # Arguments842		///843		/// * collection_id.844		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]845		#[transactional]846		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {847			let sender = ensure_signed(origin)?;848849			let mut target_collection = Self::get_collection(collection_id)?;850			Self::check_owner_permissions(&target_collection, &sender)?;851852			target_collection.sponsorship = SponsorshipState::Disabled;853			target_collection.save()854		}855856		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.857		///858		/// # Permissions859		///860		/// * Collection Owner.861		/// * Collection Admin.862		/// * Anyone if863		///     * White List is enabled, and864		///     * Address is added to white list, and865		///     * MintPermission is enabled (see SetMintPermission method)866		///867		/// # Arguments868		///869		/// * collection_id: ID of the collection.870		///871		/// * owner: Address, initial owner of the NFT.872		///873		/// * data: Token data to store on chain.874		// #[weight =875		// (130_000_000 as Weight)876		// .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))877		// .saturating_add(RocksDbWeight::get().reads(10 as Weight))878		// .saturating_add(RocksDbWeight::get().writes(8 as Weight))]879880		#[weight = <SelfWeightOf<T>>::create_item(data.data_size() as u32)]881		#[transactional]882		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResult {883			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);884			let collection = Self::get_collection(collection_id)?;885886			Self::create_item_internal(&sender, &collection, &owner, data)?;887888			collection.submit_logs()889		}890891		/// This method creates multiple items in a collection created with CreateCollection method.892		///893		/// # Permissions894		///895		/// * Collection Owner.896		/// * Collection Admin.897		/// * Anyone if898		///     * White List is enabled, and899		///     * Address is added to white list, and900		///     * MintPermission is enabled (see SetMintPermission method)901		///902		/// # Arguments903		///904		/// * collection_id: ID of the collection.905		///906		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].907		///908		/// * owner: Address, initial owner of the NFT.909		#[weight = <SelfWeightOf<T>>::create_multiple_items(items_data.len() as u32)]910		#[transactional]911		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResult {912913			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);914			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);915			let collection = Self::get_collection(collection_id)?;916917			Self::create_multiple_items_internal(&sender, &collection, &owner, items_data)?;918919			collection.submit_logs()920		}921922		// TODO! transaction weight923924		/// Set transfers_enabled value for particular collection925		///926		/// # Permissions927		///928		/// * Collection Owner.929		///930		/// # Arguments931		///932		/// * collection_id: ID of the collection.933		///934		/// * value: New flag value.935		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]936		#[transactional]937		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {938939			let sender = ensure_signed(origin)?;940			let mut target_collection = Self::get_collection(collection_id)?;941942			Self::check_owner_permissions(&target_collection, &sender)?;943944			target_collection.transfers_enabled = value;945			target_collection.save()946		}947948		// TODO! transaction weight949		/// Set meta_update_permission value for particular collection950		///951		/// # Permissions952		///953		/// * Collection Owner.954		///955		/// # Arguments956		///957		/// * collection_id: ID of the collection.958		///959		/// * value: New flag value.960		#[weight = <T as Config>::WeightInfo::burn_item()]961		#[transactional]962		pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {963964			let sender = ensure_signed(origin)?;965			let mut target_collection = Self::get_collection(collection_id)?;966967			ensure!(968				target_collection.meta_update_permission != MetaUpdatePermission::None,969				Error::<T>::MetadataFlagFrozen970			);971			Self::check_owner_permissions(&target_collection, &sender)?;972973			target_collection.meta_update_permission = value;974			Self::save_collection(target_collection);975976			Ok(())977		}978979		/// Destroys a concrete instance of NFT.980		///981		/// # Permissions982		///983		/// * Collection Owner.984		/// * Collection Admin.985		/// * Current NFT Owner.986		///987		/// # Arguments988		///989		/// * collection_id: ID of the collection.990		///991		/// * item_id: ID of NFT to burn.992		#[weight = <SelfWeightOf<T>>::burn_item()]993		#[transactional]994		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {995996			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);997			let target_collection = Self::get_collection(collection_id)?;998999			Self::burn_item_internal(&sender, &target_collection, item_id, value)?;10001001			target_collection.submit_logs()1002		}10031004		/// Change ownership of the token.1005		///1006		/// # Permissions1007		///1008		/// * Collection Owner1009		/// * Collection Admin1010		/// * Current NFT owner1011		///1012		/// # Arguments1013		///1014		/// * recipient: Address of token recipient.1015		///1016		/// * collection_id.1017		///1018		/// * item_id: ID of the item1019		///     * Non-Fungible Mode: Required.1020		///     * Fungible Mode: Ignored.1021		///     * Re-Fungible Mode: Required.1022		///1023		/// * value: Amount to transfer.1024		///     * Non-Fungible Mode: Ignored1025		///     * Fungible Mode: Must specify transferred amount1026		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1027		#[weight = <SelfWeightOf<T>>::transfer()]1028		#[transactional]1029		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {1030			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1031			let collection = Self::get_collection(collection_id)?;10321033			Self::transfer_internal(&sender, &recipient, &collection, item_id, value)?;10341035			collection.submit_logs()1036		}10371038		/// Set, change, or remove approved address to transfer the ownership of the NFT.1039		///1040		/// # Permissions1041		///1042		/// * Collection Owner1043		/// * Collection Admin1044		/// * Current NFT owner1045		///1046		/// # Arguments1047		///1048		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1049		///1050		/// * collection_id.1051		///1052		/// * item_id: ID of the item.1053		#[weight = <SelfWeightOf<T>>::approve()]1054		#[transactional]1055		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {1056			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1057			let collection = Self::get_collection(collection_id)?;10581059			Self::approve_internal(&sender, &spender, &collection, item_id, amount)?;10601061			collection.submit_logs()1062		}10631064		/// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.1065		///1066		/// # Permissions1067		/// * Collection Owner1068		/// * Collection Admin1069		/// * Current NFT owner1070		/// * Address approved by current NFT owner1071		///1072		/// # Arguments1073		///1074		/// * from: Address that owns token.1075		///1076		/// * recipient: Address of token recipient.1077		///1078		/// * collection_id.1079		///1080		/// * item_id: ID of the item.1081		///1082		/// * value: Amount to transfer.1083		#[weight = <SelfWeightOf<T>>::transfer_from()]1084		#[transactional]1085		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {1086			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1087			let collection = Self::get_collection(collection_id)?;10881089			Self::transfer_from_internal(&sender, &from, &recipient, &collection, item_id, value)?;10901091			collection.submit_logs()1092		}1093		// #[weight = 0]1094		//     // let no_perm_mes = "You do not have permissions to modify this collection";1095		//     // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1096		//     // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1097		//     // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);10981099		//     // // on_nft_received  call11001101		//     // Self::transfer(origin, collection_id, item_id, new_owner)?;11021103		//     Ok(())1104		// }11051106		/// Set off-chain data schema.1107		///1108		/// # Permissions1109		///1110		/// * Collection Owner1111		/// * Collection Admin1112		///1113		/// # Arguments1114		///1115		/// * collection_id.1116		///1117		/// * schema: String representing the offchain data schema.1118		#[weight = <SelfWeightOf<T>>::set_variable_meta_data(data.len() as u32)]1119		#[transactional]1120		pub fn set_variable_meta_data (1121			origin,1122			collection_id: CollectionId,1123			item_id: TokenId,1124			data: Vec<u8>1125		) -> DispatchResult {1126			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);11271128			let collection = Self::get_collection(collection_id)?;11291130			Self::set_variable_meta_data_internal(&sender, &collection, item_id, data)?;11311132			Ok(())1133		}11341135		/// Set schema standard1136		/// ImageURL1137		/// Unique1138		///1139		/// # Permissions1140		///1141		/// * Collection Owner1142		/// * Collection Admin1143		///1144		/// # Arguments1145		///1146		/// * collection_id.1147		///1148		/// * schema: SchemaVersion: enum1149		#[weight = <SelfWeightOf<T>>::set_schema_version()]1150		#[transactional]1151		pub fn set_schema_version(1152			origin,1153			collection_id: CollectionId,1154			version: SchemaVersion1155		) -> DispatchResult {1156			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1157			let mut target_collection = Self::get_collection(collection_id)?;1158			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;1159			target_collection.schema_version = version;1160			target_collection.save()1161		}11621163		/// Set off-chain data schema.1164		///1165		/// # Permissions1166		///1167		/// * Collection Owner1168		/// * Collection Admin1169		///1170		/// # Arguments1171		///1172		/// * collection_id.1173		///1174		/// * schema: String representing the offchain data schema.1175		#[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]1176		#[transactional]1177		pub fn set_offchain_schema(1178			origin,1179			collection_id: CollectionId,1180			schema: Vec<u8>1181		) -> DispatchResult {1182			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1183			let mut target_collection = Self::get_collection(collection_id)?;1184			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11851186			// check schema limit1187			ensure!(schema.len() as u32 <= OFFCHAIN_SCHEMA_LIMIT, "");11881189			target_collection.offchain_schema = schema;1190			target_collection.save()1191		}11921193		/// Set const on-chain data schema.1194		///1195		/// # Permissions1196		///1197		/// * Collection Owner1198		/// * Collection Admin1199		///1200		/// # Arguments1201		///1202		/// * collection_id.1203		///1204		/// * schema: String representing the const on-chain data schema.1205		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1206		#[transactional]1207		pub fn set_const_on_chain_schema (1208			origin,1209			collection_id: CollectionId,1210			schema: Vec<u8>1211		) -> DispatchResult {1212			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1213			let mut target_collection = Self::get_collection(collection_id)?;1214			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;12151216			// check schema limit1217			ensure!(schema.len() as u32 <= CONST_ON_CHAIN_SCHEMA_LIMIT, "");12181219			target_collection.const_on_chain_schema = schema;1220			target_collection.save()1221		}12221223		/// Set variable on-chain data schema.1224		///1225		/// # Permissions1226		///1227		/// * Collection Owner1228		/// * Collection Admin1229		///1230		/// # Arguments1231		///1232		/// * collection_id.1233		///1234		/// * schema: String representing the variable on-chain data schema.1235		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1236		#[transactional]1237		pub fn set_variable_on_chain_schema (1238			origin,1239			collection_id: CollectionId,1240			schema: Vec<u8>1241		) -> DispatchResult {1242			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1243			let mut target_collection = Self::get_collection(collection_id)?;1244			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;12451246			// check schema limit1247			ensure!(schema.len() as u32 <= VARIABLE_ON_CHAIN_SCHEMA_LIMIT, "");12481249			target_collection.variable_on_chain_schema = schema;1250			target_collection.save()1251		}12521253		#[weight = <SelfWeightOf<T>>::set_collection_limits()]1254		#[transactional]1255		pub fn set_collection_limits(1256			origin,1257			collection_id: u32,1258			new_limits: CollectionLimits<T::BlockNumber>,1259		) -> DispatchResult {1260			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1261			let mut target_collection = Self::get_collection(collection_id)?;1262			Self::check_owner_permissions(&target_collection, sender.as_sub())?;1263			let old_limits = &target_collection.limits;12641265			// collection bounds1266			ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1267				new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP &&1268				new_limits.sponsored_data_size <= CUSTOM_DATA_LIMIT,1269				Error::<T>::CollectionLimitBoundsExceeded);12701271			// token_limit   check  prev1272			ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1273			ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);12741275			ensure!(1276				(old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1277				(old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1278				Error::<T>::OwnerPermissionsCantBeReverted,1279			);12801281			target_collection.limits = new_limits;12821283			target_collection.save()1284		}1285	}1286}12871288impl<T: Config> Module<T> {1289	pub fn create_item_internal(1290		sender: &T::CrossAccountId,1291		collection: &CollectionHandle<T>,1292		owner: &T::CrossAccountId,1293		data: CreateItemData,1294	) -> DispatchResult {1295		ensure!(1296			owner != &T::CrossAccountId::from_eth(H160([0; 20])),1297			Error::<T>::AddressIsZero1298		);12991300		Self::can_create_items_in_collection(collection, sender, owner, 1)?;1301		Self::validate_create_item_args(collection, &data)?;1302		Self::create_item_no_validation(collection, owner, data)?;13031304		Ok(())1305	}13061307	pub fn transfer_internal(1308		sender: &T::CrossAccountId,1309		recipient: &T::CrossAccountId,1310		target_collection: &CollectionHandle<T>,1311		item_id: TokenId,1312		value: u128,1313	) -> DispatchResult {1314		ensure!(1315			recipient != &T::CrossAccountId::from_eth(H160([0; 20])),1316			Error::<T>::AddressIsZero1317		);13181319		// Limits check1320		Self::is_correct_transfer(target_collection, recipient)?;13211322		// Transfer permissions check1323		ensure!(1324			Self::is_item_owner(sender, target_collection, item_id)?1325				|| Self::is_owner_or_admin_permissions(target_collection, sender)?,1326			Error::<T>::NoPermission1327		);13281329		if target_collection.access == AccessMode::WhiteList {1330			Self::check_white_list(target_collection, sender)?;1331			Self::check_white_list(target_collection, recipient)?;1332		}13331334		match target_collection.mode {1335			CollectionMode::NFT => Self::transfer_nft(1336				target_collection,1337				item_id,1338				sender.clone(),1339				recipient.clone(),1340			)?,1341			CollectionMode::Fungible(_) => {1342				Self::transfer_fungible(target_collection, value, sender, recipient)?1343			}1344			CollectionMode::ReFungible => Self::transfer_refungible(1345				target_collection,1346				item_id,1347				value,1348				sender.clone(),1349				recipient.clone(),1350			)?,1351			_ => (),1352		};13531354		Self::deposit_event(RawEvent::Transfer(1355			target_collection.id,1356			item_id,1357			sender.clone(),1358			recipient.clone(),1359			value,1360		));13611362		Ok(())1363	}13641365	pub fn approve_internal(1366		sender: &T::CrossAccountId,1367		spender: &T::CrossAccountId,1368		collection: &CollectionHandle<T>,1369		item_id: TokenId,1370		amount: u128,1371	) -> DispatchResult {1372		Self::token_exists(collection, item_id)?;13731374		// Transfer permissions check1375		let bypasses_limits = collection.limits.owner_can_transfer1376			&& Self::is_owner_or_admin_permissions(collection, sender)?;13771378		let allowance_limit = if bypasses_limits {1379			None1380		} else if let Some(amount) = Self::owned_amount(sender, collection, item_id)? {1381			Some(amount)1382		} else {1383			fail!(Error::<T>::NoPermission);1384		};13851386		if collection.access == AccessMode::WhiteList {1387			Self::check_white_list(collection, sender)?;1388			Self::check_white_list(collection, spender)?;1389		}13901391		collection.consume_sload()?;1392		let allowance: u128 = amount1393			.checked_add(<Allowances<T>>::get(1394				collection.id,1395				(item_id, sender.as_sub(), spender.as_sub()),1396			))1397			.ok_or(Error::<T>::NumOverflow)?;1398		if let Some(limit) = allowance_limit {1399			ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1400		}1401		collection.consume_sstore()?;1402		<Allowances<T>>::insert(1403			collection.id,1404			(item_id, sender.as_sub(), spender.as_sub()),1405			allowance,1406		);14071408		if matches!(collection.mode, CollectionMode::NFT) {1409			// TODO: NFT: only one owner may exist for token in ERC7211410			collection.log(ERC721Events::Approval {1411				owner: *sender.as_eth(),1412				approved: *spender.as_eth(),1413				token_id: item_id.into(),1414			})?;1415		}14161417		if matches!(collection.mode, CollectionMode::Fungible(_)) {1418			// TODO: NFT: only one owner may exist for token in ERC201419			collection.log(ERC20Events::Approval {1420				owner: *sender.as_eth(),1421				spender: *spender.as_eth(),1422				value: allowance.into(),1423			})?;1424		}14251426		Self::deposit_event(RawEvent::Approved(1427			collection.id,1428			item_id,1429			sender.clone(),1430			spender.clone(),1431			allowance,1432		));1433		Ok(())1434	}14351436	pub fn transfer_from_internal(1437		sender: &T::CrossAccountId,1438		from: &T::CrossAccountId,1439		recipient: &T::CrossAccountId,1440		collection: &CollectionHandle<T>,1441		item_id: TokenId,1442		amount: u128,1443	) -> DispatchResult {1444		if sender == from {1445			// Transfer by `from`, because it is either equal to sender, or derived from him1446			return Self::transfer_internal(from, recipient, collection, item_id, amount);1447		}14481449		// Check approval1450		collection.consume_sload()?;1451		let approval: u128 =1452			<Allowances<T>>::get(collection.id, (item_id, from.as_sub(), sender.as_sub()));14531454		// Limits check1455		Self::is_correct_transfer(collection, recipient)?;14561457		// Transfer permissions check1458		ensure!(1459			approval >= amount1460				|| (collection.limits.owner_can_transfer1461					&& Self::is_owner_or_admin_permissions(collection, sender)?),1462			Error::<T>::NoPermission1463		);14641465		if collection.access == AccessMode::WhiteList {1466			Self::check_white_list(collection, sender)?;1467			Self::check_white_list(collection, recipient)?;1468		}14691470		// Reduce approval by transferred amount or remove if remaining approval drops to 01471		let allowance = approval.saturating_sub(amount);1472		collection.consume_sstore()?;1473		if allowance > 0 {1474			<Allowances<T>>::insert(1475				collection.id,1476				(item_id, from.as_sub(), sender.as_sub()),1477				allowance,1478			);1479		} else {1480			<Allowances<T>>::remove(collection.id, (item_id, from.as_sub(), sender.as_sub()));1481		}14821483		match collection.mode {1484			CollectionMode::NFT => {1485				Self::transfer_nft(collection, item_id, from.clone(), recipient.clone())?1486			}1487			CollectionMode::Fungible(_) => {1488				Self::transfer_fungible(collection, amount, from, recipient)?1489			}1490			CollectionMode::ReFungible => Self::transfer_refungible(1491				collection,1492				item_id,1493				amount,1494				from.clone(),1495				recipient.clone(),1496			)?,1497			_ => (),1498		};14991500		if matches!(collection.mode, CollectionMode::Fungible(_)) {1501			collection.log(ERC20Events::Approval {1502				owner: *from.as_eth(),1503				spender: *sender.as_eth(),1504				value: allowance.into(),1505			})?;1506		}15071508		Ok(())1509	}15101511	pub fn set_variable_meta_data_internal(1512		sender: &T::CrossAccountId,1513		collection: &CollectionHandle<T>,1514		item_id: TokenId,1515		data: Vec<u8>,1516	) -> DispatchResult {1517		Self::token_exists(collection, item_id)?;15181519		ensure!(1520			CUSTOM_DATA_LIMIT >= data.len() as u32,1521			Error::<T>::TokenVariableDataLimitExceeded1522		);15231524		// Modify permissions check1525		ensure!(1526			Self::is_item_owner(sender, collection, item_id)?1527				|| Self::is_owner_or_admin_permissions(collection, sender)?,1528			Error::<T>::NoPermission1529		);15301531		match collection.mode {1532			CollectionMode::NFT => Self::set_nft_variable_data(collection, item_id, data)?,1533			CollectionMode::ReFungible => {1534				Self::set_re_fungible_variable_data(collection, item_id, data)?1535			}1536			CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1537			_ => fail!(Error::<T>::UnexpectedCollectionType),1538		};15391540		Ok(())1541	}15421543	pub fn meta_update_check(1544		sender: &T::CrossAccountId,1545		collection: &CollectionHandle<T>,1546		item_id: TokenId,1547	) -> DispatchResult {1548		match collection.meta_update_permission {1549			MetaUpdatePermission::ItemOwner => {1550				ensure!(1551					Self::is_item_owner(sender, collection, item_id),1552					Error::<T>::NoPermission1553				);1554			}1555			MetaUpdatePermission::Admin => {1556				ensure!(1557					Self::is_owner_or_admin_permissions(collection, sender),1558					Error::<T>::NoPermission1559				);1560			}1561			MetaUpdatePermission::None => {1562				fail!(Error::<T>::MetadataUpdateDenied);1563			}1564		}15651566		Ok(())1567	}15681569	pub fn create_multiple_items_internal(1570		sender: &T::CrossAccountId,1571		collection: &CollectionHandle<T>,1572		owner: &T::CrossAccountId,1573		items_data: Vec<CreateItemData>,1574	) -> DispatchResult {1575		Self::can_create_items_in_collection(collection, sender, owner, items_data.len() as u32)?;15761577		for data in &items_data {1578			Self::validate_create_item_args(collection, data)?;1579		}1580		for data in &items_data {1581			Self::create_item_no_validation(collection, owner, data.clone())?;1582		}15831584		Ok(())1585	}15861587	pub fn burn_item_internal(1588		sender: &T::CrossAccountId,1589		collection: &CollectionHandle<T>,1590		item_id: TokenId,1591		value: u128,1592	) -> DispatchResult {1593		ensure!(1594			Self::is_item_owner(sender, collection, item_id)?1595				|| (collection.limits.owner_can_transfer1596					&& Self::is_owner_or_admin_permissions(collection, sender)?),1597			Error::<T>::NoPermission1598		);15991600		if collection.access == AccessMode::WhiteList {1601			Self::check_white_list(collection, sender)?;1602		}16031604		match collection.mode {1605			CollectionMode::NFT => Self::burn_nft_item(collection, item_id)?,1606			CollectionMode::Fungible(_) => Self::burn_fungible_item(sender, collection, value)?,1607			CollectionMode::ReFungible => Self::burn_refungible_item(collection, item_id, sender)?,1608			_ => (),1609		};16101611		Ok(())1612	}16131614	pub fn toggle_white_list_internal(1615		sender: &T::CrossAccountId,1616		collection: &CollectionHandle<T>,1617		address: &T::CrossAccountId,1618		whitelisted: bool,1619	) -> DispatchResult {1620		Self::check_owner_or_admin_permissions(collection, sender)?;16211622		if whitelisted {1623			<WhiteList<T>>::insert(collection.id, address.as_sub(), true);1624		} else {1625			<WhiteList<T>>::remove(collection.id, address.as_sub());1626		}16271628		Ok(())1629	}16301631	fn is_correct_transfer(1632		collection: &CollectionHandle<T>,1633		recipient: &T::CrossAccountId,1634	) -> DispatchResult {1635		let collection_id = collection.id;16361637		// check token limit and account token limit1638		collection.consume_sload()?;1639		let account_items: u32 =1640			<AddressTokens<T>>::get(collection_id, recipient.as_sub()).len() as u32;1641		ensure!(1642			collection.limits.account_token_ownership_limit > account_items,1643			Error::<T>::AccountTokenLimitExceeded1644		);16451646		// preliminary transfer check1647		ensure!(collection.transfers_enabled, Error::<T>::TransferNotAllowed);16481649		Ok(())1650	}16511652	fn can_create_items_in_collection(1653		collection: &CollectionHandle<T>,1654		sender: &T::CrossAccountId,1655		owner: &T::CrossAccountId,1656		amount: u32,1657	) -> DispatchResult {1658		let collection_id = collection.id;16591660		// check token limit and account token limit1661		let total_items: u32 = ItemListIndex::get(collection_id)1662			.checked_add(amount)1663			.ok_or(Error::<T>::CollectionTokenLimitExceeded)?;1664		let account_items: u32 = (<AddressTokens<T>>::get(collection_id, owner.as_sub()).len()1665			as u32)1666			.checked_add(amount)1667			.ok_or(Error::<T>::AccountTokenLimitExceeded)?;1668		ensure!(1669			collection.limits.token_limit >= total_items,1670			Error::<T>::CollectionTokenLimitExceeded1671		);1672		ensure!(1673			collection.limits.account_token_ownership_limit >= account_items,1674			Error::<T>::AccountTokenLimitExceeded1675		);16761677		if !Self::is_owner_or_admin_permissions(collection, sender)? {1678			ensure!(collection.mint_mode, Error::<T>::PublicMintingNotAllowed);1679			Self::check_white_list(collection, owner)?;1680			Self::check_white_list(collection, sender)?;1681		}16821683		Ok(())1684	}16851686	fn validate_create_item_args(1687		target_collection: &CollectionHandle<T>,1688		data: &CreateItemData,1689	) -> DispatchResult {1690		match target_collection.mode {1691			CollectionMode::NFT => {1692				if !matches!(data, CreateItemData::NFT(_)) {1693					fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1694				}1695			}1696			CollectionMode::Fungible(_) => {1697				if !matches!(data, CreateItemData::Fungible(_)) {1698					fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1699				}1700			}1701			CollectionMode::ReFungible => {1702				if let CreateItemData::ReFungible(data) = data {1703					// Check refungibility limits1704					ensure!(1705						data.pieces <= MAX_REFUNGIBLE_PIECES,1706						Error::<T>::WrongRefungiblePieces1707					);1708					ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1709				} else {1710					fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1711				}1712			}1713			_ => {1714				fail!(Error::<T>::UnexpectedCollectionType);1715			}1716		};17171718		Ok(())1719	}17201721	fn create_item_no_validation(1722		collection: &CollectionHandle<T>,1723		owner: &T::CrossAccountId,1724		data: CreateItemData,1725	) -> DispatchResult {1726		match data {1727			CreateItemData::NFT(data) => {1728				let item = NftItemType {1729					owner: owner.clone(),1730					const_data: data.const_data.into_inner(),1731					variable_data: data.variable_data.into_inner(),1732				};17331734				Self::add_nft_item(collection, item)?;1735			}1736			CreateItemData::Fungible(data) => {1737				Self::add_fungible_item(collection, owner, data.value)?;1738			}1739			CreateItemData::ReFungible(data) => {1740				let owner_list = vec![Ownership {1741					owner: owner.clone(),1742					fraction: data.pieces,1743				}];17441745				let item = ReFungibleItemType {1746					owner: owner_list,1747					const_data: data.const_data.into_inner(),1748					variable_data: data.variable_data.into_inner(),1749				};17501751				Self::add_refungible_item(collection, item)?;1752			}1753		};17541755		Ok(())1756	}17571758	fn add_fungible_item(1759		collection: &CollectionHandle<T>,1760		owner: &T::CrossAccountId,1761		value: u128,1762	) -> DispatchResult {1763		let collection_id = collection.id;17641765		// Does new owner already have an account?1766		collection.consume_sload()?;1767		let balance: u128 = <FungibleItemList<T>>::get(collection_id, owner.as_sub()).value;17681769		// Mint1770		let item = FungibleItemType {1771			value: balance.checked_add(value).ok_or(Error::<T>::NumOverflow)?,1772		};1773		collection.consume_sstore()?;1774		<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), item);17751776		// Update balance1777		collection.consume_sload()?;1778		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1779			.checked_add(value)1780			.ok_or(Error::<T>::NumOverflow)?;1781		collection.consume_sstore()?;1782		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);17831784		collection.log(ERC20Events::Transfer {1785			from: H160::default(),1786			to: *owner.as_eth(),1787			value: value.into(),1788		})?;1789		Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));1790		Ok(())1791	}17921793	fn add_refungible_item(1794		collection: &CollectionHandle<T>,1795		item: ReFungibleItemType<T::CrossAccountId>,1796	) -> DispatchResult {1797		let collection_id = collection.id;17981799		let current_index = <ItemListIndex>::get(collection_id)1800			.checked_add(1)1801			.ok_or(Error::<T>::NumOverflow)?;1802		let itemcopy = item.clone();18031804		ensure!(item.owner.len() == 1, Error::<T>::BadCreateRefungibleCall,);1805		let item_owner = item.owner.first().expect("only one owner is defined");18061807		let value = item_owner.fraction;1808		let owner = item_owner.owner.clone();18091810		Self::add_token_index(collection, current_index, &owner)?;18111812		<ItemListIndex>::insert(collection_id, current_index);1813		<ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);18141815		// Update balance1816		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1817			.checked_add(value)1818			.ok_or(Error::<T>::NumOverflow)?;1819		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);18201821		Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));1822		Ok(())1823	}18241825	fn add_nft_item(1826		collection: &CollectionHandle<T>,1827		item: NftItemType<T::CrossAccountId>,1828	) -> DispatchResult {1829		let collection_id = collection.id;18301831		let current_index = <ItemListIndex>::get(collection_id)1832			.checked_add(1)1833			.ok_or(Error::<T>::NumOverflow)?;18341835		let item_owner = item.owner.clone();1836		Self::add_token_index(collection, current_index, &item.owner)?;18371838		<ItemListIndex>::insert(collection_id, current_index);1839		<NftItemList<T>>::insert(collection_id, current_index, item);18401841		// Update balance1842		let new_balance = <Balance<T>>::get(collection_id, item_owner.as_sub())1843			.checked_add(1)1844			.ok_or(Error::<T>::NumOverflow)?;1845		<Balance<T>>::insert(collection_id, item_owner.as_sub(), new_balance);18461847		collection.log(ERC721Events::Transfer {1848			from: H160::default(),1849			to: *item_owner.as_eth(),1850			token_id: current_index.into(),1851		})?;1852		Self::deposit_event(RawEvent::ItemCreated(1853			collection_id,1854			current_index,1855			item_owner,1856		));1857		Ok(())1858	}18591860	fn burn_refungible_item(1861		collection: &CollectionHandle<T>,1862		item_id: TokenId,1863		owner: &T::CrossAccountId,1864	) -> DispatchResult {1865		let collection_id = collection.id;18661867		let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id)1868			.ok_or(Error::<T>::TokenNotFound)?;1869		let rft_balance = token1870			.owner1871			.iter()1872			.find(|&i| i.owner == *owner)1873			.ok_or(Error::<T>::TokenNotFound)?;1874		Self::remove_token_index(collection, item_id, owner)?;18751876		// update balance1877		let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.as_sub())1878			.checked_sub(rft_balance.fraction)1879			.ok_or(Error::<T>::NumOverflow)?;1880		<Balance<T>>::insert(collection_id, rft_balance.owner.as_sub(), new_balance);18811882		// Re-create owners list with sender removed1883		let index = token1884			.owner1885			.iter()1886			.position(|i| i.owner == *owner)1887			.expect("owned item is exists");1888		token.owner.remove(index);1889		let owner_count = token.owner.len();18901891		// Burn the token completely if this was the last (only) owner1892		if owner_count == 0 {1893			<ReFungibleItemList<T>>::remove(collection_id, item_id);1894			<VariableMetaDataBasket<T>>::remove(collection_id, item_id);1895		} else {1896			<ReFungibleItemList<T>>::insert(collection_id, item_id, token);1897		}18981899		Ok(())1900	}19011902	fn burn_nft_item(collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1903		let collection_id = collection.id;19041905		let item =1906			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;1907		Self::remove_token_index(collection, item_id, &item.owner)?;19081909		// update balance1910		let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())1911			.checked_sub(1)1912			.ok_or(Error::<T>::NumOverflow)?;1913		<Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);1914		<NftItemList<T>>::remove(collection_id, item_id);1915		<VariableMetaDataBasket<T>>::remove(collection_id, item_id);19161917		collection.log(ERC721Events::Transfer {1918			from: *item.owner.as_eth(),1919			to: H160::default(),1920			token_id: item_id.into(),1921		})?;1922		Self::deposit_event(RawEvent::ItemDestroyed(collection.id, item_id));1923		Ok(())1924	}19251926	fn burn_fungible_item(1927		owner: &T::CrossAccountId,1928		collection: &CollectionHandle<T>,1929		value: u128,1930	) -> DispatchResult {1931		let collection_id = collection.id;19321933		let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());1934		ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);19351936		// update balance1937		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1938			.checked_sub(value)1939			.ok_or(Error::<T>::NumOverflow)?;1940		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);19411942		if balance.value - value > 0 {1943			balance.value -= value;1944			<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);1945		} else {1946			<FungibleItemList<T>>::remove(collection_id, owner.as_sub());1947		}19481949		collection.log(ERC20Events::Transfer {1950			from: *owner.as_eth(),1951			to: H160::default(),1952			value: value.into(),1953		})?;1954		Ok(())1955	}19561957	pub fn get_collection(1958		collection_id: CollectionId,1959	) -> Result<CollectionHandle<T>, sp_runtime::DispatchError> {1960		Ok(<CollectionHandle<T>>::get(collection_id).ok_or(Error::<T>::CollectionNotFound)?)1961	}19621963	fn check_owner_permissions(1964		target_collection: &CollectionHandle<T>,1965		subject: &T::AccountId,1966	) -> DispatchResult {1967		ensure!(1968			*subject == target_collection.owner,1969			Error::<T>::NoPermission1970		);19711972		Ok(())1973	}19741975	fn is_owner_or_admin_permissions(1976		collection: &CollectionHandle<T>,1977		subject: &T::CrossAccountId,1978	) -> Result<bool, DispatchError> {1979		collection.consume_sload()?;1980		Ok(*subject.as_sub() == collection.owner1981			|| <AdminList<T>>::get(collection.id).contains(subject))1982	}19831984	fn check_owner_or_admin_permissions(1985		collection: &CollectionHandle<T>,1986		subject: &T::CrossAccountId,1987	) -> DispatchResult {1988		ensure!(1989			Self::is_owner_or_admin_permissions(collection, subject)?,1990			Error::<T>::NoPermission1991		);19921993		Ok(())1994	}19951996	fn owned_amount(1997		subject: &T::CrossAccountId,1998		collection: &CollectionHandle<T>,1999		item_id: TokenId,2000	) -> Result<Option<u128>, DispatchError> {2001		collection.consume_sload()?;2002		Ok(Self::owned_amount_unchecked(subject, collection, item_id))2003	}20042005	fn owned_amount_unchecked(2006		subject: &T::CrossAccountId,2007		target_collection: &CollectionHandle<T>,2008		item_id: TokenId,2009	) -> Option<u128> {2010		let collection_id = target_collection.id;20112012		match target_collection.mode {2013			CollectionMode::NFT => {2014				(<NftItemList<T>>::get(collection_id, item_id)?.owner == *subject).then(|| 1)2015			}2016			CollectionMode::Fungible(_) => {2017				Some(<FungibleItemList<T>>::get(collection_id, &subject.as_sub()).value)2018			}2019			CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)?2020				.owner2021				.iter()2022				.find(|i| i.owner == *subject)2023				.map(|i| i.fraction),2024			CollectionMode::Invalid => None,2025		}2026	}20272028	fn is_item_owner(2029		subject: &T::CrossAccountId,2030		target_collection: &CollectionHandle<T>,2031		item_id: TokenId,2032	) -> Result<bool, DispatchError> {2033		Ok(match target_collection.mode {2034			CollectionMode::Fungible(_) => true,2035			_ => Self::owned_amount(subject, target_collection, item_id)?.is_some(),2036		})2037	}20382039	fn check_white_list(2040		collection: &CollectionHandle<T>,2041		address: &T::CrossAccountId,2042	) -> DispatchResult {2043		collection.consume_sload()?;2044		ensure!(2045			<WhiteList<T>>::contains_key(collection.id, address.as_sub()),2046			Error::<T>::AddresNotInWhiteList,2047		);2048		Ok(())2049	}20502051	/// Check if token exists. In case of Fungible, check if there is an entry for2052	/// the owner in fungible balances double map2053	fn token_exists(target_collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {2054		let collection_id = target_collection.id;2055		let exists = match target_collection.mode {2056			CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),2057			CollectionMode::Fungible(_) => true,2058			CollectionMode::ReFungible => {2059				<ReFungibleItemList<T>>::contains_key(collection_id, item_id)2060			}2061			_ => false,2062		};20632064		ensure!(exists, Error::<T>::TokenNotFound);2065		Ok(())2066	}20672068	fn transfer_fungible(2069		collection: &CollectionHandle<T>,2070		value: u128,2071		owner: &T::CrossAccountId,2072		recipient: &T::CrossAccountId,2073	) -> DispatchResult {2074		let collection_id = collection.id;20752076		collection.consume_sload()?;2077		collection.consume_sload()?;2078		let mut recipient_balance = <FungibleItemList<T>>::get(collection_id, recipient.as_sub());2079		let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());20802081		recipient_balance.value = recipient_balance2082			.value2083			.checked_add(value)2084			.ok_or(Error::<T>::NumOverflow)?;2085		balance.value = balance2086			.value2087			.checked_sub(value)2088			.ok_or(Error::<T>::TokenValueTooLow)?;20892090		// update balanceOf2091		collection.consume_sstore()?;2092		collection.consume_sstore()?;2093		if balance.value != 0 {2094			<Balance<T>>::insert(collection_id, owner.as_sub(), balance.value);2095		} else {2096			<Balance<T>>::remove(collection_id, owner.as_sub());2097		}2098		<Balance<T>>::insert(collection_id, recipient.as_sub(), recipient_balance.value);20992100		// Reduce or remove sender2101		collection.consume_sstore()?;2102		collection.consume_sstore()?;2103		if balance.value != 0 {2104			<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);2105		} else {2106			<FungibleItemList<T>>::remove(collection_id, owner.as_sub());2107		}2108		<FungibleItemList<T>>::insert(collection_id, recipient.as_sub(), recipient_balance);21092110		collection.log(ERC20Events::Transfer {2111			from: *owner.as_eth(),2112			to: *recipient.as_eth(),2113			value: value.into(),2114		})?;2115		Self::deposit_event(RawEvent::Transfer(2116			collection.id,2117			1,2118			owner.clone(),2119			recipient.clone(),2120			value,2121		));21222123		Ok(())2124	}21252126	fn transfer_refungible(2127		collection: &CollectionHandle<T>,2128		item_id: TokenId,2129		value: u128,2130		owner: T::CrossAccountId,2131		new_owner: T::CrossAccountId,2132	) -> DispatchResult {2133		let collection_id = collection.id;2134		collection.consume_sload()?;2135		let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id)2136			.ok_or(Error::<T>::TokenNotFound)?;21372138		let item = full_item2139			.owner2140			.iter()2141			.find(|i| i.owner == owner)2142			.ok_or(Error::<T>::TokenNotFound)?;2143		let amount = item.fraction;21442145		ensure!(amount >= value, Error::<T>::TokenValueTooLow);21462147		collection.consume_sload()?;2148		// update balance2149		let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2150			.checked_sub(value)2151			.ok_or(Error::<T>::NumOverflow)?;2152		collection.consume_sstore()?;2153		<Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);21542155		collection.consume_sload()?;2156		let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2157			.checked_add(value)2158			.ok_or(Error::<T>::NumOverflow)?;2159		collection.consume_sstore()?;2160		<Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);21612162		let old_owner = item.owner.clone();2163		let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);21642165		let mut new_full_item = full_item.clone();2166		// transfer2167		if amount == value && !new_owner_has_account {2168			// change owner2169			// new owner do not have account2170			new_full_item2171				.owner2172				.iter_mut()2173				.find(|i| i.owner == owner)2174				.expect("old owner does present in refungible")2175				.owner = new_owner.clone();2176			collection.consume_sstore()?;2177			<ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);21782179			// update index collection2180			Self::move_token_index(collection, item_id, &old_owner, &new_owner)?;2181		} else {2182			new_full_item2183				.owner2184				.iter_mut()2185				.find(|i| i.owner == owner)2186				.expect("old owner does present in refungible")2187				.fraction -= value;21882189			// separate amount2190			if new_owner_has_account {2191				// new owner has account2192				new_full_item2193					.owner2194					.iter_mut()2195					.find(|i| i.owner == new_owner)2196					.expect("new owner has account")2197					.fraction += value;2198			} else {2199				// new owner do not have account2200				new_full_item.owner.push(Ownership {2201					owner: new_owner.clone(),2202					fraction: value,2203				});2204				Self::add_token_index(collection, item_id, &new_owner)?;2205			}22062207			collection.consume_sstore()?;2208			<ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2209		}22102211		Self::deposit_event(RawEvent::Transfer(2212			collection.id,2213			item_id,2214			owner,2215			new_owner,2216			amount,2217		));22182219		Ok(())2220	}22212222	fn transfer_nft(2223		collection: &CollectionHandle<T>,2224		item_id: TokenId,2225		sender: T::CrossAccountId,2226		new_owner: T::CrossAccountId,2227	) -> DispatchResult {2228		let collection_id = collection.id;2229		collection.consume_sload()?;2230		let mut item =2231			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;22322233		ensure!(sender == item.owner, Error::<T>::MustBeTokenOwner);22342235		collection.consume_sload()?;2236		// update balance2237		let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2238			.checked_sub(1)2239			.ok_or(Error::<T>::NumOverflow)?;2240		collection.consume_sstore()?;2241		<Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);22422243		collection.consume_sload()?;2244		let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2245			.checked_add(1)2246			.ok_or(Error::<T>::NumOverflow)?;2247		collection.consume_sstore()?;2248		<Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);22492250		// change owner2251		let old_owner = item.owner.clone();2252		item.owner = new_owner.clone();2253		collection.consume_sstore()?;2254		<NftItemList<T>>::insert(collection_id, item_id, item);22552256		// update index collection2257		Self::move_token_index(collection, item_id, &old_owner, &new_owner)?;22582259		collection.log(ERC721Events::Transfer {2260			from: *sender.as_eth(),2261			to: *new_owner.as_eth(),2262			token_id: item_id.into(),2263		})?;2264		Self::deposit_event(RawEvent::Transfer(2265			collection.id,2266			item_id,2267			sender,2268			new_owner,2269			1,2270		));22712272		Ok(())2273	}22742275	fn set_re_fungible_variable_data(2276		collection: &CollectionHandle<T>,2277		item_id: TokenId,2278		data: Vec<u8>,2279	) -> DispatchResult {2280		let collection_id = collection.id;2281		let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id)2282			.ok_or(Error::<T>::TokenNotFound)?;22832284		item.variable_data = data;22852286		<ReFungibleItemList<T>>::insert(collection_id, item_id, item);22872288		Ok(())2289	}22902291	fn set_nft_variable_data(2292		collection: &CollectionHandle<T>,2293		item_id: TokenId,2294		data: Vec<u8>,2295	) -> DispatchResult {2296		let collection_id = collection.id;2297		let mut item =2298			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;22992300		item.variable_data = data;23012302		<NftItemList<T>>::insert(collection_id, item_id, item);23032304		Ok(())2305	}23062307	#[allow(dead_code)]2308	fn init_collection(item: &Collection<T>) {2309		// check params2310		assert!(2311			item.decimal_points <= MAX_DECIMAL_POINTS,2312			"decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2313		);2314		assert!(2315			item.name.len() <= 64,2316			"Collection name can not be longer than 63 char"2317		);2318		assert!(2319			item.name.len() <= 256,2320			"Collection description can not be longer than 255 char"2321		);2322		assert!(2323			item.token_prefix.len() <= 16,2324			"Token prefix can not be longer than 15 char"2325		);23262327		// Generate next collection ID2328		let next_id = CreatedCollectionCount::get().checked_add(1).unwrap();23292330		CreatedCollectionCount::put(next_id);2331	}23322333	#[allow(dead_code)]2334	fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::CrossAccountId>) {2335		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();23362337		Self::add_token_index(2338			&CollectionHandle::get(collection_id).unwrap(),2339			current_index,2340			&item.owner,2341		)2342		.unwrap();23432344		<ItemListIndex>::insert(collection_id, current_index);23452346		// Update balance2347		let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2348			.checked_add(1)2349			.unwrap();2350		<Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2351	}23522353	#[allow(dead_code)]2354	fn init_fungible_token(2355		collection_id: CollectionId,2356		owner: &T::CrossAccountId,2357		item: &FungibleItemType,2358	) {2359		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();23602361		Self::add_token_index(2362			&CollectionHandle::get(collection_id).unwrap(),2363			current_index,2364			owner,2365		)2366		.unwrap();23672368		<ItemListIndex>::insert(collection_id, current_index);23692370		// Update balance2371		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2372			.checked_add(item.value)2373			.unwrap();2374		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2375	}23762377	#[allow(dead_code)]2378	fn init_refungible_token(2379		collection_id: CollectionId,2380		item: &ReFungibleItemType<T::CrossAccountId>,2381	) {2382		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();23832384		let value = item.owner.first().unwrap().fraction;2385		let owner = item.owner.first().unwrap().owner.clone();23862387		Self::add_token_index(2388			&CollectionHandle::get(collection_id).unwrap(),2389			current_index,2390			&owner,2391		)2392		.unwrap();23932394		<ItemListIndex>::insert(collection_id, current_index);23952396		// Update balance2397		let new_balance = <Balance<T>>::get(collection_id, &owner.as_sub())2398			.checked_add(value)2399			.unwrap();2400		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2401	}24022403	fn add_token_index(2404		collection: &CollectionHandle<T>,2405		item_index: TokenId,2406		owner: &T::CrossAccountId,2407	) -> DispatchResult {2408		// add to account limit2409		collection.consume_sload()?;2410		if <AccountItemCount<T>>::contains_key(owner.as_sub()) {2411			// bound Owned tokens by a single address2412			collection.consume_sload()?;2413			let count = <AccountItemCount<T>>::get(owner.as_sub());2414			ensure!(2415				count < ACCOUNT_TOKEN_OWNERSHIP_LIMIT,2416				Error::<T>::AddressOwnershipLimitExceeded2417			);24182419			collection.consume_sstore()?;2420			<AccountItemCount<T>>::insert(2421				owner.as_sub(),2422				count.checked_add(1).ok_or(Error::<T>::NumOverflow)?,2423			);2424		} else {2425			collection.consume_sstore()?;2426			<AccountItemCount<T>>::insert(owner.as_sub(), 1);2427		}24282429		collection.consume_sload()?;2430		let list_exists = <AddressTokens<T>>::contains_key(collection.id, owner.as_sub());2431		if list_exists {2432			collection.consume_sload()?;2433			let mut list = <AddressTokens<T>>::get(collection.id, owner.as_sub());2434			let item_contains = list.contains(&item_index.clone());24352436			if !item_contains {2437				list.push(item_index);2438			}24392440			collection.consume_sstore()?;2441			<AddressTokens<T>>::insert(collection.id, owner.as_sub(), list);2442		} else {2443			let itm = vec![item_index];2444			collection.consume_sstore()?;2445			<AddressTokens<T>>::insert(collection.id, owner.as_sub(), itm);2446		}24472448		Ok(())2449	}24502451	fn remove_token_index(2452		collection: &CollectionHandle<T>,2453		item_index: TokenId,2454		owner: &T::CrossAccountId,2455	) -> DispatchResult {2456		// update counter2457		collection.consume_sload()?;2458		collection.consume_sstore()?;2459		<AccountItemCount<T>>::insert(2460			owner.as_sub(),2461			<AccountItemCount<T>>::get(owner.as_sub())2462				.checked_sub(1)2463				.ok_or(Error::<T>::NumOverflow)?,2464		);24652466		collection.consume_sload()?;2467		let list_exists = <AddressTokens<T>>::contains_key(collection.id, owner.as_sub());2468		if list_exists {2469			collection.consume_sload()?;2470			let mut list = <AddressTokens<T>>::get(collection.id, owner.as_sub());2471			let item_contains = list.contains(&item_index.clone());24722473			if item_contains {2474				list.retain(|&item| item != item_index);2475				collection.consume_sstore()?;2476				<AddressTokens<T>>::insert(collection.id, owner.as_sub(), list);2477			}2478		}24792480		Ok(())2481	}24822483	fn move_token_index(2484		collection: &CollectionHandle<T>,2485		item_index: TokenId,2486		old_owner: &T::CrossAccountId,2487		new_owner: &T::CrossAccountId,2488	) -> DispatchResult {2489		Self::remove_token_index(collection, item_index, old_owner)?;2490		Self::add_token_index(collection, item_index, new_owner)?;24912492		Ok(())2493	}2494}24952496sp_api::decl_runtime_apis! {2497	pub trait NftApi {2498		/// Used for ethereum integration2499		fn eth_contract_code(account: H160) -> Option<Vec<u8>>;2500	}2501}