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
before · 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		/// Fungible tokens hold no ID, and the default value of TokenId for Fungible collection is 0.127		FungibleItemsHaveNoId,128		/// Tried to set data for fungible item.129		FungibleItemsDontHaveData,130		/// Fungible token does not support nesting.131		FungibleDisallowsNesting,132		/// Setting item properties is not allowed.133		SettingPropertiesNotAllowed,134		/// Setting allowance for all is not allowed.135		SettingAllowanceForAllNotAllowed,136		/// Only a fungible collection could be possibly broken; any fungible token is valid.137		FungibleTokensAreAlwaysValid,138	}139140	#[pallet::config]141	pub trait Config:142		frame_system::Config + pallet_common::Config + pallet_structure::Config + pallet_evm::Config143	{144		type WeightInfo: WeightInfo;145	}146147	#[pallet::pallet]148	pub struct Pallet<T>(_);149150	/// Total amount of fungible tokens inside a collection.151	#[pallet::storage]152	pub type TotalSupply<T: Config> =153		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u128, QueryKind = ValueQuery>;154155	/// Amount of tokens owned by an account inside a collection.156	#[pallet::storage]157	pub type Balance<T: Config> = StorageNMap<158		Key = (159			Key<Twox64Concat, CollectionId>,160			Key<Blake2_128Concat, T::CrossAccountId>,161		),162		Value = u128,163		QueryKind = ValueQuery,164	>;165166	/// Storage for assets delegated to a limited extent to other users.167	#[pallet::storage]168	pub type Allowance<T: Config> = StorageNMap<169		Key = (170			Key<Twox64Concat, CollectionId>,171			Key<Blake2_128, T::CrossAccountId>,       // Owner172			Key<Blake2_128Concat, T::CrossAccountId>, // Spender173		),174		Value = u128,175		QueryKind = ValueQuery,176	>;177}178179/// Wrapper around untyped collection handle, asserting inner collection is of fungible type.180/// Required for interaction with Fungible collections, type safety and implementation [`solidity_interface`][`evm_coder::solidity_interface`].181pub struct FungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);182183/// Implementation of methods required for dispatching during runtime.184impl<T: Config> FungibleHandle<T> {185	/// Casts [`CollectionHandle`][`pallet_common::CollectionHandle`] into [`FungibleHandle`].186	pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {187		Self(inner)188	}189190	/// Casts [`FungibleHandle`] into [`CollectionHandle`][`pallet_common::CollectionHandle`].191	pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {192		self.0193	}194	/// Returns a mutable reference to the internal [`CollectionHandle`][`pallet_common::CollectionHandle`].195	pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {196		&mut self.0197	}198}199impl<T: Config> WithRecorder<T> for FungibleHandle<T> {200	fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {201		self.0.recorder()202	}203	fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {204		self.0.into_recorder()205	}206}207impl<T: Config> Deref for FungibleHandle<T> {208	type Target = pallet_common::CollectionHandle<T>;209210	fn deref(&self) -> &Self::Target {211		&self.0212	}213}214215/// Pallet implementation for fungible assets216impl<T: Config> Pallet<T> {217	/// Initializes the collection. Returns [CollectionId] on success, [DispatchError] otherwise.218	pub fn init_collection(219		owner: T::CrossAccountId,220		payer: T::CrossAccountId,221		data: CreateCollectionData<T::CrossAccountId>,222	) -> Result<CollectionId, DispatchError> {223		<PalletCommon<T>>::init_collection(owner, payer, data)224	}225226	/// Initializes the collection with ForeignCollection flag. Returns [CollectionId] on success, [DispatchError] otherwise.227	pub fn init_foreign_collection(228		owner: T::CrossAccountId,229		payer: T::CrossAccountId,230		data: CreateCollectionData<T::CrossAccountId>,231	) -> Result<CollectionId, DispatchError> {232		<PalletCommon<T>>::init_foreign_collection(owner, payer, data)233	}234235	/// Destroys a collection.236	pub fn destroy_collection(237		collection: FungibleHandle<T>,238		sender: &T::CrossAccountId,239	) -> DispatchResult {240		let id = collection.id;241242		if Self::collection_has_tokens(id) {243			return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());244		}245246		// =========247248		PalletCommon::destroy_collection(collection.0, sender)?;249250		<TotalSupply<T>>::remove(id);251		let _ = <Balance<T>>::clear_prefix((id,), u32::MAX, None);252		let _ = <Allowance<T>>::clear_prefix((id,), u32::MAX, None);253		Ok(())254	}255256	/// Add properties to the collection.257	pub fn set_collection_properties(258		collection: &FungibleHandle<T>,259		sender: &T::CrossAccountId,260		properties: Vec<Property>,261	) -> DispatchResult {262		<PalletCommon<T>>::set_collection_properties(collection, sender, properties.into_iter())263	}264265	/// Delete properties of the collection, associated with the provided keys.266	pub fn delete_collection_properties(267		collection: &FungibleHandle<T>,268		sender: &T::CrossAccountId,269		property_keys: Vec<PropertyKey>,270	) -> DispatchResult {271		<PalletCommon<T>>::delete_collection_properties(272			collection,273			sender,274			property_keys.into_iter(),275		)276	}277278	/// Checks if collection has tokens. Return `true` if it has.279	fn collection_has_tokens(collection_id: CollectionId) -> bool {280		<TotalSupply<T>>::get(collection_id) != 0281	}282283	/// Burns the specified amount of the token. If the token balance284	/// or total supply is less than the given value,285	/// it will return [DispatchError].286	pub fn burn(287		collection: &FungibleHandle<T>,288		owner: &T::CrossAccountId,289		amount: u128,290	) -> DispatchResult {291		let total_supply = <TotalSupply<T>>::get(collection.id)292			.checked_sub(amount)293			.ok_or(<CommonError<T>>::TokenValueTooLow)?;294295		let balance = <Balance<T>>::get((collection.id, owner))296			.checked_sub(amount)297			.ok_or(<CommonError<T>>::TokenValueTooLow)?;298299		// Foreign collection check300		ensure!(!collection.flags.foreign, <CommonError<T>>::NoPermission);301302		if collection.permissions.access() == AccessMode::AllowList {303			collection.check_allowlist(owner)?;304		}305306		// =========307308		if balance == 0 {309			<Balance<T>>::remove((collection.id, owner));310			<PalletStructure<T>>::unnest_if_nested(owner, collection.id, TokenId::default());311		} else {312			<Balance<T>>::insert((collection.id, owner), balance);313		}314		<TotalSupply<T>>::insert(collection.id, total_supply);315316		<PalletEvm<T>>::deposit_log(317			ERC20Events::Transfer {318				from: *owner.as_eth(),319				to: H160::default(),320				value: amount.into(),321			}322			.to_log(collection_id_to_address(collection.id)),323		);324		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(325			collection.id,326			TokenId::default(),327			owner.clone(),328			amount,329		));330		Ok(())331	}332333	/// Burns the specified amount of the token.334	pub fn burn_foreign(335		collection: &FungibleHandle<T>,336		owner: &T::CrossAccountId,337		amount: u128,338	) -> DispatchResult {339		let total_supply = <TotalSupply<T>>::get(collection.id)340			.checked_sub(amount)341			.ok_or(<CommonError<T>>::TokenValueTooLow)?;342343		let balance = <Balance<T>>::get((collection.id, owner))344			.checked_sub(amount)345			.ok_or(<CommonError<T>>::TokenValueTooLow)?;346		// =========347348		if balance == 0 {349			<Balance<T>>::remove((collection.id, owner));350			<PalletStructure<T>>::unnest_if_nested(owner, collection.id, TokenId::default());351		} else {352			<Balance<T>>::insert((collection.id, owner), balance);353		}354		<TotalSupply<T>>::insert(collection.id, total_supply);355356		<PalletEvm<T>>::deposit_log(357			ERC20Events::Transfer {358				from: *owner.as_eth(),359				to: H160::default(),360				value: amount.into(),361			}362			.to_log(collection_id_to_address(collection.id)),363		);364		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(365			collection.id,366			TokenId::default(),367			owner.clone(),368			amount,369		));370		Ok(())371	}372373	/// Transfers the specified amount of tokens. Will check that374	/// the transfer is allowed for the token.375	///376	/// - `from`: Owner of tokens to transfer.377	/// - `to`: Recepient of transfered tokens.378	/// - `amount`: Amount of tokens to transfer.379	/// - `collection`: Collection that contains the token380	pub fn transfer(381		collection: &FungibleHandle<T>,382		from: &T::CrossAccountId,383		to: &T::CrossAccountId,384		amount: u128,385		nesting_budget: &dyn Budget,386	) -> DispatchResultWithPostInfo {387		let depositor = from;388		Self::transfer_internal(collection, depositor, from, to, amount, nesting_budget)389	}390391	/// Transfers tokens from the `from` account to the `to` account.392	/// The `depositor` is the account who deposits the tokens.393	/// For instance, the nesting rules will be checked against the `depositor`'s permissions.394	fn transfer_internal(395		collection: &FungibleHandle<T>,396		depositor: &T::CrossAccountId,397		from: &T::CrossAccountId,398		to: &T::CrossAccountId,399		amount: u128,400		nesting_budget: &dyn Budget,401	) -> DispatchResultWithPostInfo {402		ensure!(403			collection.limits.transfers_enabled(),404			<CommonError<T>>::TransferNotAllowed,405		);406407		let mut actual_weight = <SelfWeightOf<T>>::transfer_raw();408409		if collection.permissions.access() == AccessMode::AllowList {410			collection.check_allowlist(from)?;411			collection.check_allowlist(to)?;412			actual_weight += <PalletCommonWeightOf<T>>::check_accesslist() * 2;413		}414		<PalletCommon<T>>::ensure_correct_receiver(to)?;415		let balance_from = <Balance<T>>::get((collection.id, from))416			.checked_sub(amount)417			.ok_or(<CommonError<T>>::TokenValueTooLow)?;418		let balance_to = if from != to && amount != 0 {419			Some(420				<Balance<T>>::get((collection.id, to))421					.checked_add(amount)422					.ok_or(ArithmeticError::Overflow)?,423			)424		} else {425			None426		};427428		// =========429430		if let Some(balance_to) = balance_to {431			// from != to && amount != 0432433			<PalletStructure<T>>::nest_if_sent_to_token(434				depositor,435				to,436				collection.id,437				TokenId::default(),438				nesting_budget,439			)?;440441			if balance_from == 0 {442				<Balance<T>>::remove((collection.id, from));443				<PalletStructure<T>>::unnest_if_nested(from, collection.id, TokenId::default());444			} else {445				<Balance<T>>::insert((collection.id, from), balance_from);446			}447			<Balance<T>>::insert((collection.id, to), balance_to);448		}449450		<PalletEvm<T>>::deposit_log(451			ERC20Events::Transfer {452				from: *from.as_eth(),453				to: *to.as_eth(),454				value: amount.into(),455			}456			.to_log(collection_id_to_address(collection.id)),457		);458		<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(459			collection.id,460			TokenId::default(),461			from.clone(),462			to.clone(),463			amount,464		));465466		Ok(PostDispatchInfo {467			actual_weight: Some(actual_weight),468			pays_fee: Pays::Yes,469		})470	}471472	/// Minting tokens for multiple IDs.473	/// It is a utility function used in [`create_multiple_items`][`Pallet::create_multiple_items`]474	/// and [`create_multiple_items_foreign`][`Pallet::create_multiple_items_foreign`]475	pub fn create_multiple_items_common(476		collection: &FungibleHandle<T>,477		sender: &T::CrossAccountId,478		data: BTreeMap<T::CrossAccountId, u128>,479		nesting_budget: &dyn Budget,480	) -> DispatchResult {481		let total_supply = data482			.values()483			.copied()484			.try_fold(<TotalSupply<T>>::get(collection.id), |acc, v| {485				acc.checked_add(v)486			})487			.ok_or(ArithmeticError::Overflow)?;488489		for (to, _) in data.iter() {490			<PalletStructure<T>>::check_nesting(491				sender,492				to,493				collection.id,494				TokenId::default(),495				nesting_budget,496			)?;497		}498499		let updated_balances = data500			.into_iter()501			.map(|(user, amount)| {502				let updated_balance = <Balance<T>>::get((collection.id, &user))503					.checked_add(amount)504					.ok_or(ArithmeticError::Overflow)?;505				Ok((user, amount, updated_balance))506			})507			.collect::<Result<Vec<_>, DispatchError>>()?;508509		// =========510511		<TotalSupply<T>>::insert(collection.id, total_supply);512		for (user, amount, updated_balance) in updated_balances {513			<Balance<T>>::insert((collection.id, &user), updated_balance);514			<PalletStructure<T>>::nest_if_sent_to_token_unchecked(515				&user,516				collection.id,517				TokenId::default(),518			);519			<PalletEvm<T>>::deposit_log(520				ERC20Events::Transfer {521					from: H160::default(),522					to: *user.as_eth(),523					value: amount.into(),524				}525				.to_log(collection_id_to_address(collection.id)),526			);527			<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(528				collection.id,529				TokenId::default(),530				user.clone(),531				amount,532			));533		}534535		Ok(())536	}537538	/// Minting tokens for multiple IDs.539	/// See [`create_item`][`Pallet::create_item`] for more details.540	pub fn create_multiple_items(541		collection: &FungibleHandle<T>,542		sender: &T::CrossAccountId,543		data: BTreeMap<T::CrossAccountId, u128>,544		nesting_budget: &dyn Budget,545	) -> DispatchResult {546		// Foreign collection check547		ensure!(!collection.flags.foreign, <CommonError<T>>::NoPermission);548549		if !collection.is_owner_or_admin(sender) {550			ensure!(551				collection.permissions.mint_mode(),552				<CommonError<T>>::PublicMintingNotAllowed553			);554			collection.check_allowlist(sender)?;555556			for (owner, _) in data.iter() {557				collection.check_allowlist(owner)?;558			}559		}560561		Self::create_multiple_items_common(collection, sender, data, nesting_budget)562	}563564	/// Minting tokens for multiple IDs.565	/// See [`create_item_foreign`][`Pallet::create_item_foreign`] for more details.566	pub fn create_multiple_items_foreign(567		collection: &FungibleHandle<T>,568		sender: &T::CrossAccountId,569		data: BTreeMap<T::CrossAccountId, u128>,570		nesting_budget: &dyn Budget,571	) -> DispatchResult {572		Self::create_multiple_items_common(collection, sender, data, nesting_budget)573	}574575	fn set_allowance_unchecked(576		collection: &FungibleHandle<T>,577		owner: &T::CrossAccountId,578		spender: &T::CrossAccountId,579		amount: u128,580	) {581		if amount == 0 {582			<Allowance<T>>::remove((collection.id, owner, spender));583		} else {584			<Allowance<T>>::insert((collection.id, owner, spender), amount);585		}586587		<PalletEvm<T>>::deposit_log(588			ERC20Events::Approval {589				owner: *owner.as_eth(),590				spender: *spender.as_eth(),591				value: amount.into(),592			}593			.to_log(collection_id_to_address(collection.id)),594		);595		<PalletCommon<T>>::deposit_event(CommonEvent::Approved(596			collection.id,597			TokenId(0),598			owner.clone(),599			spender.clone(),600			amount,601		));602	}603604	/// Set allowance for the spender to `transfer` or `burn` owner's tokens.605	///606	/// - `collection`: Collection that contains the token607	/// - `owner`: Owner of tokens that sets the allowance.608	/// - `spender`: Recipient of the allowance rights.609	/// - `amount`: Amount of tokens the spender is allowed to `transfer` or `burn`.610	pub fn set_allowance(611		collection: &FungibleHandle<T>,612		owner: &T::CrossAccountId,613		spender: &T::CrossAccountId,614		amount: u128,615	) -> DispatchResult {616		if collection.permissions.access() == AccessMode::AllowList {617			collection.check_allowlist(owner)?;618			collection.check_allowlist(spender)?;619		}620621		if <Balance<T>>::get((collection.id, owner)) < amount {622			ensure!(623				collection.ignores_owned_amount(owner),624				<CommonError<T>>::CantApproveMoreThanOwned625			);626		}627628		// =========629630		Self::set_allowance_unchecked(collection, owner, spender, amount);631		Ok(())632	}633634	/// Set allowance for the spender to `transfer` or `burn` owner's tokens from eth mirror.635	///636	/// - `collection`: Collection that contains the token637	/// - `sender`: Owner of tokens that sets the allowance.638	/// - `from`: Owner's eth mirror.639	/// - `to`: Recipient of the allowance rights.640	/// - `amount`: Amount of tokens the spender is allowed to `transfer` or `burn`.641	pub fn set_allowance_from(642		collection: &FungibleHandle<T>,643		sender: &T::CrossAccountId,644		from: &T::CrossAccountId,645		to: &T::CrossAccountId,646		amount: u128,647	) -> DispatchResult {648		if collection.permissions.access() == AccessMode::AllowList {649			collection.check_allowlist(sender)?;650			collection.check_allowlist(from)?;651			collection.check_allowlist(to)?;652		}653654		ensure!(655			sender.conv_eq(from),656			<CommonError<T>>::AddressIsNotEthMirror657		);658659		if <Balance<T>>::get((collection.id, from)) < amount {660			ensure!(661				collection.limits.owner_can_transfer()662					&& (collection.is_owner_or_admin(sender) || collection.is_owner_or_admin(from)),663				<CommonError<T>>::CantApproveMoreThanOwned664			);665		}666667		// =========668669		Self::set_allowance_unchecked(collection, from, to, amount);670		Ok(())671	}672673	/// Checks if a non-owner has (enough) allowance from the owner to perform operations on the tokens.674	/// Returns the expected remaining allowance - it should be set manually if the transaction proceeds.675	///676	/// - `collection`: Collection that contains the token.677	/// - `spender`: CrossAccountId who has the allowance rights.678	/// - `from`: The owner of the tokens who sets the allowance.679	/// - `amount`: Amount of tokens by which the allowance sholud be reduced.680	fn check_allowed(681		collection: &FungibleHandle<T>,682		spender: &T::CrossAccountId,683		from: &T::CrossAccountId,684		amount: u128,685		nesting_budget: &dyn Budget,686	) -> Result<Option<u128>, DispatchError> {687		if spender.conv_eq(from) {688			return Ok(None);689		}690		if collection.permissions.access() == AccessMode::AllowList {691			// `from`, `to` checked in [`transfer`]692			collection.check_allowlist(spender)?;693		}694695		if collection.ignores_token_restrictions(spender) {696			return Ok(Self::compute_allowance_decrease(697				collection, from, spender, amount,698			));699		}700701		if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {702			ensure!(703				<PalletStructure<T>>::check_indirectly_owned(704					spender.clone(),705					source.0,706					source.1,707					None,708					nesting_budget709				)?,710				<CommonError<T>>::ApprovedValueTooLow,711			);712			return Ok(None);713		}714715		let allowance = Self::compute_allowance_decrease(collection, from, spender, amount);716		ensure!(allowance.is_some(), <CommonError<T>>::ApprovedValueTooLow);717718		Ok(allowance)719	}720721	/// Returns `Some(amount)` if the `spender` have allowance to spend this amount.722	/// Otherwise, it returns `None`.723	fn compute_allowance_decrease(724		collection: &FungibleHandle<T>,725		from: &T::CrossAccountId,726		spender: &T::CrossAccountId,727		amount: u128,728	) -> Option<u128> {729		<Allowance<T>>::get((collection.id, from, spender)).checked_sub(amount)730	}731732	/// Transfer fungible tokens from one account to another.733	/// Same as the [`transfer`][`Pallet::transfer`] but spender doesn't needs to be an owner of the token pieces.734	/// The owner should set allowance for the spender to transfer pieces.735	/// See [`set_allowance`][`Pallet::set_allowance`] for more details.736	pub fn transfer_from(737		collection: &FungibleHandle<T>,738		spender: &T::CrossAccountId,739		from: &T::CrossAccountId,740		to: &T::CrossAccountId,741		amount: u128,742		nesting_budget: &dyn Budget,743	) -> DispatchResultWithPostInfo {744		let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;745746		// =========747748		let mut result =749			Self::transfer_internal(collection, spender, from, to, amount, nesting_budget);750		add_weight_to_post_info(&mut result, <SelfWeightOf<T>>::check_allowed_raw());751		result?;752753		if let Some(allowance) = allowance {754			Self::set_allowance_unchecked(collection, from, spender, allowance);755			add_weight_to_post_info(756				&mut result,757				<SelfWeightOf<T>>::set_allowance_unchecked_raw(),758			)759		}760		result761	}762763	/// Burn fungible tokens from the account.764	///765	/// Same as the [`burn`][`Pallet::burn`] but spender doesn't need to be an owner of the tokens. The `from` should766	/// set allowance for the spender to burn tokens.767	/// See [`set_allowance`][`Pallet::set_allowance`] for more details.768	pub fn burn_from(769		collection: &FungibleHandle<T>,770		spender: &T::CrossAccountId,771		from: &T::CrossAccountId,772		amount: u128,773		nesting_budget: &dyn Budget,774	) -> DispatchResult {775		let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;776777		// =========778779		Self::burn(collection, from, amount)?;780		if let Some(allowance) = allowance {781			Self::set_allowance_unchecked(collection, from, spender, allowance);782		}783		Ok(())784	}785786	/// Creates fungible token.787	///788	/// The sender should be the owner/admin of the collection or collection should be configured789	/// to allow public minting.790	///791	/// - `data`: Contains user who will become the owners of the tokens and amount792	///   of tokens he will receive.793	pub fn create_item(794		collection: &FungibleHandle<T>,795		sender: &T::CrossAccountId,796		data: CreateItemData<T>,797		nesting_budget: &dyn Budget,798	) -> DispatchResult {799		Self::create_multiple_items(800			collection,801			sender,802			[(data.0, data.1)].into_iter().collect(),803			nesting_budget,804		)805	}806807	/// Creates fungible token.808	///809	/// - `data`: Contains user who will become the owners of the tokens and amount810	///   of tokens he will receive.811	pub fn create_item_foreign(812		collection: &FungibleHandle<T>,813		sender: &T::CrossAccountId,814		data: CreateItemData<T>,815		nesting_budget: &dyn Budget,816	) -> DispatchResult {817		Self::create_multiple_items_foreign(818			collection,819			sender,820			[(data.0, data.1)].into_iter().collect(),821			nesting_budget,822		)823	}824825	/// Returns 10 tokens owners in no particular order826	///827	/// There is no direct way to get token holders in ascending order,828	/// since `iter_prefix` returns values in no particular order.829	/// Therefore, getting the 10 largest holders with a large value of holders830	/// can lead to impact memory allocation + sorting with  `n * log (n)`.831	pub fn token_owners(832		collection: CollectionId,833		_token: TokenId,834	) -> Option<Vec<T::CrossAccountId>> {835		let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection,))836			.map(|(owner, _amount)| owner)837			.take(10)838			.collect();839840		if res.is_empty() {841			None842		} else {843			Some(res)844		}845	}846}