git.delta.rocks / unique-network / refs/commits / be35f7200710

difftreelog

source

pallets/foreign-assets/src/lib.rs24.1 KiBsourcehistory
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//! # Foreign Assets18//!19//! ## Overview20//!21//! The Foreign Assets is a proxy that maps XCM operations to the Unique Network's pallets logic.2223#![cfg_attr(not(feature = "std"), no_std)]24#![allow(clippy::unused_unit)]2526use core::ops::Deref;2728use frame_support::{29	dispatch::DispatchResult, pallet_prelude::*, storage_alias, traits::EnsureOrigin, PalletId,30};31use frame_system::pallet_prelude::*;32use pallet_common::{33	dispatch::CollectionDispatch, erc::CrossAccountId, XcmExtensions, NATIVE_FUNGIBLE_COLLECTION_ID,34};35use sp_runtime::traits::AccountIdConversion;36use sp_std::{boxed::Box, vec, vec::Vec};37use staging_xcm::{v4::prelude::*, VersionedAssetId};38use staging_xcm_executor::{39	traits::{ConvertLocation, Error as XcmExecutorError, TransactAsset, WeightTrader},40	AssetsInHolding,41};42use up_data_structs::{43	budget::ZeroBudget, CollectionId, CollectionMode, CollectionName, CollectionTokenPrefix,44	CreateCollectionData, CreateFungibleData, CreateItemData, TokenId,45};4647pub mod weights;4849#[cfg(feature = "runtime-benchmarks")]50mod benchmarking;5152pub use module::*;53pub use weights::WeightInfo;5455/// Status of storage migration from an old XCM version to a new one.56#[derive(Clone, Copy, PartialEq, Eq, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)]57pub enum MigrationStatus {58	V3ToV4(MigrationStatusV3ToV4),59}6061/// Status of storage migration from XCMv3 to XCMv4.62#[derive(Clone, Copy, PartialEq, Eq, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)]63pub enum MigrationStatusV3ToV4 {64	/// The migration is completed.65	Done,6667	/// An asset is skipped during the migration68	/// due to its inconsistent state.69	SkippedInconsistentAssetData(staging_xcm::v3::AssetId),7071	/// An asset instance is skipped during the migration72	/// due to its inconsistent state.73	SkippedInconsistentAssetInstanceData {74		asset_id: staging_xcm::v3::AssetId,75		asset_instance: staging_xcm::v3::AssetInstance,76	},7778	/// An asset is skipped during the migration79	/// because it couldn't be converted to the new XCM version.80	SkippedNotConvertibleAssetId(staging_xcm::v3::AssetId),8182	/// An asset instance is skipped during the migration83	/// because it couldn't be converted to the new XCM version.84	SkippedNotConvertibleAssetInstance {85		asset_id: staging_xcm::v3::AssetId,86		asset_instance: staging_xcm::v3::AssetInstance,87	},88}8990#[frame_support::pallet]91pub mod module {92	use pallet_common::CollectionIssuer;93	use up_data_structs::CollectionDescription;9495	use super::*;9697	#[pallet::config]98	pub trait Config:99		frame_system::Config100		+ pallet_common::Config101		+ pallet_fungible::Config102		+ pallet_balances::Config103	{104		/// The overarching event type.105		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;106107		/// Origin for force registering of a foreign asset.108		type ForceRegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;109110		/// The ID of the foreign assets pallet.111		type PalletId: Get<PalletId>;112113		/// Self-location of this parachain.114		type SelfLocation: Get<Location>;115116		/// The converter from a Location to a CrossAccountId.117		type LocationToAccountId: ConvertLocation<Self::CrossAccountId>;118119		/// Weight information for the extrinsics in this module.120		type WeightInfo: WeightInfo;121	}122123	#[pallet::error]124	pub enum Error<T> {125		/// The foreign asset is already registered.126		ForeignAssetAlreadyRegistered,127128		/// The given asset ID could not be converted into the current XCM version.129		BadForeignAssetId,130	}131132	#[pallet::event]133	#[pallet::generate_deposit(pub(crate) fn deposit_event)]134	pub enum Event<T: Config> {135		/// The foreign asset registered.136		ForeignAssetRegistered {137			collection_id: CollectionId,138			asset_id: Box<VersionedAssetId>,139		},140141		/// The migration status.142		MigrationStatus(MigrationStatus),143	}144145	/// The corresponding collections of foreign assets.146	#[pallet::storage]147	#[pallet::getter(fn foreign_asset_to_collection)]148	pub type ForeignAssetToCollection<T: Config> =149		StorageMap<_, Blake2_128Concat, staging_xcm::v4::AssetId, CollectionId, OptionQuery>;150151	/// The corresponding foreign assets of collections.152	#[pallet::storage]153	#[pallet::getter(fn collection_to_foreign_asset)]154	pub type CollectionToForeignAsset<T: Config> =155		StorageMap<_, Blake2_128Concat, CollectionId, staging_xcm::v4::AssetId, OptionQuery>;156157	/// The correponding NFT token id of reserve NFTs158	#[pallet::storage]159	#[pallet::getter(fn foreign_reserve_asset_instance_to_token_id)]160	pub type ForeignReserveAssetInstanceToTokenId<T: Config> = StorageDoubleMap<161		Hasher1 = Blake2_128Concat,162		Key1 = CollectionId,163		Hasher2 = Blake2_128Concat,164		Key2 = staging_xcm::v4::AssetInstance,165		Value = TokenId,166		QueryKind = OptionQuery,167	>;168169	/// The correponding reserve NFT of a token ID170	#[pallet::storage]171	#[pallet::getter(fn token_id_to_foreign_reserve_asset_instance)]172	pub type TokenIdToForeignReserveAssetInstance<T: Config> = StorageDoubleMap<173		Hasher1 = Blake2_128Concat,174		Key1 = CollectionId,175		Hasher2 = Blake2_128Concat,176		Key2 = TokenId,177		Value = staging_xcm::v4::AssetInstance,178		QueryKind = OptionQuery,179	>;180181	const STORAGE_VERSION: StorageVersion = StorageVersion::new(staging_xcm::v4::VERSION as u16);182183	#[pallet::pallet]184	#[pallet::storage_version(STORAGE_VERSION)]185	pub struct Pallet<T>(_);186187	#[pallet::call]188	impl<T: Config> Pallet<T> {189		#[pallet::call_index(0)]190		#[pallet::weight(<T as Config>::WeightInfo::force_register_foreign_asset())]191		pub fn force_register_foreign_asset(192			origin: OriginFor<T>,193			versioned_asset_id: Box<VersionedAssetId>,194			name: CollectionName,195			token_prefix: CollectionTokenPrefix,196			mode: ForeignCollectionMode,197		) -> DispatchResult {198			T::ForceRegisterOrigin::ensure_origin(origin.clone())?;199200			let asset_id: AssetId = versioned_asset_id201				.as_ref()202				.clone()203				.try_into()204				.map_err(|()| Error::<T>::BadForeignAssetId)?;205206			ensure!(207				!<ForeignAssetToCollection<T>>::contains_key(&asset_id),208				<Error<T>>::ForeignAssetAlreadyRegistered,209			);210211			let foreign_collection_owner = Self::pallet_account();212213			let description: CollectionDescription = "Foreign Assets Collection"214				.encode_utf16()215				.collect::<Vec<_>>()216				.try_into()217				.expect("description length < max description length; qed");218219			let collection_id = T::CollectionDispatch::create(220				foreign_collection_owner,221				CollectionIssuer::Internals,222				CreateCollectionData {223					name,224					token_prefix,225					description,226					mode: mode.into(),227					..Default::default()228				},229			)?;230231			<ForeignAssetToCollection<T>>::insert(&asset_id, collection_id);232			<CollectionToForeignAsset<T>>::insert(collection_id, asset_id);233234			Self::deposit_event(Event::<T>::ForeignAssetRegistered {235				collection_id,236				asset_id: versioned_asset_id,237			});238239			Ok(())240		}241	}242243	#[pallet::hooks]244	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {245		fn on_runtime_upgrade() -> Weight {246			Self::migrate_v3_to_v4()247		}248	}249}250251mod v3_storage {252	use super::*;253254	#[storage_alias]255	pub type ForeignAssetToCollection<T: Config> =256		StorageMap<Pallet<T>, Twox64Concat, staging_xcm::v3::AssetId, CollectionId, OptionQuery>;257258	#[storage_alias]259	pub type CollectionToForeignAsset<T: Config> =260		StorageMap<Pallet<T>, Twox64Concat, CollectionId, staging_xcm::v3::AssetId, OptionQuery>;261262	#[storage_alias]263	pub type ForeignReserveAssetInstanceToTokenId<T: Config> = StorageDoubleMap<264		Pallet<T>,265		Twox64Concat,266		CollectionId,267		Blake2_128Concat,268		staging_xcm::v3::AssetInstance,269		TokenId,270		OptionQuery,271	>;272273	#[storage_alias]274	pub type TokenIdToForeignReserveAssetInstance<T: Config> = StorageDoubleMap<275		Pallet<T>,276		Twox64Concat,277		CollectionId,278		Blake2_128Concat,279		TokenId,280		staging_xcm::v3::AssetInstance,281		OptionQuery,282	>;283}284285impl<T: Config> Pallet<T> {286	fn migrate_v3_to_v4() -> Weight {287		if Self::on_chain_storage_version() < staging_xcm::v4::VERSION as u16 {288			let put_version_weight = T::DbWeight::get().writes(1);289			let event_weight = T::DbWeight::get().writes(1);290			let collection_migration_weight = Self::migrate_collections();291292			StorageVersion::new(staging_xcm::v4::VERSION as u16).put::<Self>();293294			Self::deposit_event(Event::<T>::MigrationStatus(MigrationStatus::V3ToV4(295				MigrationStatusV3ToV4::Done,296			)));297298			put_version_weight299				.saturating_add(collection_migration_weight)300				.saturating_add(event_weight)301		} else {302			Weight::zero()303		}304	}305306	fn migrate_collections() -> Weight {307		use MigrationStatus::*;308		use MigrationStatusV3ToV4::*;309310		let mut weight = Weight::zero();311312		for (fwd_asset_id, collection_id) in v3_storage::ForeignAssetToCollection::<T>::drain() {313			let bwd_asset_id = v3_storage::CollectionToForeignAsset::<T>::take(collection_id);314			weight = weight.saturating_add(T::DbWeight::get().reads(2));315316			let Some(bwd_asset_id) = bwd_asset_id else {317				Self::deposit_event(Event::<T>::MigrationStatus(V3ToV4(318					SkippedInconsistentAssetData(fwd_asset_id),319				)));320321				weight = weight.saturating_add(T::DbWeight::get().writes(1));322				continue;323			};324325			if fwd_asset_id != bwd_asset_id {326				Self::deposit_event(Event::<T>::MigrationStatus(V3ToV4(327					SkippedInconsistentAssetData(fwd_asset_id),328				)));329330				weight = weight.saturating_add(T::DbWeight::get().writes(1));331				continue;332			}333334			let Ok(asset_id) = staging_xcm::v4::AssetId::try_from(fwd_asset_id) else {335				Self::deposit_event(Event::<T>::MigrationStatus(V3ToV4(336					SkippedNotConvertibleAssetId(fwd_asset_id),337				)));338339				weight = weight.saturating_add(T::DbWeight::get().writes(1));340				continue;341			};342343			<ForeignAssetToCollection<T>>::insert(&asset_id, collection_id);344			<CollectionToForeignAsset<T>>::insert(collection_id, asset_id);345			weight = weight.saturating_add(T::DbWeight::get().writes(2));346347			let migrate_tokens_weight = Self::migrate_tokens(&fwd_asset_id, collection_id);348			weight = weight.saturating_add(migrate_tokens_weight);349		}350351		weight352	}353354	fn migrate_tokens(asset_id: &staging_xcm::v3::AssetId, collection_id: CollectionId) -> Weight {355		use MigrationStatus::*;356		use MigrationStatusV3ToV4::*;357358		let mut weight = Weight::zero();359360		for (fwd_asset_instance, token_id) in361			v3_storage::ForeignReserveAssetInstanceToTokenId::<T>::drain_prefix(collection_id)362		{363			let bwd_asset_instance = v3_storage::TokenIdToForeignReserveAssetInstance::<T>::take(364				collection_id,365				token_id,366			);367			weight = weight.saturating_add(T::DbWeight::get().reads(2));368369			let Some(bwd_asset_instance) = bwd_asset_instance else {370				Self::deposit_event(Event::<T>::MigrationStatus(V3ToV4(371					SkippedInconsistentAssetInstanceData {372						asset_id: *asset_id,373						asset_instance: fwd_asset_instance,374					},375				)));376377				weight = weight.saturating_add(T::DbWeight::get().writes(1));378				continue;379			};380381			if fwd_asset_instance != bwd_asset_instance {382				Self::deposit_event(Event::<T>::MigrationStatus(V3ToV4(383					SkippedInconsistentAssetInstanceData {384						asset_id: *asset_id,385						asset_instance: fwd_asset_instance,386					},387				)));388389				weight = weight.saturating_add(T::DbWeight::get().writes(1));390				continue;391			}392393			let Ok(asset_instance) = staging_xcm::v4::AssetInstance::try_from(fwd_asset_instance)394			else {395				Self::deposit_event(Event::<T>::MigrationStatus(V3ToV4(396					SkippedNotConvertibleAssetInstance {397						asset_id: *asset_id,398						asset_instance: fwd_asset_instance,399					},400				)));401402				weight = weight.saturating_add(T::DbWeight::get().writes(1));403				continue;404			};405406			<ForeignReserveAssetInstanceToTokenId<T>>::insert(407				collection_id,408				&asset_instance,409				token_id,410			);411			<TokenIdToForeignReserveAssetInstance<T>>::insert(412				collection_id,413				token_id,414				asset_instance,415			);416			weight = weight.saturating_add(T::DbWeight::get().writes(2));417		}418419		weight420	}421422	fn pallet_account() -> T::CrossAccountId {423		let owner: T::AccountId = T::PalletId::get().into_account_truncating();424		T::CrossAccountId::from_sub(owner)425	}426427	/// Converts a concrete asset ID (the asset multilocation) to a local collection on Unique Network.428	///429	/// The multilocation corresponds to a local collection if:430	/// * It is `Here` location that corresponds to the native token of this parachain.431	/// * It is `../Parachain(<Unique Network Para ID>)` that also corresponds to the native token of this parachain.432	/// * It is `../Parachain(<Unique Network Para ID>)/GeneralIndex(<Collection ID>)` that corresponds433	/// to the collection with the ID equal to `<Collection ID>`. The `<Collection ID>` must be in the valid range,434	/// otherwise `None` is returned.435	/// * It is `GeneralIndex(<Collection ID>)`. Same as the last one above.436	///437	/// If the multilocation doesn't match the patterns listed above,438	/// or the `<Collection ID>` points to a foreign collection,439	/// `None` is returned, identifying that the given multilocation doesn't correspond to a local collection.440	fn local_asset_id_to_collection(441		AssetId(asset_location): &AssetId,442	) -> Option<CollectionLocality> {443		let self_location = T::SelfLocation::get();444445		if *asset_location == Here.into() || *asset_location == self_location {446			return Some(CollectionLocality::Local(NATIVE_FUNGIBLE_COLLECTION_ID));447		}448449		let prefix = if asset_location.parents == 0 {450			&Here451		} else if asset_location.parents == self_location.parents {452			&self_location.interior453		} else {454			return None;455		};456457		let GeneralIndex(collection_id) = asset_location.interior.match_and_split(prefix)? else {458			return None;459		};460461		let collection_id = CollectionId((*collection_id).try_into().ok()?);462463		Self::collection_to_foreign_asset(collection_id)464			.is_none()465			.then_some(CollectionLocality::Local(collection_id))466	}467468	/// Converts an asset ID to a Unique Network's collection locality (either foreign or a local one).469	///470	/// The function will check if the asset's reserve location has the corresponding471	/// foreign collection on Unique Network,472	/// and will return the "foreign" locality containing the collection ID if found.473	///474	/// If no corresponding foreign collection is found, the function will check475	/// if the asset's reserve location corresponds to a local collection.476	/// If the local collection is found, the "local" locality with the collection ID is returned.477	///478	/// If all of the above have failed, the `AssetIdConversionFailed` error will be returned.479	fn asset_to_collection(asset_id: &AssetId) -> Result<CollectionLocality, XcmError> {480		Self::foreign_asset_to_collection(asset_id)481			.map(CollectionLocality::Foreign)482			.or_else(|| Self::local_asset_id_to_collection(asset_id))483			.ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())484	}485486	/// Converts an XCM asset instance of local collection to the Unique Network's token ID.487	///488	/// The asset instance corresponds to the Unique Network's token ID if it is in the following format:489	/// `AssetInstance::Index(<token ID>)`.490	///491	/// If the asset instance is not in the valid format or the `<token ID>` can't fit into the valid token ID,492	/// `None` will be returned.493	///494	/// Note: this function can return `Some` containing the token ID of a non-existing NFT.495	/// It returns `None` when it failed to convert the `asset_instance` to a local ID.496	fn local_asset_instance_to_token_id(asset_instance: &AssetInstance) -> Option<TokenId> {497		match asset_instance {498			AssetInstance::Index(token_id) => Some(TokenId((*token_id).try_into().ok()?)),499			_ => None,500		}501	}502503	/// Obtains the token ID of the `asset_instance` in the collection.504	///505	/// Note: this function can return `Some` containing the token ID of a non-existing NFT.506	/// It returns `None` when it failed to convert the `asset_instance` to a local ID.507	fn asset_instance_to_token_id(508		collection_locality: CollectionLocality,509		asset_instance: &AssetInstance,510	) -> Option<TokenId> {511		match collection_locality {512			CollectionLocality::Local(_) => Self::local_asset_instance_to_token_id(asset_instance),513			CollectionLocality::Foreign(collection_id) => {514				Self::foreign_reserve_asset_instance_to_token_id(collection_id, asset_instance)515			}516		}517	}518519	/// Creates a foreign item in the the collection.520	fn create_foreign_asset_instance(521		xcm_ext: &dyn XcmExtensions<T>,522		collection_id: CollectionId,523		asset_instance: &AssetInstance,524		to: T::CrossAccountId,525	) -> DispatchResult {526		let derivative_token_id = xcm_ext.create_item(527			&Self::pallet_account(),528			to,529			CreateItemData::NFT(Default::default()),530			&ZeroBudget,531		)?;532533		<ForeignReserveAssetInstanceToTokenId<T>>::insert(534			collection_id,535			asset_instance,536			derivative_token_id,537		);538539		<TokenIdToForeignReserveAssetInstance<T>>::insert(540			collection_id,541			derivative_token_id,542			asset_instance,543		);544545		Ok(())546	}547548	/// Deposits an asset instance to the `to` account.549	///550	/// Either transfers an existing item from the pallet's account551	/// or creates a foreign item.552	fn deposit_asset_instance(553		xcm_ext: &dyn XcmExtensions<T>,554		collection_locality: CollectionLocality,555		asset_instance: &AssetInstance,556		to: T::CrossAccountId,557	) -> XcmResult {558		let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance);559560		let deposit_result = match (collection_locality, token_id) {561			(_, Some(token_id)) => {562				let depositor = &Self::pallet_account();563				let from = depositor;564				let amount = 1;565566				xcm_ext.transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)567			}568			(CollectionLocality::Foreign(collection_id), None) => {569				Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)570			}571			(CollectionLocality::Local(_), None) => {572				return Err(XcmExecutorError::InstanceConversionFailed.into());573			}574		};575576		deposit_result577			.map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))578	}579580	/// Withdraws an asset instance from the `from` account.581	///582	/// Transfers the asset instance to the pallet's account.583	fn withdraw_asset_instance(584		xcm_ext: &dyn XcmExtensions<T>,585		collection_locality: CollectionLocality,586		asset_instance: &AssetInstance,587		from: T::CrossAccountId,588	) -> XcmResult {589		let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance)590			.ok_or(XcmExecutorError::InstanceConversionFailed)?;591592		let depositor = &from;593		let to = Self::pallet_account();594		let amount = 1;595		xcm_ext596			.transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)597			.map_err(|_| XcmError::FailedToTransactAsset("non-fungible item withdraw failed"))?;598599		Ok(())600	}601}602603// #[derive()]604// pub enum Migration {605606// }607608impl<T: Config> TransactAsset for Pallet<T> {609	fn can_check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult {610		Err(XcmError::Unimplemented)611	}612613	fn check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) {}614615	fn can_check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult {616		Err(XcmError::Unimplemented)617	}618619	fn check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) {}620621	fn deposit_asset(what: &Asset, to: &Location, _context: Option<&XcmContext>) -> XcmResult {622		let to = T::LocationToAccountId::convert_location(to)623			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;624625		let collection_locality = Self::asset_to_collection(&what.id)?;626		let dispatch = T::CollectionDispatch::dispatch(*collection_locality)627			.map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;628629		let collection = dispatch.as_dyn();630		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;631632		match what.fun {633			Fungibility::Fungible(amount) => xcm_ext634				.create_item(635					&Self::pallet_account(),636					to,637					CreateItemData::Fungible(CreateFungibleData { value: amount }),638					&ZeroBudget,639				)640				.map(|_| ())641				.map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),642643			Fungibility::NonFungible(asset_instance) => {644				Self::deposit_asset_instance(xcm_ext, collection_locality, &asset_instance, to)645			}646		}647	}648649	fn withdraw_asset(650		what: &Asset,651		from: &Location,652		_maybe_context: Option<&XcmContext>,653	) -> Result<AssetsInHolding, XcmError> {654		let from = T::LocationToAccountId::convert_location(from)655			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;656657		let collection_locality = Self::asset_to_collection(&what.id)?;658		let dispatch = T::CollectionDispatch::dispatch(*collection_locality)659			.map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;660661		let collection = dispatch.as_dyn();662		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;663664		match what.fun {665			Fungibility::Fungible(amount) => xcm_ext666				.burn_item(from, TokenId::default(), amount)667				.map_err(|_| XcmError::FailedToTransactAsset("fungible item withdraw failed"))?,668669			Fungibility::NonFungible(asset_instance) => {670				Self::withdraw_asset_instance(xcm_ext, collection_locality, &asset_instance, from)?;671			}672		}673674		Ok(what.clone().into())675	}676677	fn internal_transfer_asset(678		what: &Asset,679		from: &Location,680		to: &Location,681		_context: &XcmContext,682	) -> Result<AssetsInHolding, XcmError> {683		let from = T::LocationToAccountId::convert_location(from)684			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;685686		let to = T::LocationToAccountId::convert_location(to)687			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;688689		let collection_locality = Self::asset_to_collection(&what.id)?;690691		let dispatch = T::CollectionDispatch::dispatch(*collection_locality)692			.map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;693		let collection = dispatch.as_dyn();694		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;695696		let depositor = &from;697698		let token_id;699		let amount;700		let map_error: fn(DispatchError) -> XcmError;701702		match what.fun {703			Fungibility::Fungible(fungible_amount) => {704				token_id = TokenId::default();705				amount = fungible_amount;706				map_error = |_| XcmError::FailedToTransactAsset("fungible item transfer failed");707			}708709			Fungibility::NonFungible(asset_instance) => {710				token_id = Self::asset_instance_to_token_id(collection_locality, &asset_instance)711					.ok_or(XcmExecutorError::InstanceConversionFailed)?;712713				amount = 1;714				map_error = |_| XcmError::FailedToTransactAsset("non-fungible item transfer failed")715			}716		}717718		xcm_ext719			.transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)720			.map_err(map_error)?;721722		Ok(what.clone().into())723	}724}725726#[derive(Clone, Copy)]727pub enum CollectionLocality {728	Local(CollectionId),729	Foreign(CollectionId),730}731732impl Deref for CollectionLocality {733	type Target = CollectionId;734735	fn deref(&self) -> &Self::Target {736		match self {737			Self::Local(id) => id,738			Self::Foreign(id) => id,739		}740	}741}742743pub struct CurrencyIdConvert<T: Config>(PhantomData<T>);744impl<T: Config> sp_runtime::traits::Convert<CollectionId, Option<Location>>745	for CurrencyIdConvert<T>746{747	fn convert(collection_id: CollectionId) -> Option<Location> {748		if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {749			Some(T::SelfLocation::get())750		} else {751			<Pallet<T>>::collection_to_foreign_asset(collection_id)752				.map(|AssetId(location)| location)753				.or_else(|| {754					T::SelfLocation::get()755						.pushed_with_interior(GeneralIndex(collection_id.0.into()))756						.ok()757				})758		}759	}760}761762#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]763pub enum ForeignCollectionMode {764	NFT,765	Fungible(u8),766}767768impl From<ForeignCollectionMode> for CollectionMode {769	fn from(value: ForeignCollectionMode) -> Self {770		match value {771			ForeignCollectionMode::NFT => Self::NFT,772			ForeignCollectionMode::Fungible(decimals) => Self::Fungible(decimals),773		}774	}775}776777pub struct FreeForAll;778779impl WeightTrader for FreeForAll {780	fn new() -> Self {781		Self782	}783784	fn buy_weight(785		&mut self,786		weight: Weight,787		payment: AssetsInHolding,788		_xcm: &XcmContext,789	) -> Result<AssetsInHolding, XcmError> {790		log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);791		Ok(payment)792	}793}