git.delta.rocks / unique-network / refs/commits / 5bde44b9e4f5

difftreelog

feat draft xcm deposit_asset

Daniel Shiposha2023-10-17parent: #c652c1e.patch.diff
in: master

8 files changed

modifiedpallets/balances-adapter/src/common.rsdiffbeforeafterboth
--- a/pallets/balances-adapter/src/common.rs
+++ b/pallets/balances-adapter/src/common.rs
@@ -1,10 +1,16 @@
 use alloc::{vec, vec::Vec};
 use core::marker::PhantomData;
 
-use frame_support::{ensure, fail, weights::Weight};
+use frame_support::{
+	ensure, fail,
+	traits::tokens::{fungible::Mutate, Fortitude, Precision},
+	weights::Weight,
+};
 use pallet_balances::{weights::SubstrateWeight as BalancesWeight, WeightInfo};
-use pallet_common::{CommonCollectionOperations, CommonWeightInfo, Error as CommonError};
-use up_data_structs::TokenId;
+use pallet_common::{
+	erc::CrossAccountId, CommonCollectionOperations, CommonWeightInfo, Error as CommonError,
+};
+use up_data_structs::{budget::Budget, TokenId};
 
 use crate::{Config, NativeFungibleHandle, Pallet};
 
@@ -332,6 +338,10 @@
 		0
 	}
 
+	fn xcm_extensions(&self) -> Option<&dyn pallet_common::XcmExtensions<T>> {
+		Some(self)
+	}
+
 	fn set_allowance_for_all(
 		&self,
 		_owner: <T>::CrossAccountId,
@@ -356,3 +366,76 @@
 		fail!(<CommonError<T>>::UnsupportedOperation);
 	}
 }
+
+impl<T: Config> pallet_common::XcmExtensions<T> for NativeFungibleHandle<T> {
+	fn is_foreign(&self) -> bool {
+		false
+	}
+
+	fn create_item_internal(
+		&self,
+		_depositor: &<T>::CrossAccountId,
+		to: <T>::CrossAccountId,
+		data: up_data_structs::CreateItemData,
+		_nesting_budget: &dyn Budget,
+	) -> Result<TokenId, sp_runtime::DispatchError> {
+		match &data {
+			up_data_structs::CreateItemData::Fungible(fungible_data) => {
+				T::Mutate::mint_into(
+					to.as_sub(),
+					fungible_data
+						.value
+						.try_into()
+						.map_err(|_| sp_runtime::ArithmeticError::Overflow)?,
+				)?;
+
+				Ok(TokenId::default())
+			}
+			_ => {
+				fail!(<CommonError<T>>::NotFungibleDataUsedToMintFungibleCollectionToken)
+			}
+		}
+	}
+
+	fn transfer_item_internal(
+		&self,
+		_depositor: &<T>::CrossAccountId,
+		from: &<T>::CrossAccountId,
+		to: &<T>::CrossAccountId,
+		token: TokenId,
+		amount: u128,
+		_nesting_budget: &dyn Budget,
+	) -> sp_runtime::DispatchResult {
+		ensure!(
+			token == TokenId::default(),
+			<CommonError<T>>::FungibleItemsHaveNoId
+		);
+
+		<Pallet<T>>::transfer(from, to, amount)
+			.map(|_| ())
+			.map_err(|post_info| post_info.error)
+	}
+
+	fn burn_item_internal(
+		&self,
+		from: T::CrossAccountId,
+		token: TokenId,
+		amount: u128,
+	) -> sp_runtime::DispatchResult {
+		ensure!(
+			token == TokenId::default(),
+			<CommonError<T>>::FungibleItemsHaveNoId
+		);
+
+		T::Mutate::burn_from(
+			from.as_sub(),
+			amount
+				.try_into()
+				.map_err(|_| sp_runtime::ArithmeticError::Overflow)?,
+			Precision::Exact,
+			Fortitude::Polite,
+		)?;
+
+		Ok(())
+	}
+}
modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -80,15 +80,16 @@
 use sp_std::vec::Vec;
 use sp_weights::Weight;
 use up_data_structs::{
-	budget::Budget, AccessMode, Collection, CollectionId, CollectionLimits, CollectionMode,
-	CollectionPermissions, CollectionProperties as CollectionPropertiesT, CollectionStats,
-	CreateCollectionData, CreateItemData, CreateItemExData, PhantomType, PropertiesError,
-	PropertiesPermissionMap, Property, PropertyKey, PropertyKeyPermission, PropertyPermission,
-	PropertyScope, PropertyValue, RpcCollection, RpcCollectionFlags, SponsoringRateLimit,
-	SponsorshipState, TokenChild, TokenData, TokenId, TokenOwnerError, TokenProperties,
-	TrySetProperty, COLLECTION_ADMINS_LIMIT, COLLECTION_NUMBER_LIMIT, CUSTOM_DATA_LIMIT,
-	FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP,
-	MAX_TOKEN_PREFIX_LENGTH, NFT_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+	budget::Budget, mapping::TokenAddressMapping, AccessMode, Collection, CollectionId,
+	CollectionLimits, CollectionMode, CollectionPermissions,
+	CollectionProperties as CollectionPropertiesT, CollectionStats, CreateCollectionData,
+	CreateItemData, CreateItemExData, PhantomType, PropertiesError, PropertiesPermissionMap,
+	Property, PropertyKey, PropertyKeyPermission, PropertyPermission, PropertyScope, PropertyValue,
+	RpcCollection, RpcCollectionFlags, SponsoringRateLimit, SponsorshipState, TokenChild,
+	TokenData, TokenId, TokenOwnerError, TokenProperties, TrySetProperty, COLLECTION_ADMINS_LIMIT,
+	COLLECTION_NUMBER_LIMIT, CUSTOM_DATA_LIMIT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+	MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, MAX_TOKEN_PREFIX_LENGTH,
+	NFT_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
 };
 use up_pov_estimate_rpc::PovInfo;
 
@@ -786,6 +787,9 @@
 
 		/// Fungible tokens hold no ID, and the default value of TokenId for a fungible collection is 0.
 		FungibleItemsHaveNoId,
+
+		/// Not Fungible item data used to mint in Fungible collection.
+		NotFungibleDataUsedToMintFungibleCollectionToken,
 	}
 
 	/// Storage of the count of created collections. Essentially contains the last collection ID.
@@ -2347,24 +2351,77 @@
 	/// Is the collection a foreign one?
 	fn is_foreign(&self) -> bool;
 
-	/// Create a collection's item.
+	/// Create a collection's item using a transaction.
+	///
+	/// This function performs additional XCM-related checks before the actual creation.
+	#[transactional]
 	fn create_item(
 		&self,
+		depositor: &T::CrossAccountId,
+		to: T::CrossAccountId,
+		data: CreateItemData,
+		nesting_budget: &dyn Budget,
+	) -> Result<TokenId, DispatchError> {
+		if T::CrossTokenAddressMapping::is_token_address(&to) {
+			return unsupported!(T);
+		}
+
+		self.create_item_internal(depositor, to, data, nesting_budget)
+	}
+
+	/// Create a collection's item.
+	fn create_item_internal(
+		&self,
+		depositor: &T::CrossAccountId,
 		to: T::CrossAccountId,
 		data: CreateItemData,
+		nesting_budget: &dyn Budget,
 	) -> Result<TokenId, DispatchError>;
 
+	/// Transfer an item from the `from` account to the `to` account using a transaction.
+	///
+	/// This function performs additional XCM-related checks before the actual transfer.
+	#[transactional]
+	fn transfer_item(
+		&self,
+		depositor: &T::CrossAccountId,
+		from: &T::CrossAccountId,
+		to: &T::CrossAccountId,
+		token: TokenId,
+		amount: u128,
+		nesting_budget: &dyn Budget,
+	) -> DispatchResult {
+		if T::CrossTokenAddressMapping::is_token_address(&to) {
+			return unsupported!(T);
+		}
+
+		self.transfer_item_internal(depositor, from, to, token, amount, nesting_budget)
+	}
+
 	/// Transfer an item from the `from` account to the `to` account.
-	fn transfer(
+	fn transfer_item_internal(
 		&self,
-		from: T::CrossAccountId,
-		to: T::CrossAccountId,
+		depositor: &T::CrossAccountId,
+		from: &T::CrossAccountId,
+		to: &T::CrossAccountId,
 		token: TokenId,
 		amount: u128,
+		nesting_budget: &dyn Budget,
 	) -> DispatchResult;
 
+	/// Burn a collection's item using a transaction.
+	#[transactional]
+	fn burn_item(&self, from: T::CrossAccountId, token: TokenId, amount: u128) -> DispatchResult {
+		self.burn_item_internal(from, token, amount)
+	}
+
 	/// Burn a collection's item.
-	fn burn(&self, from: T::CrossAccountId, token: TokenId, amount: u128) -> DispatchResult;
+	fn burn_item_internal(
+		&self,
+		from: T::CrossAccountId,
+		token: TokenId,
+		amount: u128,
+	) -> DispatchResult;
 }
 
 /// Merge [`DispatchResult`] with [`Weight`] into [`DispatchResultWithPostInfo`].
modifiedpallets/foreign-assets/src/lib.rsdiffbeforeafterboth
--- a/pallets/foreign-assets/src/lib.rs
+++ b/pallets/foreign-assets/src/lib.rs
@@ -22,13 +22,6 @@
 //!
 //! ## Overview
 //!
-//! The foreign assests pallet provides functions for:
-//!
-//! - Local and foreign assets management. The foreign assets can be updated without runtime upgrade.
-//! - Bounds between asset and target collection for cross chain transfer and inner transfers.
-//!
-//! ## Overview
-//!
 //! Under construction
 
 #![cfg_attr(not(feature = "std"), no_std)]
@@ -37,22 +30,21 @@
 use frame_support::{dispatch::DispatchResult, pallet_prelude::*, traits::EnsureOrigin, PalletId};
 use frame_system::pallet_prelude::*;
 use pallet_common::{
-	dispatch::CollectionDispatch, erc::CrossAccountId, NATIVE_FUNGIBLE_COLLECTION_ID,
+	dispatch::CollectionDispatch, erc::CrossAccountId, XcmExtensions, NATIVE_FUNGIBLE_COLLECTION_ID,
 };
 use sp_runtime::traits::AccountIdConversion;
 use sp_std::{vec, vec::Vec};
-// NOTE: MultiLocation is used in storages, we will need to do migration if upgrade the
-// MultiLocation to the XCM v3.
 use staging_xcm::{
 	opaque::latest::{prelude::XcmError, Weight},
 	v3::{prelude::*, MultiAsset, XcmContext},
 };
 use staging_xcm_executor::{
-	traits::{TransactAsset, WeightTrader},
+	traits::{ConvertLocation, Error as XcmExecutorError, TransactAsset, WeightTrader},
 	Assets,
 };
 use up_data_structs::{
-	CollectionId, CollectionMode, CollectionName, CreateCollectionData, PropertyKey, TokenId,
+	budget::ZeroBudget, CollectionId, CollectionMode, CollectionName, CreateCollectionData,
+	CreateFungibleData, CreateItemData, CreateNftData, Property, PropertyKey, TokenId,
 };
 
 pub mod weights;
@@ -87,6 +79,12 @@
 		/// The ID of the foreign assets pallet.
 		type PalletId: Get<PalletId>;
 
+		/// Self-location of this parachain.
+		type SelfLocation: Get<MultiLocation>;
+
+		/// The converter from a MultiLocation to a CrossAccountId.
+		type LocationToAccountId: ConvertLocation<Self::CrossAccountId>;
+
 		/// Weight information for the extrinsics in this module.
 		type WeightInfo: WeightInfo;
 	}
@@ -113,6 +111,12 @@
 	pub type ForeignReserveLocationToCollection<T: Config> =
 		StorageMap<_, Twox64Concat, staging_xcm::v3::MultiLocation, CollectionId, OptionQuery>;
 
+	/// The corresponding reserve location of collections.
+	#[pallet::storage]
+	#[pallet::getter(fn collection_to_foreign_reserve_location)]
+	pub type CollectionToForeignReserveLocation<T: Config> =
+		StorageMap<_, Twox64Concat, CollectionId, staging_xcm::v3::MultiLocation, OptionQuery>;
+
 	/// The correponding NFT token id of reserve NFTs
 	#[pallet::storage]
 	#[pallet::getter(fn foreign_reserve_asset_instance_to_token_id)]
@@ -180,6 +184,7 @@
 			)?;
 
 			<ForeignReserveLocationToCollection<T>>::insert(reserve_location, collection_id);
+			<CollectionToForeignReserveLocation<T>>::insert(collection_id, reserve_location);
 
 			Self::deposit_event(Event::<T>::ForeignAssetRegistered {
 				asset_id: collection_id,
@@ -210,6 +215,132 @@
 			.try_into()
 			.expect("key length < max property key length; qed")
 	}
+
+	fn native_asset_location_to_collection(
+		asset_location: &MultiLocation,
+	) -> Result<Option<CollectionId>, XcmError> {
+		let self_location = T::SelfLocation::get();
+
+		if *asset_location == Here.into() {
+			Ok(Some(NATIVE_FUNGIBLE_COLLECTION_ID))
+		} else if *asset_location == self_location {
+			Ok(Some(NATIVE_FUNGIBLE_COLLECTION_ID))
+		} else if asset_location.parents == self_location.parents {
+			match asset_location
+				.interior
+				.match_and_split(&self_location.interior)
+			{
+				Some(GeneralIndex(collection_id)) => Ok(Some(CollectionId(
+					(*collection_id)
+						.try_into()
+						.map_err(|_| XcmExecutorError::AssetIdConversionFailed)?,
+				))),
+				_ => Ok(None),
+			}
+		} else {
+			Ok(None)
+		}
+	}
+
+	fn multiasset_to_collection(asset: &MultiAsset) -> Result<CollectionId, XcmError> {
+		let AssetId::Concrete(asset_reserve_location) = asset.id else {
+			return Err(XcmExecutorError::AssetNotHandled.into());
+		};
+
+		Self::native_asset_location_to_collection(&asset_reserve_location)?
+			.or_else(|| Self::foreign_reserve_location_to_collection(asset_reserve_location))
+			.ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())
+	}
+
+	fn native_asset_instance_to_token_id(
+		asset_instance: &AssetInstance,
+	) -> Result<TokenId, XcmError> {
+		match asset_instance {
+			AssetInstance::Index(token_id) => Ok(TokenId(
+				(*token_id)
+					.try_into()
+					.map_err(|_| XcmError::AssetNotFound)?,
+			)),
+			_ => Err(XcmError::AssetNotFound),
+		}
+	}
+
+	/// Obtains the token id of the `asset_instance` in the collection.
+	///
+	/// Returns `Ok(None)` only if the `asset_instance` points to a foreign item
+	/// and it haven't been created on this blockchain yet.
+	///
+	/// If the `asset_instance` points to a native item, it cannot return `Ok(None)`.
+	fn asset_instance_to_token_id(
+		xcm_ext: &dyn XcmExtensions<T>,
+		collection_id: CollectionId,
+		asset_instance: &AssetInstance,
+	) -> Result<Option<TokenId>, XcmError> {
+		if xcm_ext.is_foreign() {
+			Ok(Self::foreign_reserve_asset_instance_to_token_id(
+				collection_id,
+				asset_instance,
+			))
+		} else {
+			Self::native_asset_instance_to_token_id(asset_instance).map(Some)
+		}
+	}
+
+	fn create_foreign_asset_instance(
+		xcm_ext: &dyn XcmExtensions<T>,
+		collection_id: CollectionId,
+		asset_instance: &AssetInstance,
+		to: T::CrossAccountId,
+	) -> XcmResult {
+		let asset_instance_encoded = asset_instance.encode();
+
+		let derivative_token_id = xcm_ext
+			.create_item(
+				&Self::pallet_account(),
+				to,
+				CreateItemData::NFT(CreateNftData {
+					properties: vec![Property {
+					key: Self::reserve_asset_instance_property_key(),
+					value: asset_instance_encoded
+						.try_into()
+						.expect("asset instance length <= 32 bytes which is less than value length limit; qed"),
+				}]
+					.try_into()
+					.expect("just one property can always be stored; qed"),
+				}),
+				&ZeroBudget,
+			)
+			.map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))?;
+
+		<ForeignReserveAssetInstanceToTokenId<T>>::insert(
+			collection_id,
+			asset_instance,
+			derivative_token_id,
+		);
+
+		Ok(())
+	}
+
+	fn deposit_asset_instance(
+		xcm_ext: &dyn XcmExtensions<T>,
+		collection_id: CollectionId,
+		to: T::CrossAccountId,
+		asset_instance: &AssetInstance,
+	) -> XcmResult {
+		if let Some(token_id) =
+			Self::asset_instance_to_token_id(xcm_ext, collection_id, asset_instance)?
+		{
+			let depositor = &Self::pallet_account();
+			let from = depositor;
+			let amount = 1;
+
+			xcm_ext
+				.transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)
+				.map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))
+		} else {
+			Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)
+		}
+	}
 }
 
 impl<T: Config> TransactAsset for Pallet<T> {
@@ -234,7 +365,31 @@
 	fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}
 
 	fn deposit_asset(what: &MultiAsset, to: &MultiLocation, context: &XcmContext) -> XcmResult {
-		Err(XcmError::Unimplemented)
+		let collection_id = Self::multiasset_to_collection(what)?;
+		let dispatch =
+			T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;
+
+		let collection = dispatch.as_dyn();
+		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;
+
+		let to = T::LocationToAccountId::convert_location(to)
+			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;
+
+		match what.fun {
+			Fungibility::Fungible(amount) => xcm_ext
+				.create_item(
+					&Self::pallet_account(),
+					to,
+					CreateItemData::Fungible(CreateFungibleData { value: amount }),
+					&ZeroBudget,
+				)
+				.map(|_| ())
+				.map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),
+
+			Fungibility::NonFungible(asset_instance) => {
+				Self::deposit_asset_instance(xcm_ext, collection_id, to, &asset_instance)
+			}
+		}
 	}
 
 	fn withdraw_asset(
@@ -263,20 +418,17 @@
 		if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {
 			Some(Here.into())
 		} else {
-			// let dispatch = T::CollectionDispatch::dispatch(collection_id).ok()?;
-			// let collection = dispatch.as_dyn();
-			// let xcm_ext = collection.xcm_extensions()?;
+			let dispatch = T::CollectionDispatch::dispatch(collection_id).ok()?;
+			let collection = dispatch.as_dyn();
+			let xcm_ext = collection.xcm_extensions()?;
 
-			// if xcm_ext.is_foreign() {
-			// 	let encoded_location =
-			// 		collection.property(&<Pallet<T>>::reserve_location_property_key())?;
-			// 	MultiLocation::decode(&mut &encoded_location[..]).ok()
-			// } else {
-			// 	T::SelfLocation::get()
-			// 		.pushed_with_interior(GeneralIndex(collection_id.0.into()))
-			// 		.ok()
-			// }
-			todo!()
+			if xcm_ext.is_foreign() {
+				<Pallet<T>>::collection_to_foreign_reserve_location(collection_id)
+			} else {
+				T::SelfLocation::get()
+					.pushed_with_interior(GeneralIndex(collection_id.0.into()))
+					.ok()
+			}
 		}
 	}
 }
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,
-	Error as CommonError, RefungibleExtensions, SelfWeightOf as PalletCommonWeightOf,
+	Error as CommonError, SelfWeightOf as PalletCommonWeightOf, XcmExtensions,
 };
 use sp_runtime::{ArithmeticError, DispatchError};
 use sp_std::{vec, vec::Vec};
@@ -114,7 +114,7 @@
 				<Pallet<T>>::create_item(self, &sender, (to, fungible_data.value), nesting_budget),
 				<CommonWeights<T>>::create_item(&data),
 			),
-			_ => fail!(<Error<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),
+			_ => fail!(<CommonError<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),
 		}
 	}
 
@@ -133,7 +133,7 @@
 						.checked_add(data.value)
 						.ok_or(ArithmeticError::Overflow)?;
 				}
-				_ => fail!(<Error<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),
+				_ => fail!(<CommonError<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),
 			}
 		}
 
@@ -152,7 +152,7 @@
 		let weight = <CommonWeights<T>>::create_multiple_items_ex(&data);
 		let data = match data {
 			up_data_structs::CreateItemExData::Fungible(f) => f,
-			_ => fail!(<Error<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),
+			_ => fail!(<CommonError<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),
 		};
 
 		with_weight(
@@ -435,6 +435,10 @@
 		<TotalSupply<T>>::try_get(self.id).ok()
 	}
 
+	fn xcm_extensions(&self) -> Option<&dyn XcmExtensions<T>> {
+		Some(self)
+	}
+
 	fn set_allowance_for_all(
 		&self,
 		_owner: T::CrossAccountId,
@@ -453,3 +457,61 @@
 		fail!(<Error<T>>::FungibleTokensAreAlwaysValid)
 	}
 }
+
+impl<T: Config> XcmExtensions<T> for FungibleHandle<T> {
+	fn is_foreign(&self) -> bool {
+		self.flags.foreign
+	}
+
+	fn create_item_internal(
+		&self,
+		depositor: &<T>::CrossAccountId,
+		to: <T>::CrossAccountId,
+		data: CreateItemData,
+		nesting_budget: &dyn Budget,
+	) -> Result<TokenId, sp_runtime::DispatchError> {
+		match &data {
+			up_data_structs::CreateItemData::Fungible(fungible_data) => {
+				<Pallet<T>>::create_multiple_items(
+					self,
+					&depositor,
+					[(to, fungible_data.value)].into_iter().collect(),
+					nesting_budget,
+				)?
+			}
+			_ => fail!(<CommonError<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),
+		}
+
+		Ok(TokenId::default())
+	}
+
+	fn transfer_item_internal(
+		&self,
+		depositor: &<T>::CrossAccountId,
+		from: &<T>::CrossAccountId,
+		to: &<T>::CrossAccountId,
+		token: TokenId,
+		amount: u128,
+		nesting_budget: &dyn Budget,
+	) -> sp_runtime::DispatchResult {
+		ensure!(
+			token == TokenId::default(),
+			<CommonError<T>>::FungibleItemsHaveNoId
+		);
+
+		<Pallet<T>>::transfer_internal(self, &depositor, &from, &to, amount, nesting_budget)
+			.map(|_| ())
+			.map_err(|post_info| post_info.error)
+	}
+
+	fn burn_item_internal(
+		&self,
+		from: <T>::CrossAccountId,
+		token: TokenId,
+		amount: u128,
+	) -> sp_runtime::DispatchResult {
+		<Self as CommonCollectionOperations<T>>::burn_item(&self, from, token, amount)
+			.map(|_| ())
+			.map_err(|post_info| post_info.error)
+	}
+}
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		/// 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	/// Destroys a collection.216	pub fn destroy_collection(217		collection: FungibleHandle<T>,218		sender: &T::CrossAccountId,219	) -> DispatchResult {220		let id = collection.id;221222		if Self::collection_has_tokens(id) {223			return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());224		}225226		// =========227228		PalletCommon::destroy_collection(collection.0, sender)?;229230		<TotalSupply<T>>::remove(id);231		let _ = <Balance<T>>::clear_prefix((id,), u32::MAX, None);232		let _ = <Allowance<T>>::clear_prefix((id,), u32::MAX, None);233		Ok(())234	}235236	/// Add properties to the collection.237	pub fn set_collection_properties(238		collection: &FungibleHandle<T>,239		sender: &T::CrossAccountId,240		properties: Vec<Property>,241	) -> DispatchResult {242		<PalletCommon<T>>::set_collection_properties(collection, sender, properties.into_iter())243	}244245	/// Delete properties of the collection, associated with the provided keys.246	pub fn delete_collection_properties(247		collection: &FungibleHandle<T>,248		sender: &T::CrossAccountId,249		property_keys: Vec<PropertyKey>,250	) -> DispatchResult {251		<PalletCommon<T>>::delete_collection_properties(252			collection,253			sender,254			property_keys.into_iter(),255		)256	}257258	/// Checks if collection has tokens. Return `true` if it has.259	fn collection_has_tokens(collection_id: CollectionId) -> bool {260		<TotalSupply<T>>::get(collection_id) != 0261	}262263	/// Burns the specified amount of the token. If the token balance264	/// or total supply is less than the given value,265	/// it will return [DispatchError].266	pub fn burn(267		collection: &FungibleHandle<T>,268		owner: &T::CrossAccountId,269		amount: u128,270	) -> DispatchResult {271		let total_supply = <TotalSupply<T>>::get(collection.id)272			.checked_sub(amount)273			.ok_or(<CommonError<T>>::TokenValueTooLow)?;274275		let balance = <Balance<T>>::get((collection.id, owner))276			.checked_sub(amount)277			.ok_or(<CommonError<T>>::TokenValueTooLow)?;278279		// Foreign collection check280		ensure!(!collection.flags.foreign, <CommonError<T>>::NoPermission);281282		if collection.permissions.access() == AccessMode::AllowList {283			collection.check_allowlist(owner)?;284		}285286		// =========287288		if balance == 0 {289			<Balance<T>>::remove((collection.id, owner));290			<PalletStructure<T>>::unnest_if_nested(owner, collection.id, TokenId::default());291		} else {292			<Balance<T>>::insert((collection.id, owner), balance);293		}294		<TotalSupply<T>>::insert(collection.id, total_supply);295296		<PalletEvm<T>>::deposit_log(297			ERC20Events::Transfer {298				from: *owner.as_eth(),299				to: H160::default(),300				value: amount.into(),301			}302			.to_log(collection_id_to_address(collection.id)),303		);304		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(305			collection.id,306			TokenId::default(),307			owner.clone(),308			amount,309		));310		Ok(())311	}312313	/// Burns the specified amount of the token.314	pub fn burn_foreign(315		collection: &FungibleHandle<T>,316		owner: &T::CrossAccountId,317		amount: u128,318	) -> DispatchResult {319		let total_supply = <TotalSupply<T>>::get(collection.id)320			.checked_sub(amount)321			.ok_or(<CommonError<T>>::TokenValueTooLow)?;322323		let balance = <Balance<T>>::get((collection.id, owner))324			.checked_sub(amount)325			.ok_or(<CommonError<T>>::TokenValueTooLow)?;326		// =========327328		if balance == 0 {329			<Balance<T>>::remove((collection.id, owner));330			<PalletStructure<T>>::unnest_if_nested(owner, collection.id, TokenId::default());331		} else {332			<Balance<T>>::insert((collection.id, owner), balance);333		}334		<TotalSupply<T>>::insert(collection.id, total_supply);335336		<PalletEvm<T>>::deposit_log(337			ERC20Events::Transfer {338				from: *owner.as_eth(),339				to: H160::default(),340				value: amount.into(),341			}342			.to_log(collection_id_to_address(collection.id)),343		);344		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(345			collection.id,346			TokenId::default(),347			owner.clone(),348			amount,349		));350		Ok(())351	}352353	/// Transfers the specified amount of tokens. Will check that354	/// the transfer is allowed for the token.355	///356	/// - `from`: Owner of tokens to transfer.357	/// - `to`: Recepient of transfered tokens.358	/// - `amount`: Amount of tokens to transfer.359	/// - `collection`: Collection that contains the token360	pub fn transfer(361		collection: &FungibleHandle<T>,362		from: &T::CrossAccountId,363		to: &T::CrossAccountId,364		amount: u128,365		nesting_budget: &dyn Budget,366	) -> DispatchResultWithPostInfo {367		let depositor = from;368		Self::transfer_internal(collection, depositor, from, to, amount, nesting_budget)369	}370371	/// Transfers tokens from the `from` account to the `to` account.372	/// The `depositor` is the account who deposits the tokens.373	/// For instance, the nesting rules will be checked against the `depositor`'s permissions.374	fn transfer_internal(375		collection: &FungibleHandle<T>,376		depositor: &T::CrossAccountId,377		from: &T::CrossAccountId,378		to: &T::CrossAccountId,379		amount: u128,380		nesting_budget: &dyn Budget,381	) -> DispatchResultWithPostInfo {382		ensure!(383			collection.limits.transfers_enabled(),384			<CommonError<T>>::TransferNotAllowed,385		);386387		let mut actual_weight = <SelfWeightOf<T>>::transfer_raw();388389		if collection.permissions.access() == AccessMode::AllowList {390			collection.check_allowlist(from)?;391			collection.check_allowlist(to)?;392			actual_weight += <PalletCommonWeightOf<T>>::check_accesslist() * 2;393		}394		<PalletCommon<T>>::ensure_correct_receiver(to)?;395		let balance_from = <Balance<T>>::get((collection.id, from))396			.checked_sub(amount)397			.ok_or(<CommonError<T>>::TokenValueTooLow)?;398		let balance_to = if from != to && amount != 0 {399			Some(400				<Balance<T>>::get((collection.id, to))401					.checked_add(amount)402					.ok_or(ArithmeticError::Overflow)?,403			)404		} else {405			None406		};407408		// =========409410		if let Some(balance_to) = balance_to {411			// from != to && amount != 0412413			<PalletStructure<T>>::nest_if_sent_to_token(414				depositor,415				to,416				collection.id,417				TokenId::default(),418				nesting_budget,419			)?;420421			if balance_from == 0 {422				<Balance<T>>::remove((collection.id, from));423				<PalletStructure<T>>::unnest_if_nested(from, collection.id, TokenId::default());424			} else {425				<Balance<T>>::insert((collection.id, from), balance_from);426			}427			<Balance<T>>::insert((collection.id, to), balance_to);428		}429430		<PalletEvm<T>>::deposit_log(431			ERC20Events::Transfer {432				from: *from.as_eth(),433				to: *to.as_eth(),434				value: amount.into(),435			}436			.to_log(collection_id_to_address(collection.id)),437		);438		<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(439			collection.id,440			TokenId::default(),441			from.clone(),442			to.clone(),443			amount,444		));445446		Ok(PostDispatchInfo {447			actual_weight: Some(actual_weight),448			pays_fee: Pays::Yes,449		})450	}451452	/// Minting tokens for multiple IDs.453	/// It is a utility function used in [`create_multiple_items`][`Pallet::create_multiple_items`]454	/// and [`create_multiple_items_foreign`][`Pallet::create_multiple_items_foreign`]455	pub fn create_multiple_items_common(456		collection: &FungibleHandle<T>,457		sender: &T::CrossAccountId,458		data: BTreeMap<T::CrossAccountId, u128>,459		nesting_budget: &dyn Budget,460	) -> DispatchResult {461		let total_supply = data462			.values()463			.copied()464			.try_fold(<TotalSupply<T>>::get(collection.id), |acc, v| {465				acc.checked_add(v)466			})467			.ok_or(ArithmeticError::Overflow)?;468469		for (to, _) in data.iter() {470			<PalletStructure<T>>::check_nesting(471				sender,472				to,473				collection.id,474				TokenId::default(),475				nesting_budget,476			)?;477		}478479		let updated_balances = data480			.into_iter()481			.map(|(user, amount)| {482				let updated_balance = <Balance<T>>::get((collection.id, &user))483					.checked_add(amount)484					.ok_or(ArithmeticError::Overflow)?;485				Ok((user, amount, updated_balance))486			})487			.collect::<Result<Vec<_>, DispatchError>>()?;488489		// =========490491		<TotalSupply<T>>::insert(collection.id, total_supply);492		for (user, amount, updated_balance) in updated_balances {493			<Balance<T>>::insert((collection.id, &user), updated_balance);494			<PalletStructure<T>>::nest_if_sent_to_token_unchecked(495				&user,496				collection.id,497				TokenId::default(),498			);499			<PalletEvm<T>>::deposit_log(500				ERC20Events::Transfer {501					from: H160::default(),502					to: *user.as_eth(),503					value: amount.into(),504				}505				.to_log(collection_id_to_address(collection.id)),506			);507			<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(508				collection.id,509				TokenId::default(),510				user.clone(),511				amount,512			));513		}514515		Ok(())516	}517518	/// Minting tokens for multiple IDs.519	/// See [`create_item`][`Pallet::create_item`] for more details.520	pub fn create_multiple_items(521		collection: &FungibleHandle<T>,522		sender: &T::CrossAccountId,523		data: BTreeMap<T::CrossAccountId, u128>,524		nesting_budget: &dyn Budget,525	) -> DispatchResult {526		// Foreign collection check527		ensure!(!collection.flags.foreign, <CommonError<T>>::NoPermission);528529		if !collection.is_owner_or_admin(sender) {530			ensure!(531				collection.permissions.mint_mode(),532				<CommonError<T>>::PublicMintingNotAllowed533			);534			collection.check_allowlist(sender)?;535536			for (owner, _) in data.iter() {537				collection.check_allowlist(owner)?;538			}539		}540541		Self::create_multiple_items_common(collection, sender, data, nesting_budget)542	}543544	/// Minting tokens for multiple IDs.545	/// See [`create_item_foreign`][`Pallet::create_item_foreign`] for more details.546	pub fn create_multiple_items_foreign(547		collection: &FungibleHandle<T>,548		sender: &T::CrossAccountId,549		data: BTreeMap<T::CrossAccountId, u128>,550		nesting_budget: &dyn Budget,551	) -> DispatchResult {552		Self::create_multiple_items_common(collection, sender, data, nesting_budget)553	}554555	fn set_allowance_unchecked(556		collection: &FungibleHandle<T>,557		owner: &T::CrossAccountId,558		spender: &T::CrossAccountId,559		amount: u128,560	) {561		if amount == 0 {562			<Allowance<T>>::remove((collection.id, owner, spender));563		} else {564			<Allowance<T>>::insert((collection.id, owner, spender), amount);565		}566567		<PalletEvm<T>>::deposit_log(568			ERC20Events::Approval {569				owner: *owner.as_eth(),570				spender: *spender.as_eth(),571				value: amount.into(),572			}573			.to_log(collection_id_to_address(collection.id)),574		);575		<PalletCommon<T>>::deposit_event(CommonEvent::Approved(576			collection.id,577			TokenId(0),578			owner.clone(),579			spender.clone(),580			amount,581		));582	}583584	/// Set allowance for the spender to `transfer` or `burn` owner's tokens.585	///586	/// - `collection`: Collection that contains the token587	/// - `owner`: Owner of tokens that sets the allowance.588	/// - `spender`: Recipient of the allowance rights.589	/// - `amount`: Amount of tokens the spender is allowed to `transfer` or `burn`.590	pub fn set_allowance(591		collection: &FungibleHandle<T>,592		owner: &T::CrossAccountId,593		spender: &T::CrossAccountId,594		amount: u128,595	) -> DispatchResult {596		if collection.permissions.access() == AccessMode::AllowList {597			collection.check_allowlist(owner)?;598			collection.check_allowlist(spender)?;599		}600601		if <Balance<T>>::get((collection.id, owner)) < amount {602			ensure!(603				collection.ignores_owned_amount(owner),604				<CommonError<T>>::CantApproveMoreThanOwned605			);606		}607608		// =========609610		Self::set_allowance_unchecked(collection, owner, spender, amount);611		Ok(())612	}613614	/// Set allowance for the spender to `transfer` or `burn` owner's tokens from eth mirror.615	///616	/// - `collection`: Collection that contains the token617	/// - `sender`: Owner of tokens that sets the allowance.618	/// - `from`: Owner's eth mirror.619	/// - `to`: Recipient of the allowance rights.620	/// - `amount`: Amount of tokens the spender is allowed to `transfer` or `burn`.621	pub fn set_allowance_from(622		collection: &FungibleHandle<T>,623		sender: &T::CrossAccountId,624		from: &T::CrossAccountId,625		to: &T::CrossAccountId,626		amount: u128,627	) -> DispatchResult {628		if collection.permissions.access() == AccessMode::AllowList {629			collection.check_allowlist(sender)?;630			collection.check_allowlist(from)?;631			collection.check_allowlist(to)?;632		}633634		ensure!(635			sender.conv_eq(from),636			<CommonError<T>>::AddressIsNotEthMirror637		);638639		if <Balance<T>>::get((collection.id, from)) < amount {640			ensure!(641				collection.limits.owner_can_transfer()642					&& (collection.is_owner_or_admin(sender) || collection.is_owner_or_admin(from)),643				<CommonError<T>>::CantApproveMoreThanOwned644			);645		}646647		// =========648649		Self::set_allowance_unchecked(collection, from, to, amount);650		Ok(())651	}652653	/// Checks if a non-owner has (enough) allowance from the owner to perform operations on the tokens.654	/// Returns the expected remaining allowance - it should be set manually if the transaction proceeds.655	///656	/// - `collection`: Collection that contains the token.657	/// - `spender`: CrossAccountId who has the allowance rights.658	/// - `from`: The owner of the tokens who sets the allowance.659	/// - `amount`: Amount of tokens by which the allowance sholud be reduced.660	fn check_allowed(661		collection: &FungibleHandle<T>,662		spender: &T::CrossAccountId,663		from: &T::CrossAccountId,664		amount: u128,665		nesting_budget: &dyn Budget,666	) -> Result<Option<u128>, DispatchError> {667		if spender.conv_eq(from) {668			return Ok(None);669		}670		if collection.permissions.access() == AccessMode::AllowList {671			// `from`, `to` checked in [`transfer`]672			collection.check_allowlist(spender)?;673		}674675		if collection.ignores_token_restrictions(spender) {676			return Ok(Self::compute_allowance_decrease(677				collection, from, spender, amount,678			));679		}680681		if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {682			ensure!(683				<PalletStructure<T>>::check_indirectly_owned(684					spender.clone(),685					source.0,686					source.1,687					None,688					nesting_budget689				)?,690				<CommonError<T>>::ApprovedValueTooLow,691			);692			return Ok(None);693		}694695		let allowance = Self::compute_allowance_decrease(collection, from, spender, amount);696		ensure!(allowance.is_some(), <CommonError<T>>::ApprovedValueTooLow);697698		Ok(allowance)699	}700701	/// Returns `Some(amount)` if the `spender` have allowance to spend this amount.702	/// Otherwise, it returns `None`.703	fn compute_allowance_decrease(704		collection: &FungibleHandle<T>,705		from: &T::CrossAccountId,706		spender: &T::CrossAccountId,707		amount: u128,708	) -> Option<u128> {709		<Allowance<T>>::get((collection.id, from, spender)).checked_sub(amount)710	}711712	/// Transfer fungible tokens from one account to another.713	/// Same as the [`transfer`][`Pallet::transfer`] but spender doesn't needs to be an owner of the token pieces.714	/// The owner should set allowance for the spender to transfer pieces.715	/// See [`set_allowance`][`Pallet::set_allowance`] for more details.716	pub fn transfer_from(717		collection: &FungibleHandle<T>,718		spender: &T::CrossAccountId,719		from: &T::CrossAccountId,720		to: &T::CrossAccountId,721		amount: u128,722		nesting_budget: &dyn Budget,723	) -> DispatchResultWithPostInfo {724		let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;725726		// =========727728		let mut result =729			Self::transfer_internal(collection, spender, from, to, amount, nesting_budget);730		add_weight_to_post_info(&mut result, <SelfWeightOf<T>>::check_allowed_raw());731		result?;732733		if let Some(allowance) = allowance {734			Self::set_allowance_unchecked(collection, from, spender, allowance);735			add_weight_to_post_info(736				&mut result,737				<SelfWeightOf<T>>::set_allowance_unchecked_raw(),738			)739		}740		result741	}742743	/// Burn fungible tokens from the account.744	///745	/// Same as the [`burn`][`Pallet::burn`] but spender doesn't need to be an owner of the tokens. The `from` should746	/// set allowance for the spender to burn tokens.747	/// See [`set_allowance`][`Pallet::set_allowance`] for more details.748	pub fn burn_from(749		collection: &FungibleHandle<T>,750		spender: &T::CrossAccountId,751		from: &T::CrossAccountId,752		amount: u128,753		nesting_budget: &dyn Budget,754	) -> DispatchResult {755		let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;756757		// =========758759		Self::burn(collection, from, amount)?;760		if let Some(allowance) = allowance {761			Self::set_allowance_unchecked(collection, from, spender, allowance);762		}763		Ok(())764	}765766	/// Creates fungible token.767	///768	/// The sender should be the owner/admin of the collection or collection should be configured769	/// to allow public minting.770	///771	/// - `data`: Contains user who will become the owners of the tokens and amount772	///   of tokens he will receive.773	pub fn create_item(774		collection: &FungibleHandle<T>,775		sender: &T::CrossAccountId,776		data: CreateItemData<T>,777		nesting_budget: &dyn Budget,778	) -> DispatchResult {779		Self::create_multiple_items(780			collection,781			sender,782			[(data.0, data.1)].into_iter().collect(),783			nesting_budget,784		)785	}786787	/// Creates fungible token.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_foreign(792		collection: &FungibleHandle<T>,793		sender: &T::CrossAccountId,794		data: CreateItemData<T>,795		nesting_budget: &dyn Budget,796	) -> DispatchResult {797		Self::create_multiple_items_foreign(798			collection,799			sender,800			[(data.0, data.1)].into_iter().collect(),801			nesting_budget,802		)803	}804805	/// Returns 10 tokens owners in no particular order806	///807	/// There is no direct way to get token holders in ascending order,808	/// since `iter_prefix` returns values in no particular order.809	/// Therefore, getting the 10 largest holders with a large value of holders810	/// can lead to impact memory allocation + sorting with  `n * log (n)`.811	pub fn token_owners(812		collection: CollectionId,813		_token: TokenId,814	) -> Option<Vec<T::CrossAccountId>> {815		let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection,))816			.map(|(owner, _amount)| owner)817			.take(10)818			.collect();819820		if res.is_empty() {821			None822		} else {823			Some(res)824		}825	}826}
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, ZeroBudget},99	mapping::TokenAddressMapping,100	AccessMode, CollectionId, CreateCollectionData, Property, PropertyKey, TokenId,101};102use weights::WeightInfo;103104use crate::erc::ERC20Events;105#[cfg(feature = "runtime-benchmarks")]106pub mod benchmarking;107pub mod common;108pub mod erc;109pub mod weights;110111pub type CreateItemData<T> = (<T as pallet_evm::Config>::CrossAccountId, u128);112pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;113114#[frame_support::pallet]115pub mod pallet {116	use frame_support::{117		pallet_prelude::*, storage::Key, Blake2_128, Blake2_128Concat, Twox64Concat,118	};119	use up_data_structs::CollectionId;120121	use super::weights::WeightInfo;122123	#[pallet::error]124	pub enum Error<T> {125		/// Tried to set data for fungible item.126		FungibleItemsDontHaveData,127		/// Fungible token does not support nesting.128		FungibleDisallowsNesting,129		/// Setting item properties is not allowed.130		SettingPropertiesNotAllowed,131		/// Setting allowance for all is not allowed.132		SettingAllowanceForAllNotAllowed,133		/// Only a fungible collection could be possibly broken; any fungible token is valid.134		FungibleTokensAreAlwaysValid,135	}136137	#[pallet::config]138	pub trait Config:139		frame_system::Config + pallet_common::Config + pallet_structure::Config + pallet_evm::Config140	{141		type WeightInfo: WeightInfo;142	}143144	#[pallet::pallet]145	pub struct Pallet<T>(_);146147	/// Total amount of fungible tokens inside a collection.148	#[pallet::storage]149	pub type TotalSupply<T: Config> =150		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u128, QueryKind = ValueQuery>;151152	/// Amount of tokens owned by an account inside a collection.153	#[pallet::storage]154	pub type Balance<T: Config> = StorageNMap<155		Key = (156			Key<Twox64Concat, CollectionId>,157			Key<Blake2_128Concat, T::CrossAccountId>,158		),159		Value = u128,160		QueryKind = ValueQuery,161	>;162163	/// Storage for assets delegated to a limited extent to other users.164	#[pallet::storage]165	pub type Allowance<T: Config> = StorageNMap<166		Key = (167			Key<Twox64Concat, CollectionId>,168			Key<Blake2_128, T::CrossAccountId>,       // Owner169			Key<Blake2_128Concat, T::CrossAccountId>, // Spender170		),171		Value = u128,172		QueryKind = ValueQuery,173	>;174}175176/// Wrapper around untyped collection handle, asserting inner collection is of fungible type.177/// Required for interaction with Fungible collections, type safety and implementation [`solidity_interface`][`evm_coder::solidity_interface`].178pub struct FungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);179180/// Implementation of methods required for dispatching during runtime.181impl<T: Config> FungibleHandle<T> {182	/// Casts [`CollectionHandle`][`pallet_common::CollectionHandle`] into [`FungibleHandle`].183	pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {184		Self(inner)185	}186187	/// Casts [`FungibleHandle`] into [`CollectionHandle`][`pallet_common::CollectionHandle`].188	pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {189		self.0190	}191	/// Returns a mutable reference to the internal [`CollectionHandle`][`pallet_common::CollectionHandle`].192	pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {193		&mut self.0194	}195}196impl<T: Config> WithRecorder<T> for FungibleHandle<T> {197	fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {198		self.0.recorder()199	}200	fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {201		self.0.into_recorder()202	}203}204impl<T: Config> Deref for FungibleHandle<T> {205	type Target = pallet_common::CollectionHandle<T>;206207	fn deref(&self) -> &Self::Target {208		&self.0209	}210}211212/// Pallet implementation for fungible assets213impl<T: Config> Pallet<T> {214	/// Destroys a collection.215	pub fn destroy_collection(216		collection: FungibleHandle<T>,217		sender: &T::CrossAccountId,218	) -> DispatchResult {219		let id = collection.id;220221		if Self::collection_has_tokens(id) {222			return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());223		}224225		// =========226227		PalletCommon::destroy_collection(collection.0, sender)?;228229		<TotalSupply<T>>::remove(id);230		let _ = <Balance<T>>::clear_prefix((id,), u32::MAX, None);231		let _ = <Allowance<T>>::clear_prefix((id,), u32::MAX, None);232		Ok(())233	}234235	/// Add properties to the collection.236	pub fn set_collection_properties(237		collection: &FungibleHandle<T>,238		sender: &T::CrossAccountId,239		properties: Vec<Property>,240	) -> DispatchResult {241		<PalletCommon<T>>::set_collection_properties(collection, sender, properties.into_iter())242	}243244	/// Delete properties of the collection, associated with the provided keys.245	pub fn delete_collection_properties(246		collection: &FungibleHandle<T>,247		sender: &T::CrossAccountId,248		property_keys: Vec<PropertyKey>,249	) -> DispatchResult {250		<PalletCommon<T>>::delete_collection_properties(251			collection,252			sender,253			property_keys.into_iter(),254		)255	}256257	/// Checks if collection has tokens. Return `true` if it has.258	fn collection_has_tokens(collection_id: CollectionId) -> bool {259		<TotalSupply<T>>::get(collection_id) != 0260	}261262	/// Burns the specified amount of the token. If the token balance263	/// or total supply is less than the given value,264	/// it will return [DispatchError].265	pub fn burn(266		collection: &FungibleHandle<T>,267		owner: &T::CrossAccountId,268		amount: u128,269	) -> DispatchResult {270		let total_supply = <TotalSupply<T>>::get(collection.id)271			.checked_sub(amount)272			.ok_or(<CommonError<T>>::TokenValueTooLow)?;273274		let balance = <Balance<T>>::get((collection.id, owner))275			.checked_sub(amount)276			.ok_or(<CommonError<T>>::TokenValueTooLow)?;277278		if collection.permissions.access() == AccessMode::AllowList {279			collection.check_allowlist(owner)?;280		}281282		// =========283284		if balance == 0 {285			<Balance<T>>::remove((collection.id, owner));286			<PalletStructure<T>>::unnest_if_nested(owner, collection.id, TokenId::default());287		} else {288			<Balance<T>>::insert((collection.id, owner), balance);289		}290		<TotalSupply<T>>::insert(collection.id, total_supply);291292		<PalletEvm<T>>::deposit_log(293			ERC20Events::Transfer {294				from: *owner.as_eth(),295				to: H160::default(),296				value: amount.into(),297			}298			.to_log(collection_id_to_address(collection.id)),299		);300		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(301			collection.id,302			TokenId::default(),303			owner.clone(),304			amount,305		));306		Ok(())307	}308309	/// Transfers the specified amount of tokens. Will check that310	/// the transfer is allowed for the token.311	///312	/// - `from`: Owner of tokens to transfer.313	/// - `to`: Recepient of transfered tokens.314	/// - `amount`: Amount of tokens to transfer.315	/// - `collection`: Collection that contains the token316	pub fn transfer(317		collection: &FungibleHandle<T>,318		from: &T::CrossAccountId,319		to: &T::CrossAccountId,320		amount: u128,321		nesting_budget: &dyn Budget,322	) -> DispatchResultWithPostInfo {323		let depositor = from;324		Self::transfer_internal(collection, depositor, from, to, amount, nesting_budget)325	}326327	/// Transfers tokens from the `from` account to the `to` account.328	/// The `depositor` is the account who deposits the tokens.329	/// For instance, the nesting rules will be checked against the `depositor`'s permissions.330	fn transfer_internal(331		collection: &FungibleHandle<T>,332		depositor: &T::CrossAccountId,333		from: &T::CrossAccountId,334		to: &T::CrossAccountId,335		amount: u128,336		nesting_budget: &dyn Budget,337	) -> DispatchResultWithPostInfo {338		ensure!(339			collection.limits.transfers_enabled(),340			<CommonError<T>>::TransferNotAllowed,341		);342343		let mut actual_weight = <SelfWeightOf<T>>::transfer_raw();344345		if collection.permissions.access() == AccessMode::AllowList {346			collection.check_allowlist(from)?;347			collection.check_allowlist(to)?;348			actual_weight += <PalletCommonWeightOf<T>>::check_accesslist() * 2;349		}350		<PalletCommon<T>>::ensure_correct_receiver(to)?;351		let balance_from = <Balance<T>>::get((collection.id, from))352			.checked_sub(amount)353			.ok_or(<CommonError<T>>::TokenValueTooLow)?;354		let balance_to = if from != to && amount != 0 {355			Some(356				<Balance<T>>::get((collection.id, to))357					.checked_add(amount)358					.ok_or(ArithmeticError::Overflow)?,359			)360		} else {361			None362		};363364		// =========365366		if let Some(balance_to) = balance_to {367			// from != to && amount != 0368369			<PalletStructure<T>>::nest_if_sent_to_token(370				depositor,371				to,372				collection.id,373				TokenId::default(),374				nesting_budget,375			)?;376377			if balance_from == 0 {378				<Balance<T>>::remove((collection.id, from));379				<PalletStructure<T>>::unnest_if_nested(from, collection.id, TokenId::default());380			} else {381				<Balance<T>>::insert((collection.id, from), balance_from);382			}383			<Balance<T>>::insert((collection.id, to), balance_to);384		}385386		<PalletEvm<T>>::deposit_log(387			ERC20Events::Transfer {388				from: *from.as_eth(),389				to: *to.as_eth(),390				value: amount.into(),391			}392			.to_log(collection_id_to_address(collection.id)),393		);394		<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(395			collection.id,396			TokenId::default(),397			from.clone(),398			to.clone(),399			amount,400		));401402		Ok(PostDispatchInfo {403			actual_weight: Some(actual_weight),404			pays_fee: Pays::Yes,405		})406	}407408	/// Minting tokens for multiple IDs.409	/// See [`create_item`][`Pallet::create_item`] for more details.410	pub fn create_multiple_items(411		collection: &FungibleHandle<T>,412		depositor: &T::CrossAccountId,413		data: BTreeMap<T::CrossAccountId, u128>,414		nesting_budget: &dyn Budget,415	) -> DispatchResult {416		if !collection.is_owner_or_admin(depositor) {417			ensure!(418				collection.permissions.mint_mode(),419				<CommonError<T>>::PublicMintingNotAllowed420			);421			collection.check_allowlist(depositor)?;422423			for (owner, _) in data.iter() {424				collection.check_allowlist(owner)?;425			}426		}427428		let total_supply = data429			.values()430			.copied()431			.try_fold(<TotalSupply<T>>::get(collection.id), |acc, v| {432				acc.checked_add(v)433			})434			.ok_or(ArithmeticError::Overflow)?;435436		for (to, _) in data.iter() {437			<PalletStructure<T>>::check_nesting(438				depositor,439				to,440				collection.id,441				TokenId::default(),442				nesting_budget,443			)?;444		}445446		let updated_balances = data447			.into_iter()448			.map(|(user, amount)| {449				let updated_balance = <Balance<T>>::get((collection.id, &user))450					.checked_add(amount)451					.ok_or(ArithmeticError::Overflow)?;452				Ok((user, amount, updated_balance))453			})454			.collect::<Result<Vec<_>, DispatchError>>()?;455456		// =========457458		<TotalSupply<T>>::insert(collection.id, total_supply);459		for (user, amount, updated_balance) in updated_balances {460			<Balance<T>>::insert((collection.id, &user), updated_balance);461			<PalletStructure<T>>::nest_if_sent_to_token_unchecked(462				&user,463				collection.id,464				TokenId::default(),465			);466			<PalletEvm<T>>::deposit_log(467				ERC20Events::Transfer {468					from: H160::default(),469					to: *user.as_eth(),470					value: amount.into(),471				}472				.to_log(collection_id_to_address(collection.id)),473			);474			<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(475				collection.id,476				TokenId::default(),477				user.clone(),478				amount,479			));480		}481482		Ok(())483	}484485	fn set_allowance_unchecked(486		collection: &FungibleHandle<T>,487		owner: &T::CrossAccountId,488		spender: &T::CrossAccountId,489		amount: u128,490	) {491		if amount == 0 {492			<Allowance<T>>::remove((collection.id, owner, spender));493		} else {494			<Allowance<T>>::insert((collection.id, owner, spender), amount);495		}496497		<PalletEvm<T>>::deposit_log(498			ERC20Events::Approval {499				owner: *owner.as_eth(),500				spender: *spender.as_eth(),501				value: amount.into(),502			}503			.to_log(collection_id_to_address(collection.id)),504		);505		<PalletCommon<T>>::deposit_event(CommonEvent::Approved(506			collection.id,507			TokenId(0),508			owner.clone(),509			spender.clone(),510			amount,511		));512	}513514	/// Set allowance for the spender to `transfer` or `burn` owner's tokens.515	///516	/// - `collection`: Collection that contains the token517	/// - `owner`: Owner of tokens that sets the allowance.518	/// - `spender`: Recipient of the allowance rights.519	/// - `amount`: Amount of tokens the spender is allowed to `transfer` or `burn`.520	pub fn set_allowance(521		collection: &FungibleHandle<T>,522		owner: &T::CrossAccountId,523		spender: &T::CrossAccountId,524		amount: u128,525	) -> DispatchResult {526		if collection.permissions.access() == AccessMode::AllowList {527			collection.check_allowlist(owner)?;528			collection.check_allowlist(spender)?;529		}530531		if <Balance<T>>::get((collection.id, owner)) < amount {532			ensure!(533				collection.ignores_owned_amount(owner),534				<CommonError<T>>::CantApproveMoreThanOwned535			);536		}537538		// =========539540		Self::set_allowance_unchecked(collection, owner, spender, amount);541		Ok(())542	}543544	/// Set allowance for the spender to `transfer` or `burn` owner's tokens from eth mirror.545	///546	/// - `collection`: Collection that contains the token547	/// - `sender`: Owner of tokens that sets the allowance.548	/// - `from`: Owner's eth mirror.549	/// - `to`: Recipient of the allowance rights.550	/// - `amount`: Amount of tokens the spender is allowed to `transfer` or `burn`.551	pub fn set_allowance_from(552		collection: &FungibleHandle<T>,553		sender: &T::CrossAccountId,554		from: &T::CrossAccountId,555		to: &T::CrossAccountId,556		amount: u128,557	) -> DispatchResult {558		if collection.permissions.access() == AccessMode::AllowList {559			collection.check_allowlist(sender)?;560			collection.check_allowlist(from)?;561			collection.check_allowlist(to)?;562		}563564		ensure!(565			sender.conv_eq(from),566			<CommonError<T>>::AddressIsNotEthMirror567		);568569		if <Balance<T>>::get((collection.id, from)) < amount {570			ensure!(571				collection.limits.owner_can_transfer()572					&& (collection.is_owner_or_admin(sender) || collection.is_owner_or_admin(from)),573				<CommonError<T>>::CantApproveMoreThanOwned574			);575		}576577		// =========578579		Self::set_allowance_unchecked(collection, from, to, amount);580		Ok(())581	}582583	/// Checks if a non-owner has (enough) allowance from the owner to perform operations on the tokens.584	/// Returns the expected remaining allowance - it should be set manually if the transaction proceeds.585	///586	/// - `collection`: Collection that contains the token.587	/// - `spender`: CrossAccountId who has the allowance rights.588	/// - `from`: The owner of the tokens who sets the allowance.589	/// - `amount`: Amount of tokens by which the allowance sholud be reduced.590	fn check_allowed(591		collection: &FungibleHandle<T>,592		spender: &T::CrossAccountId,593		from: &T::CrossAccountId,594		amount: u128,595		nesting_budget: &dyn Budget,596	) -> Result<Option<u128>, DispatchError> {597		if spender.conv_eq(from) {598			return Ok(None);599		}600		if collection.permissions.access() == AccessMode::AllowList {601			// `from`, `to` checked in [`transfer`]602			collection.check_allowlist(spender)?;603		}604605		if collection.ignores_token_restrictions(spender) {606			return Ok(Self::compute_allowance_decrease(607				collection, from, spender, amount,608			));609		}610611		if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {612			ensure!(613				<PalletStructure<T>>::check_indirectly_owned(614					spender.clone(),615					source.0,616					source.1,617					None,618					nesting_budget619				)?,620				<CommonError<T>>::ApprovedValueTooLow,621			);622			return Ok(None);623		}624625		let allowance = Self::compute_allowance_decrease(collection, from, spender, amount);626		ensure!(allowance.is_some(), <CommonError<T>>::ApprovedValueTooLow);627628		Ok(allowance)629	}630631	/// Returns `Some(amount)` if the `spender` have allowance to spend this amount.632	/// Otherwise, it returns `None`.633	fn compute_allowance_decrease(634		collection: &FungibleHandle<T>,635		from: &T::CrossAccountId,636		spender: &T::CrossAccountId,637		amount: u128,638	) -> Option<u128> {639		<Allowance<T>>::get((collection.id, from, spender)).checked_sub(amount)640	}641642	/// Transfer fungible tokens from one account to another.643	/// Same as the [`transfer`][`Pallet::transfer`] but spender doesn't needs to be an owner of the token pieces.644	/// The owner should set allowance for the spender to transfer pieces.645	/// See [`set_allowance`][`Pallet::set_allowance`] for more details.646	pub fn transfer_from(647		collection: &FungibleHandle<T>,648		spender: &T::CrossAccountId,649		from: &T::CrossAccountId,650		to: &T::CrossAccountId,651		amount: u128,652		nesting_budget: &dyn Budget,653	) -> DispatchResultWithPostInfo {654		let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;655656		// =========657658		let mut result =659			Self::transfer_internal(collection, spender, from, to, amount, nesting_budget);660		add_weight_to_post_info(&mut result, <SelfWeightOf<T>>::check_allowed_raw());661		result?;662663		if let Some(allowance) = allowance {664			Self::set_allowance_unchecked(collection, from, spender, allowance);665			add_weight_to_post_info(666				&mut result,667				<SelfWeightOf<T>>::set_allowance_unchecked_raw(),668			)669		}670		result671	}672673	/// Burn fungible tokens from the account.674	///675	/// Same as the [`burn`][`Pallet::burn`] but spender doesn't need to be an owner of the tokens. The `from` should676	/// set allowance for the spender to burn tokens.677	/// See [`set_allowance`][`Pallet::set_allowance`] for more details.678	pub fn burn_from(679		collection: &FungibleHandle<T>,680		spender: &T::CrossAccountId,681		from: &T::CrossAccountId,682		amount: u128,683		nesting_budget: &dyn Budget,684	) -> DispatchResult {685		let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;686687		// =========688689		Self::burn(collection, from, amount)?;690		if let Some(allowance) = allowance {691			Self::set_allowance_unchecked(collection, from, spender, allowance);692		}693		Ok(())694	}695696	/// Creates fungible token.697	///698	/// The sender should be the owner/admin of the collection or collection should be configured699	/// to allow public minting.700	///701	/// - `data`: Contains user who will become the owners of the tokens and amount702	///   of tokens he will receive.703	pub fn create_item(704		collection: &FungibleHandle<T>,705		sender: &T::CrossAccountId,706		data: CreateItemData<T>,707		nesting_budget: &dyn Budget,708	) -> DispatchResult {709		Self::create_multiple_items(710			collection,711			sender,712			[(data.0, data.1)].into_iter().collect(),713			nesting_budget,714		)715	}716717	/// Returns 10 tokens owners in no particular order718	///719	/// There is no direct way to get token holders in ascending order,720	/// since `iter_prefix` returns values in no particular order.721	/// Therefore, getting the 10 largest holders with a large value of holders722	/// can lead to impact memory allocation + sorting with  `n * log (n)`.723	pub fn token_owners(724		collection: CollectionId,725		_token: TokenId,726	) -> Option<Vec<T::CrossAccountId>> {727		let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection,))728			.map(|(owner, _amount)| owner)729			.take(10)730			.collect();731732		if res.is_empty() {733			None734		} else {735			Some(res)736		}737	}738}
modifiedpallets/nonfungible/src/common.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -19,8 +19,8 @@
 use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight};
 use pallet_common::{
 	weights::WeightInfo as _, with_weight, write_token_properties_total_weight,
-	CommonCollectionOperations, CommonWeightInfo, RefungibleExtensions,
-	SelfWeightOf as PalletCommonWeightOf,
+	CommonCollectionOperations, CommonWeightInfo, SelfWeightOf as PalletCommonWeightOf,
+	XcmExtensions,
 };
 use pallet_structure::Pallet as PalletStructure;
 use sp_runtime::DispatchError;
@@ -543,6 +543,10 @@
 		}
 	}
 
+	fn xcm_extensions(&self) -> Option<&dyn XcmExtensions<T>> {
+		Some(self)
+	}
+
 	fn set_allowance_for_all(
 		&self,
 		owner: T::CrossAccountId,
@@ -566,3 +570,53 @@
 		)
 	}
 }
+
+impl<T: Config> XcmExtensions<T> for NonfungibleHandle<T> {
+	fn is_foreign(&self) -> bool {
+		self.flags.foreign
+	}
+
+	fn create_item_internal(
+		&self,
+		depositor: &<T>::CrossAccountId,
+		to: <T>::CrossAccountId,
+		data: up_data_structs::CreateItemData,
+		nesting_budget: &dyn Budget,
+	) -> Result<TokenId, sp_runtime::DispatchError> {
+		<Pallet<T>>::create_multiple_items(
+			self,
+			&depositor,
+			vec![map_create_data::<T>(data, &to)?],
+			nesting_budget,
+		)?;
+
+		Ok(self.last_token_id())
+	}
+
+	fn transfer_item_internal(
+		&self,
+		depositor: &<T>::CrossAccountId,
+		from: &<T>::CrossAccountId,
+		to: &<T>::CrossAccountId,
+		token: TokenId,
+		amount: u128,
+		nesting_budget: &dyn Budget,
+	) -> sp_runtime::DispatchResult {
+		ensure!(amount == 1, <Error<T>>::NonfungibleItemsHaveNoAmount);
+
+		<Pallet<T>>::transfer_internal(self, &depositor, &from, &to, token, nesting_budget)
+			.map(|_| ())
+			.map_err(|post_info| post_info.error)
+	}
+
+	fn burn_item_internal(
+		&self,
+		from: T::CrossAccountId,
+		token: TokenId,
+		amount: u128,
+	) -> sp_runtime::DispatchResult {
+		ensure!(amount == 1, <Error<T>>::NonfungibleItemsHaveNoAmount);
+
+		<Pallet<T>>::burn(self, &from, token)
+	}
+}
modifiedprimitives/data-structs/src/budget.rsdiffbeforeafterboth
--- a/primitives/data-structs/src/budget.rs
+++ b/primitives/data-structs/src/budget.rs
@@ -36,3 +36,10 @@
 		true
 	}
 }
+
+pub struct ZeroBudget;
+impl Budget for ZeroBudget {
+	fn consume_custom(&self, _calls: u32) -> bool {
+		false
+	}
+}
modifiedruntime/common/config/pallets/foreign_asset.rsdiffbeforeafterboth
--- a/runtime/common/config/pallets/foreign_asset.rs
+++ b/runtime/common/config/pallets/foreign_asset.rs
@@ -1,14 +1,43 @@
 use frame_support::{parameter_types, PalletId};
+use pallet_evm::account::CrossAccountId;
+use sp_core::H160;
+use staging_xcm::prelude::*;
+use staging_xcm_builder::AccountKey20Aliases;
 
-use crate::{runtime_common::config::governance, Runtime, RuntimeEvent};
+use crate::{
+	runtime_common::config::{
+		ethereum::CrossAccountId as ConfigCrossAccountId,
+		governance,
+		xcm::{LocationToAccountId, SelfLocation},
+	},
+	RelayNetwork, Runtime, RuntimeEvent,
+};
 
 parameter_types! {
 	pub ForeignAssetPalletId: PalletId = PalletId(*b"frgnasts");
 }
 
+pub struct LocationToCrossAccountId;
+impl staging_xcm_executor::traits::ConvertLocation<ConfigCrossAccountId>
+	for LocationToCrossAccountId
+{
+	fn convert_location(location: &MultiLocation) -> Option<ConfigCrossAccountId> {
+		LocationToAccountId::convert_location(location)
+			.map(|sub| ConfigCrossAccountId::from_sub(sub))
+			.or_else(|| {
+				let eth_address =
+					AccountKey20Aliases::<RelayNetwork, H160>::convert_location(location)?;
+
+				Some(ConfigCrossAccountId::from_eth(eth_address))
+			})
+	}
+}
+
 impl pallet_foreign_assets::Config for Runtime {
 	type RuntimeEvent = RuntimeEvent;
 	type ForceRegisterOrigin = governance::RootOrTechnicalCommitteeMember;
 	type PalletId = ForeignAssetPalletId;
+	type SelfLocation = SelfLocation;
+	type LocationToAccountId = LocationToCrossAccountId;
 	type WeightInfo = pallet_foreign_assets::weights::SubstrateWeight<Self>;
 }