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

difftreelog

fix foreign flags

Daniel Shiposha2024-05-28parent: #be35f72.patch.diff
in: master

1 file changed

modifiedpallets/foreign-assets/src/lib.rsdiffbeforeafterboth
before · pallets/foreign-assets/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//! # 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}
after · pallets/foreign-assets/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//! # 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, CollectionFlags, CollectionId, CollectionMode, CollectionName,44	CollectionTokenPrefix, 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					flags: CollectionFlags {228						foreign: true,229						..Default::default()230					},231					..Default::default()232				},233			)?;234235			<ForeignAssetToCollection<T>>::insert(&asset_id, collection_id);236			<CollectionToForeignAsset<T>>::insert(collection_id, asset_id);237238			Self::deposit_event(Event::<T>::ForeignAssetRegistered {239				collection_id,240				asset_id: versioned_asset_id,241			});242243			Ok(())244		}245	}246247	#[pallet::hooks]248	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {249		fn on_runtime_upgrade() -> Weight {250			if Self::on_chain_storage_version() < staging_xcm::v4::VERSION as u16 {251				let put_version_weight = T::DbWeight::get().writes(1);252				let fix_foreign_flag_weight = Self::fix_foreign_flag();253				let weight_v3_to_v4 = Self::migrate_v3_to_v4();254255				StorageVersion::new(staging_xcm::v4::VERSION as u16).put::<Self>();256257				put_version_weight258					.saturating_add(fix_foreign_flag_weight)259					.saturating_add(weight_v3_to_v4)260			} else {261				Weight::zero()262			}263		}264	}265}266267mod v3_storage {268	use super::*;269270	#[storage_alias]271	pub type ForeignAssetToCollection<T: Config> =272		StorageMap<Pallet<T>, Twox64Concat, staging_xcm::v3::AssetId, CollectionId, OptionQuery>;273274	#[storage_alias]275	pub type CollectionToForeignAsset<T: Config> =276		StorageMap<Pallet<T>, Twox64Concat, CollectionId, staging_xcm::v3::AssetId, OptionQuery>;277278	#[storage_alias]279	pub type ForeignReserveAssetInstanceToTokenId<T: Config> = StorageDoubleMap<280		Pallet<T>,281		Twox64Concat,282		CollectionId,283		Blake2_128Concat,284		staging_xcm::v3::AssetInstance,285		TokenId,286		OptionQuery,287	>;288289	#[storage_alias]290	pub type TokenIdToForeignReserveAssetInstance<T: Config> = StorageDoubleMap<291		Pallet<T>,292		Twox64Concat,293		CollectionId,294		Blake2_128Concat,295		TokenId,296		staging_xcm::v3::AssetInstance,297		OptionQuery,298	>;299}300301impl<T: Config> Pallet<T> {302	fn fix_foreign_flag() -> Weight {303		let mut weight = Weight::zero();304305		for (_, collection_id) in v3_storage::ForeignAssetToCollection::<T>::iter() {306			pallet_common::CollectionById::<T>::mutate(collection_id, |collection| {307				if let Some(collection) = collection {308					collection.flags.foreign = true;309				}310			});311312			weight = weight.saturating_add(T::DbWeight::get().reads_writes(2, 1));313		}314315		weight316	}317318	fn migrate_v3_to_v4() -> Weight {319		let event_weight = T::DbWeight::get().writes(1);320		let collection_migration_weight = Self::migrate_collections();321322		Self::deposit_event(Event::<T>::MigrationStatus(MigrationStatus::V3ToV4(323			MigrationStatusV3ToV4::Done,324		)));325326		collection_migration_weight.saturating_add(event_weight)327	}328329	fn migrate_collections() -> Weight {330		use MigrationStatus::*;331		use MigrationStatusV3ToV4::*;332333		let mut weight = Weight::zero();334335		for (fwd_asset_id, collection_id) in v3_storage::ForeignAssetToCollection::<T>::drain() {336			let bwd_asset_id = v3_storage::CollectionToForeignAsset::<T>::take(collection_id);337			weight = weight.saturating_add(T::DbWeight::get().reads(2));338339			let Some(bwd_asset_id) = bwd_asset_id else {340				Self::deposit_event(Event::<T>::MigrationStatus(V3ToV4(341					SkippedInconsistentAssetData(fwd_asset_id),342				)));343344				weight = weight.saturating_add(T::DbWeight::get().writes(1));345				continue;346			};347348			if fwd_asset_id != bwd_asset_id {349				Self::deposit_event(Event::<T>::MigrationStatus(V3ToV4(350					SkippedInconsistentAssetData(fwd_asset_id),351				)));352353				weight = weight.saturating_add(T::DbWeight::get().writes(1));354				continue;355			}356357			let Ok(asset_id) = staging_xcm::v4::AssetId::try_from(fwd_asset_id) else {358				Self::deposit_event(Event::<T>::MigrationStatus(V3ToV4(359					SkippedNotConvertibleAssetId(fwd_asset_id),360				)));361362				weight = weight.saturating_add(T::DbWeight::get().writes(1));363				continue;364			};365366			<ForeignAssetToCollection<T>>::insert(&asset_id, collection_id);367			<CollectionToForeignAsset<T>>::insert(collection_id, asset_id);368			weight = weight.saturating_add(T::DbWeight::get().writes(2));369370			let migrate_tokens_weight = Self::migrate_tokens(&fwd_asset_id, collection_id);371			weight = weight.saturating_add(migrate_tokens_weight);372		}373374		weight375	}376377	fn migrate_tokens(asset_id: &staging_xcm::v3::AssetId, collection_id: CollectionId) -> Weight {378		use MigrationStatus::*;379		use MigrationStatusV3ToV4::*;380381		let mut weight = Weight::zero();382383		for (fwd_asset_instance, token_id) in384			v3_storage::ForeignReserveAssetInstanceToTokenId::<T>::drain_prefix(collection_id)385		{386			let bwd_asset_instance = v3_storage::TokenIdToForeignReserveAssetInstance::<T>::take(387				collection_id,388				token_id,389			);390			weight = weight.saturating_add(T::DbWeight::get().reads(2));391392			let Some(bwd_asset_instance) = bwd_asset_instance else {393				Self::deposit_event(Event::<T>::MigrationStatus(V3ToV4(394					SkippedInconsistentAssetInstanceData {395						asset_id: *asset_id,396						asset_instance: fwd_asset_instance,397					},398				)));399400				weight = weight.saturating_add(T::DbWeight::get().writes(1));401				continue;402			};403404			if fwd_asset_instance != bwd_asset_instance {405				Self::deposit_event(Event::<T>::MigrationStatus(V3ToV4(406					SkippedInconsistentAssetInstanceData {407						asset_id: *asset_id,408						asset_instance: fwd_asset_instance,409					},410				)));411412				weight = weight.saturating_add(T::DbWeight::get().writes(1));413				continue;414			}415416			let Ok(asset_instance) = staging_xcm::v4::AssetInstance::try_from(fwd_asset_instance)417			else {418				Self::deposit_event(Event::<T>::MigrationStatus(V3ToV4(419					SkippedNotConvertibleAssetInstance {420						asset_id: *asset_id,421						asset_instance: fwd_asset_instance,422					},423				)));424425				weight = weight.saturating_add(T::DbWeight::get().writes(1));426				continue;427			};428429			<ForeignReserveAssetInstanceToTokenId<T>>::insert(430				collection_id,431				&asset_instance,432				token_id,433			);434			<TokenIdToForeignReserveAssetInstance<T>>::insert(435				collection_id,436				token_id,437				asset_instance,438			);439			weight = weight.saturating_add(T::DbWeight::get().writes(2));440		}441442		weight443	}444445	fn pallet_account() -> T::CrossAccountId {446		let owner: T::AccountId = T::PalletId::get().into_account_truncating();447		T::CrossAccountId::from_sub(owner)448	}449450	/// Converts a concrete asset ID (the asset multilocation) to a local collection on Unique Network.451	///452	/// The multilocation corresponds to a local collection if:453	/// * It is `Here` location that corresponds to the native token of this parachain.454	/// * It is `../Parachain(<Unique Network Para ID>)` that also corresponds to the native token of this parachain.455	/// * It is `../Parachain(<Unique Network Para ID>)/GeneralIndex(<Collection ID>)` that corresponds456	/// to the collection with the ID equal to `<Collection ID>`. The `<Collection ID>` must be in the valid range,457	/// otherwise `None` is returned.458	/// * It is `GeneralIndex(<Collection ID>)`. Same as the last one above.459	///460	/// If the multilocation doesn't match the patterns listed above,461	/// or the `<Collection ID>` points to a foreign collection,462	/// `None` is returned, identifying that the given multilocation doesn't correspond to a local collection.463	fn local_asset_id_to_collection(464		AssetId(asset_location): &AssetId,465	) -> Option<CollectionLocality> {466		let self_location = T::SelfLocation::get();467468		if *asset_location == Here.into() || *asset_location == self_location {469			return Some(CollectionLocality::Local(NATIVE_FUNGIBLE_COLLECTION_ID));470		}471472		let prefix = if asset_location.parents == 0 {473			&Here474		} else if asset_location.parents == self_location.parents {475			&self_location.interior476		} else {477			return None;478		};479480		let GeneralIndex(collection_id) = asset_location.interior.match_and_split(prefix)? else {481			return None;482		};483484		let collection_id = CollectionId((*collection_id).try_into().ok()?);485486		Self::collection_to_foreign_asset(collection_id)487			.is_none()488			.then_some(CollectionLocality::Local(collection_id))489	}490491	/// Converts an asset ID to a Unique Network's collection locality (either foreign or a local one).492	///493	/// The function will check if the asset's reserve location has the corresponding494	/// foreign collection on Unique Network,495	/// and will return the "foreign" locality containing the collection ID if found.496	///497	/// If no corresponding foreign collection is found, the function will check498	/// if the asset's reserve location corresponds to a local collection.499	/// If the local collection is found, the "local" locality with the collection ID is returned.500	///501	/// If all of the above have failed, the `AssetIdConversionFailed` error will be returned.502	fn asset_to_collection(asset_id: &AssetId) -> Result<CollectionLocality, XcmError> {503		Self::foreign_asset_to_collection(asset_id)504			.map(CollectionLocality::Foreign)505			.or_else(|| Self::local_asset_id_to_collection(asset_id))506			.ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())507	}508509	/// Converts an XCM asset instance of local collection to the Unique Network's token ID.510	///511	/// The asset instance corresponds to the Unique Network's token ID if it is in the following format:512	/// `AssetInstance::Index(<token ID>)`.513	///514	/// If the asset instance is not in the valid format or the `<token ID>` can't fit into the valid token ID,515	/// `None` will be returned.516	///517	/// Note: this function can return `Some` containing the token ID of a non-existing NFT.518	/// It returns `None` when it failed to convert the `asset_instance` to a local ID.519	fn local_asset_instance_to_token_id(asset_instance: &AssetInstance) -> Option<TokenId> {520		match asset_instance {521			AssetInstance::Index(token_id) => Some(TokenId((*token_id).try_into().ok()?)),522			_ => None,523		}524	}525526	/// Obtains the token ID of the `asset_instance` in the collection.527	///528	/// Note: this function can return `Some` containing the token ID of a non-existing NFT.529	/// It returns `None` when it failed to convert the `asset_instance` to a local ID.530	fn asset_instance_to_token_id(531		collection_locality: CollectionLocality,532		asset_instance: &AssetInstance,533	) -> Option<TokenId> {534		match collection_locality {535			CollectionLocality::Local(_) => Self::local_asset_instance_to_token_id(asset_instance),536			CollectionLocality::Foreign(collection_id) => {537				Self::foreign_reserve_asset_instance_to_token_id(collection_id, asset_instance)538			}539		}540	}541542	/// Creates a foreign item in the the collection.543	fn create_foreign_asset_instance(544		xcm_ext: &dyn XcmExtensions<T>,545		collection_id: CollectionId,546		asset_instance: &AssetInstance,547		to: T::CrossAccountId,548	) -> DispatchResult {549		let derivative_token_id = xcm_ext.create_item(550			&Self::pallet_account(),551			to,552			CreateItemData::NFT(Default::default()),553			&ZeroBudget,554		)?;555556		<ForeignReserveAssetInstanceToTokenId<T>>::insert(557			collection_id,558			asset_instance,559			derivative_token_id,560		);561562		<TokenIdToForeignReserveAssetInstance<T>>::insert(563			collection_id,564			derivative_token_id,565			asset_instance,566		);567568		Ok(())569	}570571	/// Deposits an asset instance to the `to` account.572	///573	/// Either transfers an existing item from the pallet's account574	/// or creates a foreign item.575	fn deposit_asset_instance(576		xcm_ext: &dyn XcmExtensions<T>,577		collection_locality: CollectionLocality,578		asset_instance: &AssetInstance,579		to: T::CrossAccountId,580	) -> XcmResult {581		let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance);582583		let deposit_result = match (collection_locality, token_id) {584			(_, Some(token_id)) => {585				let depositor = &Self::pallet_account();586				let from = depositor;587				let amount = 1;588589				xcm_ext.transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)590			}591			(CollectionLocality::Foreign(collection_id), None) => {592				Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)593			}594			(CollectionLocality::Local(_), None) => {595				return Err(XcmExecutorError::InstanceConversionFailed.into());596			}597		};598599		deposit_result600			.map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))601	}602603	/// Withdraws an asset instance from the `from` account.604	///605	/// Transfers the asset instance to the pallet's account.606	fn withdraw_asset_instance(607		xcm_ext: &dyn XcmExtensions<T>,608		collection_locality: CollectionLocality,609		asset_instance: &AssetInstance,610		from: T::CrossAccountId,611	) -> XcmResult {612		let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance)613			.ok_or(XcmExecutorError::InstanceConversionFailed)?;614615		let depositor = &from;616		let to = Self::pallet_account();617		let amount = 1;618		xcm_ext619			.transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)620			.map_err(|_| XcmError::FailedToTransactAsset("non-fungible item withdraw failed"))?;621622		Ok(())623	}624}625626// #[derive()]627// pub enum Migration {628629// }630631impl<T: Config> TransactAsset for Pallet<T> {632	fn can_check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult {633		Err(XcmError::Unimplemented)634	}635636	fn check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) {}637638	fn can_check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult {639		Err(XcmError::Unimplemented)640	}641642	fn check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) {}643644	fn deposit_asset(what: &Asset, to: &Location, _context: Option<&XcmContext>) -> XcmResult {645		let to = T::LocationToAccountId::convert_location(to)646			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;647648		let collection_locality = Self::asset_to_collection(&what.id)?;649		let dispatch = T::CollectionDispatch::dispatch(*collection_locality)650			.map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;651652		let collection = dispatch.as_dyn();653		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;654655		match what.fun {656			Fungibility::Fungible(amount) => xcm_ext657				.create_item(658					&Self::pallet_account(),659					to,660					CreateItemData::Fungible(CreateFungibleData { value: amount }),661					&ZeroBudget,662				)663				.map(|_| ())664				.map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),665666			Fungibility::NonFungible(asset_instance) => {667				Self::deposit_asset_instance(xcm_ext, collection_locality, &asset_instance, to)668			}669		}670	}671672	fn withdraw_asset(673		what: &Asset,674		from: &Location,675		_maybe_context: Option<&XcmContext>,676	) -> Result<AssetsInHolding, XcmError> {677		let from = T::LocationToAccountId::convert_location(from)678			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;679680		let collection_locality = Self::asset_to_collection(&what.id)?;681		let dispatch = T::CollectionDispatch::dispatch(*collection_locality)682			.map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;683684		let collection = dispatch.as_dyn();685		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;686687		match what.fun {688			Fungibility::Fungible(amount) => xcm_ext689				.burn_item(from, TokenId::default(), amount)690				.map_err(|_| XcmError::FailedToTransactAsset("fungible item withdraw failed"))?,691692			Fungibility::NonFungible(asset_instance) => {693				Self::withdraw_asset_instance(xcm_ext, collection_locality, &asset_instance, from)?;694			}695		}696697		Ok(what.clone().into())698	}699700	fn internal_transfer_asset(701		what: &Asset,702		from: &Location,703		to: &Location,704		_context: &XcmContext,705	) -> Result<AssetsInHolding, XcmError> {706		let from = T::LocationToAccountId::convert_location(from)707			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;708709		let to = T::LocationToAccountId::convert_location(to)710			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;711712		let collection_locality = Self::asset_to_collection(&what.id)?;713714		let dispatch = T::CollectionDispatch::dispatch(*collection_locality)715			.map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;716		let collection = dispatch.as_dyn();717		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;718719		let depositor = &from;720721		let token_id;722		let amount;723		let map_error: fn(DispatchError) -> XcmError;724725		match what.fun {726			Fungibility::Fungible(fungible_amount) => {727				token_id = TokenId::default();728				amount = fungible_amount;729				map_error = |_| XcmError::FailedToTransactAsset("fungible item transfer failed");730			}731732			Fungibility::NonFungible(asset_instance) => {733				token_id = Self::asset_instance_to_token_id(collection_locality, &asset_instance)734					.ok_or(XcmExecutorError::InstanceConversionFailed)?;735736				amount = 1;737				map_error = |_| XcmError::FailedToTransactAsset("non-fungible item transfer failed")738			}739		}740741		xcm_ext742			.transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)743			.map_err(map_error)?;744745		Ok(what.clone().into())746	}747}748749#[derive(Clone, Copy)]750pub enum CollectionLocality {751	Local(CollectionId),752	Foreign(CollectionId),753}754755impl Deref for CollectionLocality {756	type Target = CollectionId;757758	fn deref(&self) -> &Self::Target {759		match self {760			Self::Local(id) => id,761			Self::Foreign(id) => id,762		}763	}764}765766pub struct CurrencyIdConvert<T: Config>(PhantomData<T>);767impl<T: Config> sp_runtime::traits::Convert<CollectionId, Option<Location>>768	for CurrencyIdConvert<T>769{770	fn convert(collection_id: CollectionId) -> Option<Location> {771		if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {772			Some(T::SelfLocation::get())773		} else {774			<Pallet<T>>::collection_to_foreign_asset(collection_id)775				.map(|AssetId(location)| location)776				.or_else(|| {777					T::SelfLocation::get()778						.pushed_with_interior(GeneralIndex(collection_id.0.into()))779						.ok()780				})781		}782	}783}784785#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]786pub enum ForeignCollectionMode {787	NFT,788	Fungible(u8),789}790791impl From<ForeignCollectionMode> for CollectionMode {792	fn from(value: ForeignCollectionMode) -> Self {793		match value {794			ForeignCollectionMode::NFT => Self::NFT,795			ForeignCollectionMode::Fungible(decimals) => Self::Fungible(decimals),796		}797	}798}799800pub struct FreeForAll;801802impl WeightTrader for FreeForAll {803	fn new() -> Self {804		Self805	}806807	fn buy_weight(808		&mut self,809		weight: Weight,810		payment: AssetsInHolding,811		_xcm: &XcmContext,812	) -> Result<AssetsInHolding, XcmError> {813		log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);814		Ok(payment)815	}816}