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
19use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight};19use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight};
20use pallet_common::{20use pallet_common::{
21 weights::WeightInfo as _, with_weight, CommonCollectionOperations, CommonWeightInfo,21 weights::WeightInfo as _, with_weight, CommonCollectionOperations, CommonWeightInfo,
22 Error as CommonError, RefungibleExtensions, SelfWeightOf as PalletCommonWeightOf,22 Error as CommonError, SelfWeightOf as PalletCommonWeightOf, XcmExtensions,
23};23};
24use sp_runtime::{ArithmeticError, DispatchError};24use sp_runtime::{ArithmeticError, DispatchError};
25use sp_std::{vec, vec::Vec};25use sp_std::{vec, vec::Vec};
114 <Pallet<T>>::create_item(self, &sender, (to, fungible_data.value), nesting_budget),114 <Pallet<T>>::create_item(self, &sender, (to, fungible_data.value), nesting_budget),
115 <CommonWeights<T>>::create_item(&data),115 <CommonWeights<T>>::create_item(&data),
116 ),116 ),
117 _ => fail!(<Error<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),117 _ => fail!(<CommonError<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),
118 }118 }
119 }119 }
120120
133 .checked_add(data.value)133 .checked_add(data.value)
134 .ok_or(ArithmeticError::Overflow)?;134 .ok_or(ArithmeticError::Overflow)?;
135 }135 }
136 _ => fail!(<Error<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),136 _ => fail!(<CommonError<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),
137 }137 }
138 }138 }
139139
152 let weight = <CommonWeights<T>>::create_multiple_items_ex(&data);152 let weight = <CommonWeights<T>>::create_multiple_items_ex(&data);
153 let data = match data {153 let data = match data {
154 up_data_structs::CreateItemExData::Fungible(f) => f,154 up_data_structs::CreateItemExData::Fungible(f) => f,
155 _ => fail!(<Error<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),155 _ => fail!(<CommonError<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),
156 };156 };
157157
158 with_weight(158 with_weight(
435 <TotalSupply<T>>::try_get(self.id).ok()435 <TotalSupply<T>>::try_get(self.id).ok()
436 }436 }
437
438 fn xcm_extensions(&self) -> Option<&dyn XcmExtensions<T>> {
439 Some(self)
440 }
437441
438 fn set_allowance_for_all(442 fn set_allowance_for_all(
439 &self,443 &self,
454 }458 }
455}459}
460
461impl<T: Config> XcmExtensions<T> for FungibleHandle<T> {
462 fn is_foreign(&self) -> bool {
463 self.flags.foreign
464 }
465
466 fn create_item_internal(
467 &self,
468 depositor: &<T>::CrossAccountId,
469 to: <T>::CrossAccountId,
470 data: CreateItemData,
471 nesting_budget: &dyn Budget,
472 ) -> Result<TokenId, sp_runtime::DispatchError> {
473 match &data {
474 up_data_structs::CreateItemData::Fungible(fungible_data) => {
475 <Pallet<T>>::create_multiple_items(
476 self,
477 &depositor,
478 [(to, fungible_data.value)].into_iter().collect(),
479 nesting_budget,
480 )?
481 }
482 _ => fail!(<CommonError<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),
483 }
484
485 Ok(TokenId::default())
486 }
487
488 fn transfer_item_internal(
489 &self,
490 depositor: &<T>::CrossAccountId,
491 from: &<T>::CrossAccountId,
492 to: &<T>::CrossAccountId,
493 token: TokenId,
494 amount: u128,
495 nesting_budget: &dyn Budget,
496 ) -> sp_runtime::DispatchResult {
497 ensure!(
498 token == TokenId::default(),
499 <CommonError<T>>::FungibleItemsHaveNoId
500 );
501
502 <Pallet<T>>::transfer_internal(self, &depositor, &from, &to, amount, nesting_budget)
503 .map(|_| ())
504 .map_err(|post_info| post_info.error)
505 }
506
507 fn burn_item_internal(
508 &self,
509 from: <T>::CrossAccountId,
510 token: TokenId,
511 amount: u128,
512 ) -> sp_runtime::DispatchResult {
513 <Self as CommonCollectionOperations<T>>::burn_item(&self, from, token, amount)
514 .map(|_| ())
515 .map_err(|post_info| post_info.error)
516 }
517}
456518
modifiedpallets/fungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -95,8 +95,9 @@
 use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};
 use sp_std::{collections::btree_map::BTreeMap, vec::Vec};
 use up_data_structs::{
-	budget::Budget, mapping::TokenAddressMapping, AccessMode, CollectionId, CreateCollectionData,
-	Property, PropertyKey, TokenId,
+	budget::{Budget, ZeroBudget},
+	mapping::TokenAddressMapping,
+	AccessMode, CollectionId, CreateCollectionData, Property, PropertyKey, TokenId,
 };
 use weights::WeightInfo;
 
@@ -121,8 +122,6 @@
 
 	#[pallet::error]
 	pub enum Error<T> {
-		/// Not Fungible item data used to mint in Fungible collection.
-		NotFungibleDataUsedToMintFungibleCollectionToken,
 		/// Tried to set data for fungible item.
 		FungibleItemsDontHaveData,
 		/// Fungible token does not support nesting.
@@ -275,9 +274,6 @@
 		let balance = <Balance<T>>::get((collection.id, owner))
 			.checked_sub(amount)
 			.ok_or(<CommonError<T>>::TokenValueTooLow)?;
-
-		// Foreign collection check
-		ensure!(!collection.flags.foreign, <CommonError<T>>::NoPermission);
 
 		if collection.permissions.access() == AccessMode::AllowList {
 			collection.check_allowlist(owner)?;
@@ -309,47 +305,7 @@
 		));
 		Ok(())
 	}
-
-	/// Burns the specified amount of the token.
-	pub fn burn_foreign(
-		collection: &FungibleHandle<T>,
-		owner: &T::CrossAccountId,
-		amount: u128,
-	) -> DispatchResult {
-		let total_supply = <TotalSupply<T>>::get(collection.id)
-			.checked_sub(amount)
-			.ok_or(<CommonError<T>>::TokenValueTooLow)?;
-
-		let balance = <Balance<T>>::get((collection.id, owner))
-			.checked_sub(amount)
-			.ok_or(<CommonError<T>>::TokenValueTooLow)?;
-		// =========
 
-		if balance == 0 {
-			<Balance<T>>::remove((collection.id, owner));
-			<PalletStructure<T>>::unnest_if_nested(owner, collection.id, TokenId::default());
-		} else {
-			<Balance<T>>::insert((collection.id, owner), balance);
-		}
-		<TotalSupply<T>>::insert(collection.id, total_supply);
-
-		<PalletEvm<T>>::deposit_log(
-			ERC20Events::Transfer {
-				from: *owner.as_eth(),
-				to: H160::default(),
-				value: amount.into(),
-			}
-			.to_log(collection_id_to_address(collection.id)),
-		);
-		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(
-			collection.id,
-			TokenId::default(),
-			owner.clone(),
-			amount,
-		));
-		Ok(())
-	}
-
 	/// Transfers the specified amount of tokens. Will check that
 	/// the transfer is allowed for the token.
 	///
@@ -450,14 +406,25 @@
 	}
 
 	/// Minting tokens for multiple IDs.
-	/// It is a utility function used in [`create_multiple_items`][`Pallet::create_multiple_items`]
-	/// and [`create_multiple_items_foreign`][`Pallet::create_multiple_items_foreign`]
-	pub fn create_multiple_items_common(
+	/// See [`create_item`][`Pallet::create_item`] for more details.
+	pub fn create_multiple_items(
 		collection: &FungibleHandle<T>,
-		sender: &T::CrossAccountId,
+		depositor: &T::CrossAccountId,
 		data: BTreeMap<T::CrossAccountId, u128>,
 		nesting_budget: &dyn Budget,
 	) -> DispatchResult {
+		if !collection.is_owner_or_admin(depositor) {
+			ensure!(
+				collection.permissions.mint_mode(),
+				<CommonError<T>>::PublicMintingNotAllowed
+			);
+			collection.check_allowlist(depositor)?;
+
+			for (owner, _) in data.iter() {
+				collection.check_allowlist(owner)?;
+			}
+		}
+
 		let total_supply = data
 			.values()
 			.copied()
@@ -468,7 +435,7 @@
 
 		for (to, _) in data.iter() {
 			<PalletStructure<T>>::check_nesting(
-				sender,
+				depositor,
 				to,
 				collection.id,
 				TokenId::default(),
@@ -514,44 +481,7 @@
 
 		Ok(())
 	}
-
-	/// Minting tokens for multiple IDs.
-	/// See [`create_item`][`Pallet::create_item`] for more details.
-	pub fn create_multiple_items(
-		collection: &FungibleHandle<T>,
-		sender: &T::CrossAccountId,
-		data: BTreeMap<T::CrossAccountId, u128>,
-		nesting_budget: &dyn Budget,
-	) -> DispatchResult {
-		// Foreign collection check
-		ensure!(!collection.flags.foreign, <CommonError<T>>::NoPermission);
-
-		if !collection.is_owner_or_admin(sender) {
-			ensure!(
-				collection.permissions.mint_mode(),
-				<CommonError<T>>::PublicMintingNotAllowed
-			);
-			collection.check_allowlist(sender)?;
-
-			for (owner, _) in data.iter() {
-				collection.check_allowlist(owner)?;
-			}
-		}
 
-		Self::create_multiple_items_common(collection, sender, data, nesting_budget)
-	}
-
-	/// Minting tokens for multiple IDs.
-	/// See [`create_item_foreign`][`Pallet::create_item_foreign`] for more details.
-	pub fn create_multiple_items_foreign(
-		collection: &FungibleHandle<T>,
-		sender: &T::CrossAccountId,
-		data: BTreeMap<T::CrossAccountId, u128>,
-		nesting_budget: &dyn Budget,
-	) -> DispatchResult {
-		Self::create_multiple_items_common(collection, sender, data, nesting_budget)
-	}
-
 	fn set_allowance_unchecked(
 		collection: &FungibleHandle<T>,
 		owner: &T::CrossAccountId,
@@ -777,24 +707,6 @@
 		nesting_budget: &dyn Budget,
 	) -> DispatchResult {
 		Self::create_multiple_items(
-			collection,
-			sender,
-			[(data.0, data.1)].into_iter().collect(),
-			nesting_budget,
-		)
-	}
-
-	/// Creates fungible token.
-	///
-	/// - `data`: Contains user who will become the owners of the tokens and amount
-	///   of tokens he will receive.
-	pub fn create_item_foreign(
-		collection: &FungibleHandle<T>,
-		sender: &T::CrossAccountId,
-		data: CreateItemData<T>,
-		nesting_budget: &dyn Budget,
-	) -> DispatchResult {
-		Self::create_multiple_items_foreign(
 			collection,
 			sender,
 			[(data.0, data.1)].into_iter().collect(),
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>;
 }