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

difftreelog

fix(foreign-assets) put storage version in genesis config

Daniel Shiposha2024-06-03parent: #eca09a2.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, 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}
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 frame_support::traits::BuildGenesisConfig;93	use pallet_common::CollectionIssuer;94	use up_data_structs::CollectionDescription;9596	use super::*;9798	#[pallet::config]99	pub trait Config:100		frame_system::Config101		+ pallet_common::Config102		+ pallet_fungible::Config103		+ pallet_balances::Config104	{105		/// The overarching event type.106		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;107108		/// Origin for force registering of a foreign asset.109		type ForceRegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;110111		/// The ID of the foreign assets pallet.112		type PalletId: Get<PalletId>;113114		/// Self-location of this parachain.115		type SelfLocation: Get<Location>;116117		/// The converter from a Location to a CrossAccountId.118		type LocationToAccountId: ConvertLocation<Self::CrossAccountId>;119120		/// Weight information for the extrinsics in this module.121		type WeightInfo: WeightInfo;122	}123124	#[pallet::error]125	pub enum Error<T> {126		/// The foreign asset is already registered.127		ForeignAssetAlreadyRegistered,128129		/// The given asset ID could not be converted into the current XCM version.130		BadForeignAssetId,131	}132133	#[pallet::event]134	#[pallet::generate_deposit(pub(crate) fn deposit_event)]135	pub enum Event<T: Config> {136		/// The foreign asset registered.137		ForeignAssetRegistered {138			collection_id: CollectionId,139			asset_id: Box<VersionedAssetId>,140		},141142		/// The migration status.143		MigrationStatus(MigrationStatus),144	}145146	/// The corresponding collections of foreign assets.147	#[pallet::storage]148	#[pallet::getter(fn foreign_asset_to_collection)]149	pub type ForeignAssetToCollection<T: Config> =150		StorageMap<_, Blake2_128Concat, staging_xcm::v4::AssetId, CollectionId, OptionQuery>;151152	/// The corresponding foreign assets of collections.153	#[pallet::storage]154	#[pallet::getter(fn collection_to_foreign_asset)]155	pub type CollectionToForeignAsset<T: Config> =156		StorageMap<_, Blake2_128Concat, CollectionId, staging_xcm::v4::AssetId, OptionQuery>;157158	/// The correponding NFT token id of reserve NFTs159	#[pallet::storage]160	#[pallet::getter(fn foreign_reserve_asset_instance_to_token_id)]161	pub type ForeignReserveAssetInstanceToTokenId<T: Config> = StorageDoubleMap<162		Hasher1 = Blake2_128Concat,163		Key1 = CollectionId,164		Hasher2 = Blake2_128Concat,165		Key2 = staging_xcm::v4::AssetInstance,166		Value = TokenId,167		QueryKind = OptionQuery,168	>;169170	/// The correponding reserve NFT of a token ID171	#[pallet::storage]172	#[pallet::getter(fn token_id_to_foreign_reserve_asset_instance)]173	pub type TokenIdToForeignReserveAssetInstance<T: Config> = StorageDoubleMap<174		Hasher1 = Blake2_128Concat,175		Key1 = CollectionId,176		Hasher2 = Blake2_128Concat,177		Key2 = TokenId,178		Value = staging_xcm::v4::AssetInstance,179		QueryKind = OptionQuery,180	>;181182	const STORAGE_VERSION: StorageVersion = StorageVersion::new(staging_xcm::v4::VERSION as u16);183184	#[pallet::pallet]185	#[pallet::storage_version(STORAGE_VERSION)]186	pub struct Pallet<T>(_);187188	#[pallet::call]189	impl<T: Config> Pallet<T> {190		#[pallet::call_index(0)]191		#[pallet::weight(<T as Config>::WeightInfo::force_register_foreign_asset())]192		pub fn force_register_foreign_asset(193			origin: OriginFor<T>,194			versioned_asset_id: Box<VersionedAssetId>,195			name: CollectionName,196			token_prefix: CollectionTokenPrefix,197			mode: ForeignCollectionMode,198		) -> DispatchResult {199			T::ForceRegisterOrigin::ensure_origin(origin.clone())?;200201			let asset_id: AssetId = versioned_asset_id202				.as_ref()203				.clone()204				.try_into()205				.map_err(|()| Error::<T>::BadForeignAssetId)?;206207			ensure!(208				!<ForeignAssetToCollection<T>>::contains_key(&asset_id),209				<Error<T>>::ForeignAssetAlreadyRegistered,210			);211212			let foreign_collection_owner = Self::pallet_account();213214			let description: CollectionDescription = "Foreign Assets Collection"215				.encode_utf16()216				.collect::<Vec<_>>()217				.try_into()218				.expect("description length < max description length; qed");219220			let collection_id = T::CollectionDispatch::create(221				foreign_collection_owner,222				CollectionIssuer::Internals,223				CreateCollectionData {224					name,225					token_prefix,226					description,227					mode: mode.into(),228					flags: CollectionFlags {229						foreign: true,230						..Default::default()231					},232					..Default::default()233				},234			)?;235236			<ForeignAssetToCollection<T>>::insert(&asset_id, collection_id);237			<CollectionToForeignAsset<T>>::insert(collection_id, asset_id);238239			Self::deposit_event(Event::<T>::ForeignAssetRegistered {240				collection_id,241				asset_id: versioned_asset_id,242			});243244			Ok(())245		}246	}247248	#[pallet::genesis_config]249	#[derive(Default)]250	pub struct GenesisConfig<T: Config>(PhantomData<T>);251252	#[pallet::genesis_build]253	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {254		fn build(&self) {255			<Pallet<T>>::in_code_storage_version().put::<Pallet<T>>();256		}257	}258259	#[pallet::hooks]260	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {261		fn on_runtime_upgrade() -> Weight {262			if Self::on_chain_storage_version() < staging_xcm::v4::VERSION as u16 {263				let put_version_weight = T::DbWeight::get().writes(1);264				let fix_foreign_flag_weight = Self::fix_foreign_flag();265				let weight_v3_to_v4 = Self::migrate_v3_to_v4();266267				Self::in_code_storage_version().put::<Self>();268269				put_version_weight270					.saturating_add(fix_foreign_flag_weight)271					.saturating_add(weight_v3_to_v4)272			} else {273				Weight::zero()274			}275		}276	}277}278279mod v3_storage {280	use super::*;281282	#[storage_alias]283	pub type ForeignAssetToCollection<T: Config> =284		StorageMap<Pallet<T>, Twox64Concat, staging_xcm::v3::AssetId, CollectionId, OptionQuery>;285286	#[storage_alias]287	pub type CollectionToForeignAsset<T: Config> =288		StorageMap<Pallet<T>, Twox64Concat, CollectionId, staging_xcm::v3::AssetId, OptionQuery>;289290	#[storage_alias]291	pub type ForeignReserveAssetInstanceToTokenId<T: Config> = StorageDoubleMap<292		Pallet<T>,293		Twox64Concat,294		CollectionId,295		Blake2_128Concat,296		staging_xcm::v3::AssetInstance,297		TokenId,298		OptionQuery,299	>;300301	#[storage_alias]302	pub type TokenIdToForeignReserveAssetInstance<T: Config> = StorageDoubleMap<303		Pallet<T>,304		Twox64Concat,305		CollectionId,306		Blake2_128Concat,307		TokenId,308		staging_xcm::v3::AssetInstance,309		OptionQuery,310	>;311}312313impl<T: Config> Pallet<T> {314	fn fix_foreign_flag() -> Weight {315		let mut weight = Weight::zero();316317		for (_, collection_id) in v3_storage::ForeignAssetToCollection::<T>::iter() {318			pallet_common::CollectionById::<T>::mutate(collection_id, |collection| {319				if let Some(collection) = collection {320					collection.flags.foreign = true;321				}322			});323324			weight = weight.saturating_add(T::DbWeight::get().reads_writes(2, 1));325		}326327		weight328	}329330	fn migrate_v3_to_v4() -> Weight {331		let event_weight = T::DbWeight::get().writes(1);332		let collection_migration_weight = Self::migrate_collections();333334		Self::deposit_event(Event::<T>::MigrationStatus(MigrationStatus::V3ToV4(335			MigrationStatusV3ToV4::Done,336		)));337338		collection_migration_weight.saturating_add(event_weight)339	}340341	fn migrate_collections() -> Weight {342		use MigrationStatus::*;343		use MigrationStatusV3ToV4::*;344345		let mut weight = Weight::zero();346347		for (fwd_asset_id, collection_id) in v3_storage::ForeignAssetToCollection::<T>::drain() {348			let bwd_asset_id = v3_storage::CollectionToForeignAsset::<T>::take(collection_id);349			weight = weight.saturating_add(T::DbWeight::get().reads(2));350351			let Some(bwd_asset_id) = bwd_asset_id else {352				Self::deposit_event(Event::<T>::MigrationStatus(V3ToV4(353					SkippedInconsistentAssetData(fwd_asset_id),354				)));355356				weight = weight.saturating_add(T::DbWeight::get().writes(1));357				continue;358			};359360			if fwd_asset_id != bwd_asset_id {361				Self::deposit_event(Event::<T>::MigrationStatus(V3ToV4(362					SkippedInconsistentAssetData(fwd_asset_id),363				)));364365				weight = weight.saturating_add(T::DbWeight::get().writes(1));366				continue;367			}368369			let Ok(asset_id) = staging_xcm::v4::AssetId::try_from(fwd_asset_id) else {370				Self::deposit_event(Event::<T>::MigrationStatus(V3ToV4(371					SkippedNotConvertibleAssetId(fwd_asset_id),372				)));373374				weight = weight.saturating_add(T::DbWeight::get().writes(1));375				continue;376			};377378			<ForeignAssetToCollection<T>>::insert(&asset_id, collection_id);379			<CollectionToForeignAsset<T>>::insert(collection_id, asset_id);380			weight = weight.saturating_add(T::DbWeight::get().writes(2));381382			let migrate_tokens_weight = Self::migrate_tokens(&fwd_asset_id, collection_id);383			weight = weight.saturating_add(migrate_tokens_weight);384		}385386		weight387	}388389	fn migrate_tokens(asset_id: &staging_xcm::v3::AssetId, collection_id: CollectionId) -> Weight {390		use MigrationStatus::*;391		use MigrationStatusV3ToV4::*;392393		let mut weight = Weight::zero();394395		for (fwd_asset_instance, token_id) in396			v3_storage::ForeignReserveAssetInstanceToTokenId::<T>::drain_prefix(collection_id)397		{398			let bwd_asset_instance = v3_storage::TokenIdToForeignReserveAssetInstance::<T>::take(399				collection_id,400				token_id,401			);402			weight = weight.saturating_add(T::DbWeight::get().reads(2));403404			let Some(bwd_asset_instance) = bwd_asset_instance else {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			if fwd_asset_instance != bwd_asset_instance {417				Self::deposit_event(Event::<T>::MigrationStatus(V3ToV4(418					SkippedInconsistentAssetInstanceData {419						asset_id: *asset_id,420						asset_instance: fwd_asset_instance,421					},422				)));423424				weight = weight.saturating_add(T::DbWeight::get().writes(1));425				continue;426			}427428			let Ok(asset_instance) = staging_xcm::v4::AssetInstance::try_from(fwd_asset_instance)429			else {430				Self::deposit_event(Event::<T>::MigrationStatus(V3ToV4(431					SkippedNotConvertibleAssetInstance {432						asset_id: *asset_id,433						asset_instance: fwd_asset_instance,434					},435				)));436437				weight = weight.saturating_add(T::DbWeight::get().writes(1));438				continue;439			};440441			<ForeignReserveAssetInstanceToTokenId<T>>::insert(442				collection_id,443				&asset_instance,444				token_id,445			);446			<TokenIdToForeignReserveAssetInstance<T>>::insert(447				collection_id,448				token_id,449				asset_instance,450			);451			weight = weight.saturating_add(T::DbWeight::get().writes(2));452		}453454		weight455	}456457	fn pallet_account() -> T::CrossAccountId {458		let owner: T::AccountId = T::PalletId::get().into_account_truncating();459		T::CrossAccountId::from_sub(owner)460	}461462	/// Converts a concrete asset ID (the asset multilocation) to a local collection on Unique Network.463	///464	/// The multilocation corresponds to a local collection if:465	/// * It is `Here` location that corresponds to the native token of this parachain.466	/// * It is `../Parachain(<Unique Network Para ID>)` that also corresponds to the native token of this parachain.467	/// * It is `../Parachain(<Unique Network Para ID>)/GeneralIndex(<Collection ID>)` that corresponds468	/// to the collection with the ID equal to `<Collection ID>`. The `<Collection ID>` must be in the valid range,469	/// otherwise `None` is returned.470	/// * It is `GeneralIndex(<Collection ID>)`. Same as the last one above.471	///472	/// If the multilocation doesn't match the patterns listed above,473	/// or the `<Collection ID>` points to a foreign collection,474	/// `None` is returned, identifying that the given multilocation doesn't correspond to a local collection.475	fn local_asset_id_to_collection(476		AssetId(asset_location): &AssetId,477	) -> Option<CollectionLocality> {478		let self_location = T::SelfLocation::get();479480		if *asset_location == Here.into() || *asset_location == self_location {481			return Some(CollectionLocality::Local(NATIVE_FUNGIBLE_COLLECTION_ID));482		}483484		let prefix = if asset_location.parents == 0 {485			&Here486		} else if asset_location.parents == self_location.parents {487			&self_location.interior488		} else {489			return None;490		};491492		let GeneralIndex(collection_id) = asset_location.interior.match_and_split(prefix)? else {493			return None;494		};495496		let collection_id = CollectionId((*collection_id).try_into().ok()?);497498		Self::collection_to_foreign_asset(collection_id)499			.is_none()500			.then_some(CollectionLocality::Local(collection_id))501	}502503	/// Converts an asset ID to a Unique Network's collection locality (either foreign or a local one).504	///505	/// The function will check if the asset's reserve location has the corresponding506	/// foreign collection on Unique Network,507	/// and will return the "foreign" locality containing the collection ID if found.508	///509	/// If no corresponding foreign collection is found, the function will check510	/// if the asset's reserve location corresponds to a local collection.511	/// If the local collection is found, the "local" locality with the collection ID is returned.512	///513	/// If all of the above have failed, the `AssetIdConversionFailed` error will be returned.514	fn asset_to_collection(asset_id: &AssetId) -> Result<CollectionLocality, XcmError> {515		Self::foreign_asset_to_collection(asset_id)516			.map(CollectionLocality::Foreign)517			.or_else(|| Self::local_asset_id_to_collection(asset_id))518			.ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())519	}520521	/// Converts an XCM asset instance of local collection to the Unique Network's token ID.522	///523	/// The asset instance corresponds to the Unique Network's token ID if it is in the following format:524	/// `AssetInstance::Index(<token ID>)`.525	///526	/// If the asset instance is not in the valid format or the `<token ID>` can't fit into the valid token ID,527	/// `None` will be returned.528	///529	/// Note: this function can return `Some` containing the token ID of a non-existing NFT.530	/// It returns `None` when it failed to convert the `asset_instance` to a local ID.531	fn local_asset_instance_to_token_id(asset_instance: &AssetInstance) -> Option<TokenId> {532		match asset_instance {533			AssetInstance::Index(token_id) => Some(TokenId((*token_id).try_into().ok()?)),534			_ => None,535		}536	}537538	/// Obtains the token ID of the `asset_instance` in the collection.539	///540	/// Note: this function can return `Some` containing the token ID of a non-existing NFT.541	/// It returns `None` when it failed to convert the `asset_instance` to a local ID.542	fn asset_instance_to_token_id(543		collection_locality: CollectionLocality,544		asset_instance: &AssetInstance,545	) -> Option<TokenId> {546		match collection_locality {547			CollectionLocality::Local(_) => Self::local_asset_instance_to_token_id(asset_instance),548			CollectionLocality::Foreign(collection_id) => {549				Self::foreign_reserve_asset_instance_to_token_id(collection_id, asset_instance)550			}551		}552	}553554	/// Creates a foreign item in the the collection.555	fn create_foreign_asset_instance(556		xcm_ext: &dyn XcmExtensions<T>,557		collection_id: CollectionId,558		asset_instance: &AssetInstance,559		to: T::CrossAccountId,560	) -> DispatchResult {561		let derivative_token_id = xcm_ext.create_item(562			&Self::pallet_account(),563			to,564			CreateItemData::NFT(Default::default()),565			&ZeroBudget,566		)?;567568		<ForeignReserveAssetInstanceToTokenId<T>>::insert(569			collection_id,570			asset_instance,571			derivative_token_id,572		);573574		<TokenIdToForeignReserveAssetInstance<T>>::insert(575			collection_id,576			derivative_token_id,577			asset_instance,578		);579580		Ok(())581	}582583	/// Deposits an asset instance to the `to` account.584	///585	/// Either transfers an existing item from the pallet's account586	/// or creates a foreign item.587	fn deposit_asset_instance(588		xcm_ext: &dyn XcmExtensions<T>,589		collection_locality: CollectionLocality,590		asset_instance: &AssetInstance,591		to: T::CrossAccountId,592	) -> XcmResult {593		let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance);594595		let deposit_result = match (collection_locality, token_id) {596			(_, Some(token_id)) => {597				let depositor = &Self::pallet_account();598				let from = depositor;599				let amount = 1;600601				xcm_ext.transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)602			}603			(CollectionLocality::Foreign(collection_id), None) => {604				Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)605			}606			(CollectionLocality::Local(_), None) => {607				return Err(XcmExecutorError::InstanceConversionFailed.into());608			}609		};610611		deposit_result612			.map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))613	}614615	/// Withdraws an asset instance from the `from` account.616	///617	/// Transfers the asset instance to the pallet's account.618	fn withdraw_asset_instance(619		xcm_ext: &dyn XcmExtensions<T>,620		collection_locality: CollectionLocality,621		asset_instance: &AssetInstance,622		from: T::CrossAccountId,623	) -> XcmResult {624		let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance)625			.ok_or(XcmExecutorError::InstanceConversionFailed)?;626627		let depositor = &from;628		let to = Self::pallet_account();629		let amount = 1;630		xcm_ext631			.transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)632			.map_err(|_| XcmError::FailedToTransactAsset("non-fungible item withdraw failed"))?;633634		Ok(())635	}636}637638// #[derive()]639// pub enum Migration {640641// }642643impl<T: Config> TransactAsset for Pallet<T> {644	fn can_check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult {645		Err(XcmError::Unimplemented)646	}647648	fn check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) {}649650	fn can_check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult {651		Err(XcmError::Unimplemented)652	}653654	fn check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) {}655656	fn deposit_asset(what: &Asset, to: &Location, _context: Option<&XcmContext>) -> XcmResult {657		let to = T::LocationToAccountId::convert_location(to)658			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;659660		let collection_locality = Self::asset_to_collection(&what.id)?;661		let dispatch = T::CollectionDispatch::dispatch(*collection_locality)662			.map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;663664		let collection = dispatch.as_dyn();665		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;666667		match what.fun {668			Fungibility::Fungible(amount) => xcm_ext669				.create_item(670					&Self::pallet_account(),671					to,672					CreateItemData::Fungible(CreateFungibleData { value: amount }),673					&ZeroBudget,674				)675				.map(|_| ())676				.map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),677678			Fungibility::NonFungible(asset_instance) => {679				Self::deposit_asset_instance(xcm_ext, collection_locality, &asset_instance, to)680			}681		}682	}683684	fn withdraw_asset(685		what: &Asset,686		from: &Location,687		_maybe_context: Option<&XcmContext>,688	) -> Result<AssetsInHolding, XcmError> {689		let from = T::LocationToAccountId::convert_location(from)690			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;691692		let collection_locality = Self::asset_to_collection(&what.id)?;693		let dispatch = T::CollectionDispatch::dispatch(*collection_locality)694			.map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;695696		let collection = dispatch.as_dyn();697		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;698699		match what.fun {700			Fungibility::Fungible(amount) => xcm_ext701				.burn_item(from, TokenId::default(), amount)702				.map_err(|_| XcmError::FailedToTransactAsset("fungible item withdraw failed"))?,703704			Fungibility::NonFungible(asset_instance) => {705				Self::withdraw_asset_instance(xcm_ext, collection_locality, &asset_instance, from)?;706			}707		}708709		Ok(what.clone().into())710	}711712	fn internal_transfer_asset(713		what: &Asset,714		from: &Location,715		to: &Location,716		_context: &XcmContext,717	) -> Result<AssetsInHolding, XcmError> {718		let from = T::LocationToAccountId::convert_location(from)719			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;720721		let to = T::LocationToAccountId::convert_location(to)722			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;723724		let collection_locality = Self::asset_to_collection(&what.id)?;725726		let dispatch = T::CollectionDispatch::dispatch(*collection_locality)727			.map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;728		let collection = dispatch.as_dyn();729		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;730731		let depositor = &from;732733		let token_id;734		let amount;735		let map_error: fn(DispatchError) -> XcmError;736737		match what.fun {738			Fungibility::Fungible(fungible_amount) => {739				token_id = TokenId::default();740				amount = fungible_amount;741				map_error = |_| XcmError::FailedToTransactAsset("fungible item transfer failed");742			}743744			Fungibility::NonFungible(asset_instance) => {745				token_id = Self::asset_instance_to_token_id(collection_locality, &asset_instance)746					.ok_or(XcmExecutorError::InstanceConversionFailed)?;747748				amount = 1;749				map_error = |_| XcmError::FailedToTransactAsset("non-fungible item transfer failed")750			}751		}752753		xcm_ext754			.transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)755			.map_err(map_error)?;756757		Ok(what.clone().into())758	}759}760761#[derive(Clone, Copy)]762pub enum CollectionLocality {763	Local(CollectionId),764	Foreign(CollectionId),765}766767impl Deref for CollectionLocality {768	type Target = CollectionId;769770	fn deref(&self) -> &Self::Target {771		match self {772			Self::Local(id) => id,773			Self::Foreign(id) => id,774		}775	}776}777778pub struct CurrencyIdConvert<T: Config>(PhantomData<T>);779impl<T: Config> sp_runtime::traits::Convert<CollectionId, Option<Location>>780	for CurrencyIdConvert<T>781{782	fn convert(collection_id: CollectionId) -> Option<Location> {783		if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {784			Some(T::SelfLocation::get())785		} else {786			<Pallet<T>>::collection_to_foreign_asset(collection_id)787				.map(|AssetId(location)| location)788				.or_else(|| {789					T::SelfLocation::get()790						.pushed_with_interior(GeneralIndex(collection_id.0.into()))791						.ok()792				})793		}794	}795}796797#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]798pub enum ForeignCollectionMode {799	NFT,800	Fungible(u8),801}802803impl From<ForeignCollectionMode> for CollectionMode {804	fn from(value: ForeignCollectionMode) -> Self {805		match value {806			ForeignCollectionMode::NFT => Self::NFT,807			ForeignCollectionMode::Fungible(decimals) => Self::Fungible(decimals),808		}809	}810}811812pub struct FreeForAll;813814impl WeightTrader for FreeForAll {815	fn new() -> Self {816		Self817	}818819	fn buy_weight(820		&mut self,821		weight: Weight,822		payment: AssetsInHolding,823		_xcm: &XcmContext,824	) -> Result<AssetsInHolding, XcmError> {825		log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);826		Ok(payment)827	}828}