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

difftreelog

source

pallets/fungible/src/lib.rs25.7 KiBsourcehistory
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Fungible Pallet18//!19//! The Fungible pallet provides functionality for dealing with fungible assets.20//!21//! - [`CreateItemData`]22//! - [`Config`]23//! - [`FungibleHandle`]24//! - [`Pallet`]25//! - [`TotalSupply`]26//! - [`Balance`]27//! - [`Allowance`]28//! - [`Error`]29//!30//! ## Fungible tokens31//!32//! Fungible tokens or assets are divisible and non-unique. For instance,33//! fiat currencies like the dollar are fungible: A $1 bill34//! in New York City has the same value as a $1 bill in Miami.35//! A fungible token can also be a cryptocurrency like Bitcoin: 1 BTC is worth 1 BTC,36//! no matter where it is issued. Thus, the fungibility refers to a specific currency’s37//! ability to maintain one standard value. As well, it needs to have uniform acceptance.38//! This means that a currency’s history should not be able to affect its value,39//! and this is due to the fact that each piece that is a part of the currency is equal40//! in value when compared to every other piece of that exact same currency.41//! In the world of cryptocurrencies, this is essentially a coin or a token42//! that can be replaced by another identical coin or token, and they are43//! both mutually interchangeable. A popular implementation of fungible tokens is44//! the ERC-20 token standard.45//!46//! ### ERC-2047//!48//! The [ERC-20](https://ethereum.org/en/developers/docs/standards/tokens/erc-20/) (Ethereum Request for Comments 20), proposed by Fabian Vogelsteller in November 2015,49//! is a Token Standard that implements an API for tokens within Smart Contracts.50//!51//! Example functionalities ERC-20 provides:52//!53//! * transfer tokens from one account to another54//! * get the current token balance of an account55//! * get the total supply of the token available on the network56//! * approve whether an amount of token from an account can be spent by a third-party account57//!58//! ## Overview59//!60//! The module provides functionality for asset management of fungible asset, supports ERC-20 standart, includes:61//!62//! * Asset Issuance63//! * Asset Transferal64//! * Asset Destruction65//! * Delegated Asset Transfers66//!67//! **NOTE:** The created fungible asset always has `token_id` = 0.68//! So `tokenA` and `tokenB` will have different `collection_id`.69//!70//! ### Implementations71//!72//! The Fungible pallet provides implementations for the following traits.73//!74//! - [`WithRecorder`](pallet_evm_coder_substrate::WithRecorder): Trait for EVM support75//! - [`CommonCollectionOperations`](pallet_common::CommonCollectionOperations): Functions for dealing with collections76//! - [`CommonWeightInfo`](pallet_common::CommonWeightInfo): Functions for retrieval of transaction weight77//! - [`CommonEvmHandler`](pallet_common::erc::CommonEvmHandler): Function for handling EVM runtime calls7879#![cfg_attr(not(feature = "std"), no_std)]8081use core::ops::Deref;8283use evm_coder::ToLog;84use frame_support::{dispatch::PostDispatchInfo, ensure, pallet_prelude::*};85pub use pallet::*;86use pallet_common::{87	eth::collection_id_to_address, helpers::add_weight_to_post_info,88	weights::WeightInfo as CommonWeightInfo, Error as CommonError, Event as CommonEvent,89	Pallet as PalletCommon, SelfWeightOf as PalletCommonWeightOf,90};91use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};92use pallet_evm_coder_substrate::WithRecorder;93use pallet_structure::Pallet as PalletStructure;94use sp_core::H160;95use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};96use sp_std::{collections::btree_map::BTreeMap, vec::Vec};97use up_data_structs::{98	budget::Budget, mapping::TokenAddressMapping, AccessMode, CollectionId, CreateCollectionData,99	Property, PropertyKey, TokenId,100};101use weights::WeightInfo;102103use crate::erc::ERC20Events;104#[cfg(feature = "runtime-benchmarks")]105pub mod benchmarking;106pub mod common;107pub mod erc;108pub mod weights;109110pub type CreateItemData<T> = (<T as pallet_evm::Config>::CrossAccountId, u128);111pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;112113#[frame_support::pallet]114pub mod pallet {115	use frame_support::{116		pallet_prelude::*, storage::Key, Blake2_128, Blake2_128Concat, Twox64Concat,117	};118	use up_data_structs::CollectionId;119120	use super::weights::WeightInfo;121122	#[pallet::error]123	pub enum Error<T> {124		/// Not Fungible item data used to mint in Fungible collection.125		NotFungibleDataUsedToMintFungibleCollectionToken,126		/// Tried to set data for fungible item.127		FungibleItemsDontHaveData,128		/// Fungible token does not support nesting.129		FungibleDisallowsNesting,130		/// Setting item properties is not allowed.131		SettingPropertiesNotAllowed,132		/// Setting allowance for all is not allowed.133		SettingAllowanceForAllNotAllowed,134		/// Only a fungible collection could be possibly broken; any fungible token is valid.135		FungibleTokensAreAlwaysValid,136	}137138	#[pallet::config]139	pub trait Config:140		frame_system::Config + pallet_common::Config + pallet_structure::Config + pallet_evm::Config141	{142		type WeightInfo: WeightInfo;143	}144145	#[pallet::pallet]146	pub struct Pallet<T>(_);147148	/// Total amount of fungible tokens inside a collection.149	#[pallet::storage]150	pub type TotalSupply<T: Config> =151		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u128, QueryKind = ValueQuery>;152153	/// Amount of tokens owned by an account inside a collection.154	#[pallet::storage]155	pub type Balance<T: Config> = StorageNMap<156		Key = (157			Key<Twox64Concat, CollectionId>,158			Key<Blake2_128Concat, T::CrossAccountId>,159		),160		Value = u128,161		QueryKind = ValueQuery,162	>;163164	/// Storage for assets delegated to a limited extent to other users.165	#[pallet::storage]166	pub type Allowance<T: Config> = StorageNMap<167		Key = (168			Key<Twox64Concat, CollectionId>,169			Key<Blake2_128, T::CrossAccountId>,       // Owner170			Key<Blake2_128Concat, T::CrossAccountId>, // Spender171		),172		Value = u128,173		QueryKind = ValueQuery,174	>;175}176177/// Wrapper around untyped collection handle, asserting inner collection is of fungible type.178/// Required for interaction with Fungible collections, type safety and implementation [`solidity_interface`][`evm_coder::solidity_interface`].179pub struct FungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);180181/// Implementation of methods required for dispatching during runtime.182impl<T: Config> FungibleHandle<T> {183	/// Casts [`CollectionHandle`][`pallet_common::CollectionHandle`] into [`FungibleHandle`].184	pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {185		Self(inner)186	}187188	/// Casts [`FungibleHandle`] into [`CollectionHandle`][`pallet_common::CollectionHandle`].189	pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {190		self.0191	}192	/// Returns a mutable reference to the internal [`CollectionHandle`][`pallet_common::CollectionHandle`].193	pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {194		&mut self.0195	}196}197impl<T: Config> WithRecorder<T> for FungibleHandle<T> {198	fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {199		self.0.recorder()200	}201	fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {202		self.0.into_recorder()203	}204}205impl<T: Config> Deref for FungibleHandle<T> {206	type Target = pallet_common::CollectionHandle<T>;207208	fn deref(&self) -> &Self::Target {209		&self.0210	}211}212213/// Pallet implementation for fungible assets214impl<T: Config> Pallet<T> {215	/// Initializes the collection. Returns [CollectionId] on success, [DispatchError] otherwise.216	pub fn init_collection(217		owner: T::CrossAccountId,218		payer: T::CrossAccountId,219		data: CreateCollectionData<T::CrossAccountId>,220	) -> Result<CollectionId, DispatchError> {221		<PalletCommon<T>>::init_collection(owner, payer, data)222	}223224	/// Initializes the collection with ForeignCollection flag. Returns [CollectionId] on success, [DispatchError] otherwise.225	pub fn init_foreign_collection(226		owner: T::CrossAccountId,227		payer: T::CrossAccountId,228		data: CreateCollectionData<T::CrossAccountId>,229	) -> Result<CollectionId, DispatchError> {230		<PalletCommon<T>>::init_foreign_collection(owner, payer, data)231	}232233	/// Destroys a collection.234	pub fn destroy_collection(235		collection: FungibleHandle<T>,236		sender: &T::CrossAccountId,237	) -> DispatchResult {238		let id = collection.id;239240		if Self::collection_has_tokens(id) {241			return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());242		}243244		// =========245246		PalletCommon::destroy_collection(collection.0, sender)?;247248		<TotalSupply<T>>::remove(id);249		let _ = <Balance<T>>::clear_prefix((id,), u32::MAX, None);250		let _ = <Allowance<T>>::clear_prefix((id,), u32::MAX, None);251		Ok(())252	}253254	/// Add properties to the collection.255	pub fn set_collection_properties(256		collection: &FungibleHandle<T>,257		sender: &T::CrossAccountId,258		properties: Vec<Property>,259	) -> DispatchResult {260		<PalletCommon<T>>::set_collection_properties(collection, sender, properties.into_iter())261	}262263	/// Delete properties of the collection, associated with the provided keys.264	pub fn delete_collection_properties(265		collection: &FungibleHandle<T>,266		sender: &T::CrossAccountId,267		property_keys: Vec<PropertyKey>,268	) -> DispatchResult {269		<PalletCommon<T>>::delete_collection_properties(270			collection,271			sender,272			property_keys.into_iter(),273		)274	}275276	/// Checks if collection has tokens. Return `true` if it has.277	fn collection_has_tokens(collection_id: CollectionId) -> bool {278		<TotalSupply<T>>::get(collection_id) != 0279	}280281	/// Burns the specified amount of the token. If the token balance282	/// or total supply is less than the given value,283	/// it will return [DispatchError].284	pub fn burn(285		collection: &FungibleHandle<T>,286		owner: &T::CrossAccountId,287		amount: u128,288	) -> DispatchResult {289		let total_supply = <TotalSupply<T>>::get(collection.id)290			.checked_sub(amount)291			.ok_or(<CommonError<T>>::TokenValueTooLow)?;292293		let balance = <Balance<T>>::get((collection.id, owner))294			.checked_sub(amount)295			.ok_or(<CommonError<T>>::TokenValueTooLow)?;296297		// Foreign collection check298		ensure!(!collection.flags.foreign, <CommonError<T>>::NoPermission);299300		if collection.permissions.access() == AccessMode::AllowList {301			collection.check_allowlist(owner)?;302		}303304		// =========305306		if balance == 0 {307			<Balance<T>>::remove((collection.id, owner));308			<PalletStructure<T>>::unnest_if_nested(owner, collection.id, TokenId::default());309		} else {310			<Balance<T>>::insert((collection.id, owner), balance);311		}312		<TotalSupply<T>>::insert(collection.id, total_supply);313314		<PalletEvm<T>>::deposit_log(315			ERC20Events::Transfer {316				from: *owner.as_eth(),317				to: H160::default(),318				value: amount.into(),319			}320			.to_log(collection_id_to_address(collection.id)),321		);322		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(323			collection.id,324			TokenId::default(),325			owner.clone(),326			amount,327		));328		Ok(())329	}330331	/// Burns the specified amount of the token.332	pub fn burn_foreign(333		collection: &FungibleHandle<T>,334		owner: &T::CrossAccountId,335		amount: u128,336	) -> DispatchResult {337		let total_supply = <TotalSupply<T>>::get(collection.id)338			.checked_sub(amount)339			.ok_or(<CommonError<T>>::TokenValueTooLow)?;340341		let balance = <Balance<T>>::get((collection.id, owner))342			.checked_sub(amount)343			.ok_or(<CommonError<T>>::TokenValueTooLow)?;344		// =========345346		if balance == 0 {347			<Balance<T>>::remove((collection.id, owner));348			<PalletStructure<T>>::unnest_if_nested(owner, collection.id, TokenId::default());349		} else {350			<Balance<T>>::insert((collection.id, owner), balance);351		}352		<TotalSupply<T>>::insert(collection.id, total_supply);353354		<PalletEvm<T>>::deposit_log(355			ERC20Events::Transfer {356				from: *owner.as_eth(),357				to: H160::default(),358				value: amount.into(),359			}360			.to_log(collection_id_to_address(collection.id)),361		);362		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(363			collection.id,364			TokenId::default(),365			owner.clone(),366			amount,367		));368		Ok(())369	}370371	/// Transfers the specified amount of tokens. Will check that372	/// the transfer is allowed for the token.373	///374	/// - `from`: Owner of tokens to transfer.375	/// - `to`: Recepient of transfered tokens.376	/// - `amount`: Amount of tokens to transfer.377	/// - `collection`: Collection that contains the token378	pub fn transfer(379		collection: &FungibleHandle<T>,380		from: &T::CrossAccountId,381		to: &T::CrossAccountId,382		amount: u128,383		nesting_budget: &dyn Budget,384	) -> DispatchResultWithPostInfo {385		let depositor = from;386		Self::transfer_internal(collection, depositor, from, to, amount, nesting_budget)387	}388389	/// Transfers tokens from the `from` account to the `to` account.390	/// The `depositor` is the account who deposits the tokens.391	/// For instance, the nesting rules will be checked against the `depositor`'s permissions.392	fn transfer_internal(393		collection: &FungibleHandle<T>,394		depositor: &T::CrossAccountId,395		from: &T::CrossAccountId,396		to: &T::CrossAccountId,397		amount: u128,398		nesting_budget: &dyn Budget,399	) -> DispatchResultWithPostInfo {400		ensure!(401			collection.limits.transfers_enabled(),402			<CommonError<T>>::TransferNotAllowed,403		);404405		let mut actual_weight = <SelfWeightOf<T>>::transfer_raw();406407		if collection.permissions.access() == AccessMode::AllowList {408			collection.check_allowlist(from)?;409			collection.check_allowlist(to)?;410			actual_weight += <PalletCommonWeightOf<T>>::check_accesslist() * 2;411		}412		<PalletCommon<T>>::ensure_correct_receiver(to)?;413		let balance_from = <Balance<T>>::get((collection.id, from))414			.checked_sub(amount)415			.ok_or(<CommonError<T>>::TokenValueTooLow)?;416		let balance_to = if from != to && amount != 0 {417			Some(418				<Balance<T>>::get((collection.id, to))419					.checked_add(amount)420					.ok_or(ArithmeticError::Overflow)?,421			)422		} else {423			None424		};425426		// =========427428		if let Some(balance_to) = balance_to {429			// from != to && amount != 0430431			<PalletStructure<T>>::nest_if_sent_to_token(432				depositor,433				to,434				collection.id,435				TokenId::default(),436				nesting_budget,437			)?;438439			if balance_from == 0 {440				<Balance<T>>::remove((collection.id, from));441				<PalletStructure<T>>::unnest_if_nested(from, collection.id, TokenId::default());442			} else {443				<Balance<T>>::insert((collection.id, from), balance_from);444			}445			<Balance<T>>::insert((collection.id, to), balance_to);446		}447448		<PalletEvm<T>>::deposit_log(449			ERC20Events::Transfer {450				from: *from.as_eth(),451				to: *to.as_eth(),452				value: amount.into(),453			}454			.to_log(collection_id_to_address(collection.id)),455		);456		<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(457			collection.id,458			TokenId::default(),459			from.clone(),460			to.clone(),461			amount,462		));463464		Ok(PostDispatchInfo {465			actual_weight: Some(actual_weight),466			pays_fee: Pays::Yes,467		})468	}469470	/// Minting tokens for multiple IDs.471	/// It is a utility function used in [`create_multiple_items`][`Pallet::create_multiple_items`]472	/// and [`create_multiple_items_foreign`][`Pallet::create_multiple_items_foreign`]473	pub fn create_multiple_items_common(474		collection: &FungibleHandle<T>,475		sender: &T::CrossAccountId,476		data: BTreeMap<T::CrossAccountId, u128>,477		nesting_budget: &dyn Budget,478	) -> DispatchResult {479		let total_supply = data480			.values()481			.copied()482			.try_fold(<TotalSupply<T>>::get(collection.id), |acc, v| {483				acc.checked_add(v)484			})485			.ok_or(ArithmeticError::Overflow)?;486487		for (to, _) in data.iter() {488			<PalletStructure<T>>::check_nesting(489				sender,490				to,491				collection.id,492				TokenId::default(),493				nesting_budget,494			)?;495		}496497		let updated_balances = data498			.into_iter()499			.map(|(user, amount)| {500				let updated_balance = <Balance<T>>::get((collection.id, &user))501					.checked_add(amount)502					.ok_or(ArithmeticError::Overflow)?;503				Ok((user, amount, updated_balance))504			})505			.collect::<Result<Vec<_>, DispatchError>>()?;506507		// =========508509		<TotalSupply<T>>::insert(collection.id, total_supply);510		for (user, amount, updated_balance) in updated_balances {511			<Balance<T>>::insert((collection.id, &user), updated_balance);512			<PalletStructure<T>>::nest_if_sent_to_token_unchecked(513				&user,514				collection.id,515				TokenId::default(),516			);517			<PalletEvm<T>>::deposit_log(518				ERC20Events::Transfer {519					from: H160::default(),520					to: *user.as_eth(),521					value: amount.into(),522				}523				.to_log(collection_id_to_address(collection.id)),524			);525			<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(526				collection.id,527				TokenId::default(),528				user.clone(),529				amount,530			));531		}532533		Ok(())534	}535536	/// Minting tokens for multiple IDs.537	/// See [`create_item`][`Pallet::create_item`] for more details.538	pub fn create_multiple_items(539		collection: &FungibleHandle<T>,540		sender: &T::CrossAccountId,541		data: BTreeMap<T::CrossAccountId, u128>,542		nesting_budget: &dyn Budget,543	) -> DispatchResult {544		// Foreign collection check545		ensure!(!collection.flags.foreign, <CommonError<T>>::NoPermission);546547		if !collection.is_owner_or_admin(sender) {548			ensure!(549				collection.permissions.mint_mode(),550				<CommonError<T>>::PublicMintingNotAllowed551			);552			collection.check_allowlist(sender)?;553554			for (owner, _) in data.iter() {555				collection.check_allowlist(owner)?;556			}557		}558559		Self::create_multiple_items_common(collection, sender, data, nesting_budget)560	}561562	/// Minting tokens for multiple IDs.563	/// See [`create_item_foreign`][`Pallet::create_item_foreign`] for more details.564	pub fn create_multiple_items_foreign(565		collection: &FungibleHandle<T>,566		sender: &T::CrossAccountId,567		data: BTreeMap<T::CrossAccountId, u128>,568		nesting_budget: &dyn Budget,569	) -> DispatchResult {570		Self::create_multiple_items_common(collection, sender, data, nesting_budget)571	}572573	fn set_allowance_unchecked(574		collection: &FungibleHandle<T>,575		owner: &T::CrossAccountId,576		spender: &T::CrossAccountId,577		amount: u128,578	) {579		if amount == 0 {580			<Allowance<T>>::remove((collection.id, owner, spender));581		} else {582			<Allowance<T>>::insert((collection.id, owner, spender), amount);583		}584585		<PalletEvm<T>>::deposit_log(586			ERC20Events::Approval {587				owner: *owner.as_eth(),588				spender: *spender.as_eth(),589				value: amount.into(),590			}591			.to_log(collection_id_to_address(collection.id)),592		);593		<PalletCommon<T>>::deposit_event(CommonEvent::Approved(594			collection.id,595			TokenId(0),596			owner.clone(),597			spender.clone(),598			amount,599		));600	}601602	/// Set allowance for the spender to `transfer` or `burn` owner's tokens.603	///604	/// - `collection`: Collection that contains the token605	/// - `owner`: Owner of tokens that sets the allowance.606	/// - `spender`: Recipient of the allowance rights.607	/// - `amount`: Amount of tokens the spender is allowed to `transfer` or `burn`.608	pub fn set_allowance(609		collection: &FungibleHandle<T>,610		owner: &T::CrossAccountId,611		spender: &T::CrossAccountId,612		amount: u128,613	) -> DispatchResult {614		if collection.permissions.access() == AccessMode::AllowList {615			collection.check_allowlist(owner)?;616			collection.check_allowlist(spender)?;617		}618619		if <Balance<T>>::get((collection.id, owner)) < amount {620			ensure!(621				collection.ignores_owned_amount(owner),622				<CommonError<T>>::CantApproveMoreThanOwned623			);624		}625626		// =========627628		Self::set_allowance_unchecked(collection, owner, spender, amount);629		Ok(())630	}631632	/// Set allowance for the spender to `transfer` or `burn` owner's tokens from eth mirror.633	///634	/// - `collection`: Collection that contains the token635	/// - `sender`: Owner of tokens that sets the allowance.636	/// - `from`: Owner's eth mirror.637	/// - `to`: Recipient of the allowance rights.638	/// - `amount`: Amount of tokens the spender is allowed to `transfer` or `burn`.639	pub fn set_allowance_from(640		collection: &FungibleHandle<T>,641		sender: &T::CrossAccountId,642		from: &T::CrossAccountId,643		to: &T::CrossAccountId,644		amount: u128,645	) -> DispatchResult {646		if collection.permissions.access() == AccessMode::AllowList {647			collection.check_allowlist(sender)?;648			collection.check_allowlist(from)?;649			collection.check_allowlist(to)?;650		}651652		ensure!(653			sender.conv_eq(from),654			<CommonError<T>>::AddressIsNotEthMirror655		);656657		if <Balance<T>>::get((collection.id, from)) < amount {658			ensure!(659				collection.limits.owner_can_transfer()660					&& (collection.is_owner_or_admin(sender) || collection.is_owner_or_admin(from)),661				<CommonError<T>>::CantApproveMoreThanOwned662			);663		}664665		// =========666667		Self::set_allowance_unchecked(collection, from, to, amount);668		Ok(())669	}670671	/// Checks if a non-owner has (enough) allowance from the owner to perform operations on the tokens.672	/// Returns the expected remaining allowance - it should be set manually if the transaction proceeds.673	///674	/// - `collection`: Collection that contains the token.675	/// - `spender`: CrossAccountId who has the allowance rights.676	/// - `from`: The owner of the tokens who sets the allowance.677	/// - `amount`: Amount of tokens by which the allowance sholud be reduced.678	fn check_allowed(679		collection: &FungibleHandle<T>,680		spender: &T::CrossAccountId,681		from: &T::CrossAccountId,682		amount: u128,683		nesting_budget: &dyn Budget,684	) -> Result<Option<u128>, DispatchError> {685		if spender.conv_eq(from) {686			return Ok(None);687		}688		if collection.permissions.access() == AccessMode::AllowList {689			// `from`, `to` checked in [`transfer`]690			collection.check_allowlist(spender)?;691		}692693		if collection.ignores_token_restrictions(spender) {694			return Ok(Self::compute_allowance_decrease(695				collection, from, spender, amount,696			));697		}698699		if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {700			ensure!(701				<PalletStructure<T>>::check_indirectly_owned(702					spender.clone(),703					source.0,704					source.1,705					None,706					nesting_budget707				)?,708				<CommonError<T>>::ApprovedValueTooLow,709			);710			return Ok(None);711		}712713		let allowance = Self::compute_allowance_decrease(collection, from, spender, amount);714		ensure!(allowance.is_some(), <CommonError<T>>::ApprovedValueTooLow);715716		Ok(allowance)717	}718719	/// Returns `Some(amount)` if the `spender` have allowance to spend this amount.720	/// Otherwise, it returns `None`.721	fn compute_allowance_decrease(722		collection: &FungibleHandle<T>,723		from: &T::CrossAccountId,724		spender: &T::CrossAccountId,725		amount: u128,726	) -> Option<u128> {727		<Allowance<T>>::get((collection.id, from, spender)).checked_sub(amount)728	}729730	/// Transfer fungible tokens from one account to another.731	/// Same as the [`transfer`][`Pallet::transfer`] but spender doesn't needs to be an owner of the token pieces.732	/// The owner should set allowance for the spender to transfer pieces.733	/// See [`set_allowance`][`Pallet::set_allowance`] for more details.734	pub fn transfer_from(735		collection: &FungibleHandle<T>,736		spender: &T::CrossAccountId,737		from: &T::CrossAccountId,738		to: &T::CrossAccountId,739		amount: u128,740		nesting_budget: &dyn Budget,741	) -> DispatchResultWithPostInfo {742		let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;743744		// =========745746		let mut result =747			Self::transfer_internal(collection, spender, from, to, amount, nesting_budget);748		add_weight_to_post_info(&mut result, <SelfWeightOf<T>>::check_allowed_raw());749		result?;750751		if let Some(allowance) = allowance {752			Self::set_allowance_unchecked(collection, from, spender, allowance);753			add_weight_to_post_info(754				&mut result,755				<SelfWeightOf<T>>::set_allowance_unchecked_raw(),756			)757		}758		result759	}760761	/// Burn fungible tokens from the account.762	///763	/// Same as the [`burn`][`Pallet::burn`] but spender doesn't need to be an owner of the tokens. The `from` should764	/// set allowance for the spender to burn tokens.765	/// See [`set_allowance`][`Pallet::set_allowance`] for more details.766	pub fn burn_from(767		collection: &FungibleHandle<T>,768		spender: &T::CrossAccountId,769		from: &T::CrossAccountId,770		amount: u128,771		nesting_budget: &dyn Budget,772	) -> DispatchResult {773		let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;774775		// =========776777		Self::burn(collection, from, amount)?;778		if let Some(allowance) = allowance {779			Self::set_allowance_unchecked(collection, from, spender, allowance);780		}781		Ok(())782	}783784	/// Creates fungible token.785	///786	/// The sender should be the owner/admin of the collection or collection should be configured787	/// to allow public minting.788	///789	/// - `data`: Contains user who will become the owners of the tokens and amount790	///   of tokens he will receive.791	pub fn create_item(792		collection: &FungibleHandle<T>,793		sender: &T::CrossAccountId,794		data: CreateItemData<T>,795		nesting_budget: &dyn Budget,796	) -> DispatchResult {797		Self::create_multiple_items(798			collection,799			sender,800			[(data.0, data.1)].into_iter().collect(),801			nesting_budget,802		)803	}804805	/// Creates fungible token.806	///807	/// - `data`: Contains user who will become the owners of the tokens and amount808	///   of tokens he will receive.809	pub fn create_item_foreign(810		collection: &FungibleHandle<T>,811		sender: &T::CrossAccountId,812		data: CreateItemData<T>,813		nesting_budget: &dyn Budget,814	) -> DispatchResult {815		Self::create_multiple_items_foreign(816			collection,817			sender,818			[(data.0, data.1)].into_iter().collect(),819			nesting_budget,820		)821	}822823	/// Returns 10 tokens owners in no particular order824	///825	/// There is no direct way to get token holders in ascending order,826	/// since `iter_prefix` returns values in no particular order.827	/// Therefore, getting the 10 largest holders with a large value of holders828	/// can lead to impact memory allocation + sorting with  `n * log (n)`.829	pub fn token_owners(830		collection: CollectionId,831		_token: TokenId,832	) -> Option<Vec<T::CrossAccountId>> {833		let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection,))834			.map(|(owner, _amount)| owner)835			.take(10)836			.collect();837838		if res.is_empty() {839			None840		} else {841			Some(res)842		}843	}844}