git.delta.rocks / unique-network / refs/commits / 0a49946aa7b2

difftreelog

fix forbid non-1 tokenid in native fungibles (#1015)

Daniel Shiposha2023-10-16parent: #0cf3df0.patch.diff
in: master
* fix: native fungibles collection

* refactor: move FungibleItemsHaveNoId to pallet-common

6 files changed

modifiedpallets/balances-adapter/src/common.rsdiffbeforeafterboth
--- a/pallets/balances-adapter/src/common.rs
+++ b/pallets/balances-adapter/src/common.rs
@@ -1,9 +1,9 @@
 use alloc::{vec, vec::Vec};
 use core::marker::PhantomData;
 
-use frame_support::{fail, weights::Weight};
+use frame_support::{ensure, fail, weights::Weight};
 use pallet_balances::{weights::SubstrateWeight as BalancesWeight, WeightInfo};
-use pallet_common::{CommonCollectionOperations, CommonWeightInfo};
+use pallet_common::{CommonCollectionOperations, CommonWeightInfo, Error as CommonError};
 use up_data_structs::TokenId;
 
 use crate::{Config, NativeFungibleHandle, Pallet};
@@ -77,7 +77,7 @@
 		_data: up_data_structs::CreateItemData,
 		_nesting_budget: &dyn up_data_structs::budget::Budget,
 	) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
-		fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+		fail!(<CommonError<T>>::UnsupportedOperation);
 	}
 
 	fn create_multiple_items(
@@ -87,7 +87,7 @@
 		_data: Vec<up_data_structs::CreateItemData>,
 		_nesting_budget: &dyn up_data_structs::budget::Budget,
 	) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
-		fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+		fail!(<CommonError<T>>::UnsupportedOperation);
 	}
 
 	fn create_multiple_items_ex(
@@ -96,7 +96,7 @@
 		_data: up_data_structs::CreateItemExData<<T>::CrossAccountId>,
 		_nesting_budget: &dyn up_data_structs::budget::Budget,
 	) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
-		fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+		fail!(<CommonError<T>>::UnsupportedOperation);
 	}
 
 	fn burn_item(
@@ -105,7 +105,7 @@
 		_token: TokenId,
 		_amount: u128,
 	) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
-		fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+		fail!(<CommonError<T>>::UnsupportedOperation);
 	}
 
 	fn set_collection_properties(
@@ -113,7 +113,7 @@
 		_sender: <T>::CrossAccountId,
 		_properties: Vec<up_data_structs::Property>,
 	) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
-		fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+		fail!(<CommonError<T>>::UnsupportedOperation);
 	}
 
 	fn delete_collection_properties(
@@ -121,7 +121,7 @@
 		_sender: &<T>::CrossAccountId,
 		_property_keys: Vec<up_data_structs::PropertyKey>,
 	) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
-		fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+		fail!(<CommonError<T>>::UnsupportedOperation);
 	}
 
 	fn set_token_properties(
@@ -131,7 +131,7 @@
 		_properties: Vec<up_data_structs::Property>,
 		_budget: &dyn up_data_structs::budget::Budget,
 	) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
-		fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+		fail!(<CommonError<T>>::UnsupportedOperation);
 	}
 
 	fn delete_token_properties(
@@ -141,7 +141,7 @@
 		_property_keys: Vec<up_data_structs::PropertyKey>,
 		_budget: &dyn up_data_structs::budget::Budget,
 	) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
-		fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+		fail!(<CommonError<T>>::UnsupportedOperation);
 	}
 
 	fn get_token_properties_raw(
@@ -161,18 +161,23 @@
 		_sender: &<T>::CrossAccountId,
 		_property_permissions: Vec<up_data_structs::PropertyKeyPermission>,
 	) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
-		fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+		fail!(<CommonError<T>>::UnsupportedOperation);
 	}
 
 	fn transfer(
 		&self,
 		sender: <T>::CrossAccountId,
 		to: <T>::CrossAccountId,
-		_token: TokenId,
+		token: TokenId,
 		amount: u128,
-		budget: &dyn up_data_structs::budget::Budget,
+		_budget: &dyn up_data_structs::budget::Budget,
 	) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
-		<Pallet<T>>::transfer(self, &sender, &to, amount, budget)
+		ensure!(
+			token == TokenId::default(),
+			<CommonError<T>>::FungibleItemsHaveNoId
+		);
+
+		<Pallet<T>>::transfer(&sender, &to, amount)
 	}
 
 	fn approve(
@@ -182,7 +187,7 @@
 		_token: TokenId,
 		_amount: u128,
 	) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
-		fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+		fail!(<CommonError<T>>::UnsupportedOperation);
 	}
 
 	fn approve_from(
@@ -193,7 +198,7 @@
 		_token: TokenId,
 		_amount: u128,
 	) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
-		fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+		fail!(<CommonError<T>>::UnsupportedOperation);
 	}
 
 	fn transfer_from(
@@ -201,11 +206,16 @@
 		sender: <T>::CrossAccountId,
 		from: <T>::CrossAccountId,
 		to: <T>::CrossAccountId,
-		_token: TokenId,
+		token: TokenId,
 		amount: u128,
 		budget: &dyn up_data_structs::budget::Budget,
 	) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
-		<Pallet<T>>::transfer_from(self, &sender, &from, &to, amount, budget)
+		ensure!(
+			token == TokenId::default(),
+			<CommonError<T>>::FungibleItemsHaveNoId
+		);
+
+		<Pallet<T>>::transfer_from(&sender, &from, &to, amount, budget)
 	}
 
 	fn burn_from(
@@ -216,7 +226,7 @@
 		_amount: u128,
 		_budget: &dyn up_data_structs::budget::Budget,
 	) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
-		fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+		fail!(<CommonError<T>>::UnsupportedOperation);
 	}
 
 	fn check_nesting(
@@ -226,7 +236,7 @@
 		_under: TokenId,
 		_budget: &dyn up_data_structs::budget::Budget,
 	) -> frame_support::sp_runtime::DispatchResult {
-		fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+		fail!(<CommonError<T>>::UnsupportedOperation);
 	}
 
 	fn nest(&self, _under: TokenId, _to_nest: (up_data_structs::CollectionId, TokenId)) {}
@@ -332,7 +342,7 @@
 		_operator: <T>::CrossAccountId,
 		_approve: bool,
 	) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
-		fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+		fail!(<CommonError<T>>::UnsupportedOperation);
 	}
 
 	fn allowance_for_all(
@@ -347,6 +357,6 @@
 		&self,
 		_token: TokenId,
 	) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
-		fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+		fail!(<CommonError<T>>::UnsupportedOperation);
 	}
 }
modifiedpallets/balances-adapter/src/erc.rsdiffbeforeafterboth
--- a/pallets/balances-adapter/src/erc.rs
+++ b/pallets/balances-adapter/src/erc.rs
@@ -58,12 +58,8 @@
 		let caller = T::CrossAccountId::from_eth(caller);
 		let to = T::CrossAccountId::from_eth(to);
 		let amount = amount.try_into().map_err(|_| "amount overflow")?;
-		let budget = self
-			.recorder()
-			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
-		<Pallet<T>>::transfer(self, &caller, &to, amount, &budget)
-			.map_err(|e| dispatch_to_evm::<T>(e.error))?;
+		<Pallet<T>>::transfer(&caller, &to, amount).map_err(|e| dispatch_to_evm::<T>(e.error))?;
 		Ok(true)
 	}
 
@@ -83,7 +79,7 @@
 			.recorder()
 			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
-		<Pallet<T>>::transfer_from(self, &caller, &from, &to, amount, &budget)
+		<Pallet<T>>::transfer_from(&caller, &from, &to, amount, &budget)
 			.map_err(|e| dispatch_to_evm::<T>(e.error))?;
 		Ok(true)
 	}
@@ -106,12 +102,8 @@
 		let caller = T::CrossAccountId::from_eth(caller);
 		let to = to.into_sub_cross_account::<T>()?;
 		let amount = amount.try_into().map_err(|_| "amount overflow")?;
-		let budget = self
-			.recorder()
-			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
-		<Pallet<T>>::transfer(self, &caller, &to, amount, &budget)
-			.map_err(|e| dispatch_to_evm::<T>(e.error))?;
+		<Pallet<T>>::transfer(&caller, &to, amount).map_err(|e| dispatch_to_evm::<T>(e.error))?;
 
 		Ok(true)
 	}
@@ -137,7 +129,7 @@
 			.recorder()
 			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
-		<Pallet<T>>::transfer_from(self, &caller, &from, &to, amount, &budget)
+		<Pallet<T>>::transfer_from(&caller, &from, &to, amount, &budget)
 			.map_err(|e| dispatch_to_evm::<T>(e.error))?;
 
 		Ok(true)
modifiedpallets/balances-adapter/src/lib.rsdiffbeforeafterboth
--- a/pallets/balances-adapter/src/lib.rs
+++ b/pallets/balances-adapter/src/lib.rs
@@ -155,20 +155,15 @@
 			Ok(Self::balance_of(from))
 		}
 
-		/// Transfers the specified amount of tokens. Will check that
-		/// the transfer is allowed for the token.
+		/// Transfers the specified amount of tokens.
 		///
-		/// - `collection`: Collection that contains the token.
 		/// - `from`: Owner of tokens to transfer.
 		/// - `to`: Recepient of transfered tokens.
 		/// - `amount`: Amount of tokens to transfer.
-		/// - `nesting_budget`: Limit for searching parents in-depth to check ownership.
 		pub fn transfer(
-			_collection: &NativeFungibleHandle<T>,
 			from: &T::CrossAccountId,
 			to: &T::CrossAccountId,
 			amount: u128,
-			_nesting_budget: &dyn Budget,
 		) -> DispatchResultWithPostInfo {
 			<PalletCommon<T>>::ensure_correct_receiver(to)?;
 
@@ -185,19 +180,18 @@
 			})
 		}
 
-		/// Transfer NFT token from one account to another.
+		/// Transfer tokens from one account to another.
 		///
-		/// Same as the [`Self::transfer`] but spender doesn't needs to be the owner of the token.
-		/// The owner should set allowance for the spender to transfer token.
+		/// Same as the [`Self::transfer`] but the spender doesn't needs to be the direct owner of the token.
+		/// The spender must be allowed to transfer token.
+		/// If the tokens are nested in an NFT and the spender owns the NFT, the allowance is considered to be set.
 		///
-		/// - `collection`: Collection that contains the token.
 		/// - `spender`: Account that spend the money.
 		/// - `from`: Owner of tokens to transfer.
 		/// - `to`: Recepient of transfered tokens.
 		/// - `amount`: Amount of tokens to transfer.
 		/// - `nesting_budget`: Limit for searching parents in-depth to check ownership.
 		pub fn transfer_from(
-			collection: &NativeFungibleHandle<T>,
 			spender: &T::CrossAccountId,
 			from: &T::CrossAccountId,
 			to: &T::CrossAccountId,
@@ -208,7 +202,7 @@
 			if allowance < amount {
 				return Err(<CommonError<T>>::ApprovedValueTooLow.into());
 			}
-			Self::transfer(collection, from, to, amount, nesting_budget)
+			Self::transfer(from, to, amount)
 		}
 	}
 }
modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -783,6 +783,9 @@
 
 		/// The user is not an administrator.
 		UserIsNotCollectionAdmin,
+
+		/// Fungible tokens hold no ID, and the default value of TokenId for a fungible collection is 0.
+		FungibleItemsHaveNoId,
 	}
 
 	/// Storage of the count of created collections. Essentially contains the last collection ID.
modifiedpallets/fungible/src/common.rsdiffbeforeafterboth
--- a/pallets/fungible/src/common.rs
+++ b/pallets/fungible/src/common.rs
@@ -19,7 +19,7 @@
 use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight};
 use pallet_common::{
 	weights::WeightInfo as _, with_weight, CommonCollectionOperations, CommonWeightInfo,
-	RefungibleExtensions, SelfWeightOf as PalletCommonWeightOf,
+	Error as CommonError, RefungibleExtensions, SelfWeightOf as PalletCommonWeightOf,
 };
 use sp_runtime::{ArithmeticError, DispatchError};
 use sp_std::{vec, vec::Vec};
@@ -169,7 +169,7 @@
 	) -> DispatchResultWithPostInfo {
 		ensure!(
 			token == TokenId::default(),
-			<Error<T>>::FungibleItemsHaveNoId
+			<CommonError<T>>::FungibleItemsHaveNoId
 		);
 
 		with_weight(
@@ -188,7 +188,7 @@
 	) -> DispatchResultWithPostInfo {
 		ensure!(
 			token == TokenId::default(),
-			<Error<T>>::FungibleItemsHaveNoId
+			<CommonError<T>>::FungibleItemsHaveNoId
 		);
 
 		<Pallet<T>>::transfer(self, &from, &to, amount, nesting_budget)
@@ -203,7 +203,7 @@
 	) -> DispatchResultWithPostInfo {
 		ensure!(
 			token == TokenId::default(),
-			<Error<T>>::FungibleItemsHaveNoId
+			<CommonError<T>>::FungibleItemsHaveNoId
 		);
 
 		with_weight(
@@ -222,7 +222,7 @@
 	) -> DispatchResultWithPostInfo {
 		ensure!(
 			token == TokenId::default(),
-			<Error<T>>::FungibleItemsHaveNoId
+			<CommonError<T>>::FungibleItemsHaveNoId
 		);
 
 		with_weight(
@@ -242,7 +242,7 @@
 	) -> DispatchResultWithPostInfo {
 		ensure!(
 			token == TokenId::default(),
-			<Error<T>>::FungibleItemsHaveNoId
+			<CommonError<T>>::FungibleItemsHaveNoId
 		);
 
 		<Pallet<T>>::transfer_from(self, &sender, &from, &to, amount, nesting_budget)
@@ -258,7 +258,7 @@
 	) -> DispatchResultWithPostInfo {
 		ensure!(
 			token == TokenId::default(),
-			<Error<T>>::FungibleItemsHaveNoId
+			<CommonError<T>>::FungibleItemsHaveNoId
 		);
 
 		with_weight(
modifiedpallets/fungible/src/lib.rsdiffbeforeafterboth
after · pallets/fungible/src/lib.rs
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}