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
20//! - [`Call`]20//! - [`Call`]
21//! - [`Pallet`]21//! - [`Pallet`]
22//!22//!
23//! ## Overview
24//!
25//! The foreign assests pallet provides functions for:
26//!
27//! - Local and foreign assets management. The foreign assets can be updated without runtime upgrade.
28//! - Bounds between asset and target collection for cross chain transfer and inner transfers.
29//!
30//! ## Overview23//! ## Overview
31//!24//!
32//! Under construction25//! Under construction
37use frame_support::{dispatch::DispatchResult, pallet_prelude::*, traits::EnsureOrigin, PalletId};30use frame_support::{dispatch::DispatchResult, pallet_prelude::*, traits::EnsureOrigin, PalletId};
38use frame_system::pallet_prelude::*;31use frame_system::pallet_prelude::*;
39use pallet_common::{32use pallet_common::{
40 dispatch::CollectionDispatch, erc::CrossAccountId, NATIVE_FUNGIBLE_COLLECTION_ID,33 dispatch::CollectionDispatch, erc::CrossAccountId, XcmExtensions, NATIVE_FUNGIBLE_COLLECTION_ID,
41};34};
42use sp_runtime::traits::AccountIdConversion;35use sp_runtime::traits::AccountIdConversion;
43use sp_std::{vec, vec::Vec};36use sp_std::{vec, vec::Vec};
44// NOTE: MultiLocation is used in storages, we will need to do migration if upgrade the
45// MultiLocation to the XCM v3.
46use staging_xcm::{37use staging_xcm::{
47 opaque::latest::{prelude::XcmError, Weight},38 opaque::latest::{prelude::XcmError, Weight},
48 v3::{prelude::*, MultiAsset, XcmContext},39 v3::{prelude::*, MultiAsset, XcmContext},
49};40};
50use staging_xcm_executor::{41use staging_xcm_executor::{
51 traits::{TransactAsset, WeightTrader},42 traits::{ConvertLocation, Error as XcmExecutorError, TransactAsset, WeightTrader},
52 Assets,43 Assets,
53};44};
54use up_data_structs::{45use up_data_structs::{
55 CollectionId, CollectionMode, CollectionName, CreateCollectionData, PropertyKey, TokenId,46 budget::ZeroBudget, CollectionId, CollectionMode, CollectionName, CreateCollectionData,
47 CreateFungibleData, CreateItemData, CreateNftData, Property, PropertyKey, TokenId,
56};48};
5749
58pub mod weights;50pub mod weights;
87 /// The ID of the foreign assets pallet.79 /// The ID of the foreign assets pallet.
88 type PalletId: Get<PalletId>;80 type PalletId: Get<PalletId>;
81
82 /// Self-location of this parachain.
83 type SelfLocation: Get<MultiLocation>;
84
85 /// The converter from a MultiLocation to a CrossAccountId.
86 type LocationToAccountId: ConvertLocation<Self::CrossAccountId>;
8987
90 /// Weight information for the extrinsics in this module.88 /// Weight information for the extrinsics in this module.
91 type WeightInfo: WeightInfo;89 type WeightInfo: WeightInfo;
113 pub type ForeignReserveLocationToCollection<T: Config> =111 pub type ForeignReserveLocationToCollection<T: Config> =
114 StorageMap<_, Twox64Concat, staging_xcm::v3::MultiLocation, CollectionId, OptionQuery>;112 StorageMap<_, Twox64Concat, staging_xcm::v3::MultiLocation, CollectionId, OptionQuery>;
113
114 /// The corresponding reserve location of collections.
115 #[pallet::storage]
116 #[pallet::getter(fn collection_to_foreign_reserve_location)]
117 pub type CollectionToForeignReserveLocation<T: Config> =
118 StorageMap<_, Twox64Concat, CollectionId, staging_xcm::v3::MultiLocation, OptionQuery>;
115119
116 /// The correponding NFT token id of reserve NFTs120 /// The correponding NFT token id of reserve NFTs
117 #[pallet::storage]121 #[pallet::storage]
180 )?;184 )?;
181185
182 <ForeignReserveLocationToCollection<T>>::insert(reserve_location, collection_id);186 <ForeignReserveLocationToCollection<T>>::insert(reserve_location, collection_id);
187 <CollectionToForeignReserveLocation<T>>::insert(collection_id, reserve_location);
183188
184 Self::deposit_event(Event::<T>::ForeignAssetRegistered {189 Self::deposit_event(Event::<T>::ForeignAssetRegistered {
185 asset_id: collection_id,190 asset_id: collection_id,
211 .expect("key length < max property key length; qed")216 .expect("key length < max property key length; qed")
212 }217 }
218
219 fn native_asset_location_to_collection(
220 asset_location: &MultiLocation,
221 ) -> Result<Option<CollectionId>, XcmError> {
222 let self_location = T::SelfLocation::get();
223
224 if *asset_location == Here.into() {
225 Ok(Some(NATIVE_FUNGIBLE_COLLECTION_ID))
226 } else if *asset_location == self_location {
227 Ok(Some(NATIVE_FUNGIBLE_COLLECTION_ID))
228 } else if asset_location.parents == self_location.parents {
229 match asset_location
230 .interior
231 .match_and_split(&self_location.interior)
232 {
233 Some(GeneralIndex(collection_id)) => Ok(Some(CollectionId(
234 (*collection_id)
235 .try_into()
236 .map_err(|_| XcmExecutorError::AssetIdConversionFailed)?,
237 ))),
238 _ => Ok(None),
239 }
240 } else {
241 Ok(None)
242 }
243 }
244
245 fn multiasset_to_collection(asset: &MultiAsset) -> Result<CollectionId, XcmError> {
246 let AssetId::Concrete(asset_reserve_location) = asset.id else {
247 return Err(XcmExecutorError::AssetNotHandled.into());
248 };
249
250 Self::native_asset_location_to_collection(&asset_reserve_location)?
251 .or_else(|| Self::foreign_reserve_location_to_collection(asset_reserve_location))
252 .ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())
253 }
254
255 fn native_asset_instance_to_token_id(
256 asset_instance: &AssetInstance,
257 ) -> Result<TokenId, XcmError> {
258 match asset_instance {
259 AssetInstance::Index(token_id) => Ok(TokenId(
260 (*token_id)
261 .try_into()
262 .map_err(|_| XcmError::AssetNotFound)?,
263 )),
264 _ => Err(XcmError::AssetNotFound),
265 }
266 }
267
268 /// Obtains the token id of the `asset_instance` in the collection.
269 ///
270 /// Returns `Ok(None)` only if the `asset_instance` points to a foreign item
271 /// and it haven't been created on this blockchain yet.
272 ///
273 /// If the `asset_instance` points to a native item, it cannot return `Ok(None)`.
274 fn asset_instance_to_token_id(
275 xcm_ext: &dyn XcmExtensions<T>,
276 collection_id: CollectionId,
277 asset_instance: &AssetInstance,
278 ) -> Result<Option<TokenId>, XcmError> {
279 if xcm_ext.is_foreign() {
280 Ok(Self::foreign_reserve_asset_instance_to_token_id(
281 collection_id,
282 asset_instance,
283 ))
284 } else {
285 Self::native_asset_instance_to_token_id(asset_instance).map(Some)
286 }
287 }
288
289 fn create_foreign_asset_instance(
290 xcm_ext: &dyn XcmExtensions<T>,
291 collection_id: CollectionId,
292 asset_instance: &AssetInstance,
293 to: T::CrossAccountId,
294 ) -> XcmResult {
295 let asset_instance_encoded = asset_instance.encode();
296
297 let derivative_token_id = xcm_ext
298 .create_item(
299 &Self::pallet_account(),
300 to,
301 CreateItemData::NFT(CreateNftData {
302 properties: vec![Property {
303 key: Self::reserve_asset_instance_property_key(),
304 value: asset_instance_encoded
305 .try_into()
306 .expect("asset instance length <= 32 bytes which is less than value length limit; qed"),
307 }]
308 .try_into()
309 .expect("just one property can always be stored; qed"),
310 }),
311 &ZeroBudget,
312 )
313 .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))?;
314
315 <ForeignReserveAssetInstanceToTokenId<T>>::insert(
316 collection_id,
317 asset_instance,
318 derivative_token_id,
319 );
320
321 Ok(())
322 }
323
324 fn deposit_asset_instance(
325 xcm_ext: &dyn XcmExtensions<T>,
326 collection_id: CollectionId,
327 to: T::CrossAccountId,
328 asset_instance: &AssetInstance,
329 ) -> XcmResult {
330 if let Some(token_id) =
331 Self::asset_instance_to_token_id(xcm_ext, collection_id, asset_instance)?
332 {
333 let depositor = &Self::pallet_account();
334 let from = depositor;
335 let amount = 1;
336
337 xcm_ext
338 .transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)
339 .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))
340 } else {
341 Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)
342 }
343 }
213}344}
214345
215impl<T: Config> TransactAsset for Pallet<T> {346impl<T: Config> TransactAsset for Pallet<T> {
234 fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}365 fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}
235366
236 fn deposit_asset(what: &MultiAsset, to: &MultiLocation, context: &XcmContext) -> XcmResult {367 fn deposit_asset(what: &MultiAsset, to: &MultiLocation, context: &XcmContext) -> XcmResult {
368 let collection_id = Self::multiasset_to_collection(what)?;
369 let dispatch =
370 T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;
371
372 let collection = dispatch.as_dyn();
237 Err(XcmError::Unimplemented)373 let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;
374
375 let to = T::LocationToAccountId::convert_location(to)
376 .ok_or(XcmExecutorError::AccountIdConversionFailed)?;
377
378 match what.fun {
379 Fungibility::Fungible(amount) => xcm_ext
380 .create_item(
381 &Self::pallet_account(),
382 to,
383 CreateItemData::Fungible(CreateFungibleData { value: amount }),
384 &ZeroBudget,
385 )
386 .map(|_| ())
387 .map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),
388
389 Fungibility::NonFungible(asset_instance) => {
390 Self::deposit_asset_instance(xcm_ext, collection_id, to, &asset_instance)
391 }
392 }
238 }393 }
239394
240 fn withdraw_asset(395 fn withdraw_asset(
263 if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {418 if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {
264 Some(Here.into())419 Some(Here.into())
265 } else {420 } else {
266 // let dispatch = T::CollectionDispatch::dispatch(collection_id).ok()?;421 let dispatch = T::CollectionDispatch::dispatch(collection_id).ok()?;
267 // let collection = dispatch.as_dyn();
268 // let xcm_ext = collection.xcm_extensions()?;
269
270 // if xcm_ext.is_foreign() {
271 // let encoded_location =
272 // collection.property(&<Pallet<T>>::reserve_location_property_key())?;
273 // MultiLocation::decode(&mut &encoded_location[..]).ok()
274 // } else {
275 // T::SelfLocation::get()
276 // .pushed_with_interior(GeneralIndex(collection_id.0.into()))
277 // .ok()
278 // }
279 todo!()422 let collection = dispatch.as_dyn();
423 let xcm_ext = collection.xcm_extensions()?;
424
425 if xcm_ext.is_foreign() {
426 <Pallet<T>>::collection_to_foreign_reserve_location(collection_id)
427 } else {
428 T::SelfLocation::get()
429 .pushed_with_interior(GeneralIndex(collection_id.0.into()))
430 .ok()
431 }
280 }432 }
281 }433 }
282}434}
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
--- 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>;
 }