git.delta.rocks / unique-network / refs/commits / 871c90fdbf87

difftreelog

chore add note about non-existing NFT in XCM asset-instance convert

Daniel Shiposha2023-11-28parent: #0f77c10.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::{dispatch::DispatchResult, pallet_prelude::*, traits::EnsureOrigin, PalletId};29use frame_system::pallet_prelude::*;30use pallet_common::{31	dispatch::CollectionDispatch, erc::CrossAccountId, XcmExtensions, NATIVE_FUNGIBLE_COLLECTION_ID,32};33use sp_runtime::traits::AccountIdConversion;34use sp_std::{boxed::Box, vec, vec::Vec};35use staging_xcm::{36	opaque::latest::{prelude::XcmError, Weight},37	v3::{prelude::*, MultiAsset, XcmContext},38	VersionedAssetId,39};40use staging_xcm_executor::{41	traits::{ConvertLocation, Error as XcmExecutorError, TransactAsset, WeightTrader},42	Assets,43};44use up_data_structs::{45	budget::ZeroBudget, CollectionId, CollectionMode, CollectionName, CollectionTokenPrefix,46	CreateCollectionData, CreateFungibleData, CreateItemData, TokenId,47};4849pub mod weights;5051#[cfg(feature = "runtime-benchmarks")]52mod benchmarking;5354pub use module::*;55pub use weights::WeightInfo;5657#[frame_support::pallet]58pub mod module {59	use pallet_common::CollectionIssuer;60	use up_data_structs::CollectionDescription;6162	use super::*;6364	#[pallet::config]65	pub trait Config:66		frame_system::Config67		+ pallet_common::Config68		+ pallet_fungible::Config69		+ pallet_balances::Config70	{71		/// The overarching event type.72		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;7374		/// Origin for force registering of a foreign asset.75		type ForceRegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;7677		/// The ID of the foreign assets pallet.78		type PalletId: Get<PalletId>;7980		/// Self-location of this parachain.81		type SelfLocation: Get<MultiLocation>;8283		/// The converter from a MultiLocation to a CrossAccountId.84		type LocationToAccountId: ConvertLocation<Self::CrossAccountId>;8586		/// Weight information for the extrinsics in this module.87		type WeightInfo: WeightInfo;88	}8990	#[pallet::error]91	pub enum Error<T> {92		/// The foreign asset is already registered.93		ForeignAssetAlreadyRegistered,9495		/// The given asset ID could not be converted into the current XCM version.96		BadForeignAssetId,97	}9899	#[pallet::event]100	#[pallet::generate_deposit(fn deposit_event)]101	pub enum Event<T: Config> {102		/// The foreign asset registered.103		ForeignAssetRegistered {104			collection_id: CollectionId,105			asset_id: Box<VersionedAssetId>,106		},107	}108109	/// The corresponding collections of foreign assets.110	#[pallet::storage]111	#[pallet::getter(fn foreign_asset_to_collection)]112	pub type ForeignAssetToCollection<T: Config> =113		StorageMap<_, Twox64Concat, staging_xcm::v3::AssetId, CollectionId, OptionQuery>;114115	/// The corresponding foreign assets of collections.116	#[pallet::storage]117	#[pallet::getter(fn collection_to_foreign_asset)]118	pub type CollectionToForeignAsset<T: Config> =119		StorageMap<_, Twox64Concat, CollectionId, staging_xcm::v3::AssetId, OptionQuery>;120121	/// The correponding NFT token id of reserve NFTs122	#[pallet::storage]123	#[pallet::getter(fn foreign_reserve_asset_instance_to_token_id)]124	pub type ForeignReserveAssetInstanceToTokenId<T: Config> = StorageDoubleMap<125		Hasher1 = Twox64Concat,126		Key1 = CollectionId,127		Hasher2 = Blake2_128Concat,128		Key2 = staging_xcm::v3::AssetInstance,129		Value = TokenId,130		QueryKind = OptionQuery,131	>;132133	/// The correponding reserve NFT of a token ID134	#[pallet::storage]135	#[pallet::getter(fn token_id_to_foreign_reserve_asset_instance)]136	pub type TokenIdToForeignReserveAssetInstance<T: Config> = StorageDoubleMap<137		Hasher1 = Twox64Concat,138		Key1 = CollectionId,139		Hasher2 = Blake2_128Concat,140		Key2 = TokenId,141		Value = staging_xcm::v3::AssetInstance,142		QueryKind = OptionQuery,143	>;144145	#[pallet::pallet]146	pub struct Pallet<T>(_);147148	#[pallet::call]149	impl<T: Config> Pallet<T> {150		#[pallet::call_index(0)]151		#[pallet::weight(<T as Config>::WeightInfo::register_foreign_asset())]152		pub fn force_register_foreign_asset(153			origin: OriginFor<T>,154			versioned_asset_id: Box<VersionedAssetId>,155			name: CollectionName,156			token_prefix: CollectionTokenPrefix,157			mode: ForeignCollectionMode,158		) -> DispatchResult {159			T::ForceRegisterOrigin::ensure_origin(origin.clone())?;160161			let asset_id: AssetId = versioned_asset_id162				.as_ref()163				.clone()164				.try_into()165				.map_err(|()| Error::<T>::BadForeignAssetId)?;166167			ensure!(168				!<ForeignAssetToCollection<T>>::contains_key(asset_id),169				<Error<T>>::ForeignAssetAlreadyRegistered,170			);171172			let foreign_collection_owner = Self::pallet_account();173174			let description: CollectionDescription = "Foreign Assets Collection"175				.encode_utf16()176				.collect::<Vec<_>>()177				.try_into()178				.expect("description length < max description length; qed");179180			let collection_id = T::CollectionDispatch::create(181				foreign_collection_owner,182				CollectionIssuer::Internals,183				CreateCollectionData {184					name,185					token_prefix,186					description,187					mode: mode.into(),188					..Default::default()189				},190			)?;191192			<ForeignAssetToCollection<T>>::insert(asset_id, collection_id);193			<CollectionToForeignAsset<T>>::insert(collection_id, asset_id);194195			Self::deposit_event(Event::<T>::ForeignAssetRegistered {196				collection_id,197				asset_id: versioned_asset_id,198			});199200			Ok(())201		}202	}203}204205impl<T: Config> Pallet<T> {206	fn pallet_account() -> T::CrossAccountId {207		let owner: T::AccountId = T::PalletId::get().into_account_truncating();208		T::CrossAccountId::from_sub(owner)209	}210211	/// Converts a concrete asset ID (the asset multilocation) to a local collection on Unique Network.212	///213	/// The multilocation corresponds to a local collection if:214	/// * It is `Here` location that corresponds to the native token of this parachain.215	/// * It is `../Parachain(<Unique Network Para ID>)` that also corresponds to the native token of this parachain.216	/// * It is `../Parachain(<Unique Network Para ID>)/GeneralIndex(<Collection ID>)` that corresponds217	/// to the collection with the ID equal to `<Collection ID>`. The `<Collection ID>` must be in the valid range,218	/// otherwise `None` is returned.219	/// * It is `GeneralIndex(<Collection ID>)`. Same as the last one above.220	///221	/// If the multilocation doesn't match the patterns listed above,222	/// or the `<Collection ID>` points to a foreign collection,223	/// `None` is returned, identifying that the given multilocation doesn't correspond to a local collection.224	fn local_asset_id_to_collection(asset_id: &AssetId) -> Option<CollectionLocality> {225		let AssetId::Concrete(asset_location) = asset_id else {226			return None;227		};228229		let self_location = T::SelfLocation::get();230231		if *asset_location == Here.into() || *asset_location == self_location {232			return Some(CollectionLocality::Local(NATIVE_FUNGIBLE_COLLECTION_ID));233		}234235		let prefix = if asset_location.parents == 0 {236			&Here237		} else if asset_location.parents == self_location.parents {238			&self_location.interior239		} else {240			return None;241		};242243		let GeneralIndex(collection_id) = asset_location.interior.match_and_split(prefix)? else {244			return None;245		};246247		let collection_id = CollectionId((*collection_id).try_into().ok()?);248249		Self::collection_to_foreign_asset(collection_id)250			.is_none()251			.then_some(CollectionLocality::Local(collection_id))252	}253254	/// Converts an asset ID to a Unique Network's collection locality (either foreign or a local one).255	///256	/// The function will check if the asset's reserve location has the corresponding257	/// foreign collection on Unique Network,258	/// and will return the "foreign" locality containing the collection ID if found.259	///260	/// If no corresponding foreign collection is found, the function will check261	/// if the asset's reserve location corresponds to a local collection.262	/// If the local collection is found, the "local" locality with the collection ID is returned.263	///264	/// If all of the above have failed, the `AssetIdConversionFailed` error will be returned.265	fn asset_to_collection(asset_id: &AssetId) -> Result<CollectionLocality, XcmError> {266		Self::foreign_asset_to_collection(asset_id)267			.map(CollectionLocality::Foreign)268			.or_else(|| Self::local_asset_id_to_collection(asset_id))269			.ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())270	}271272	/// Converts an XCM asset instance of local collection to the Unique Network's token ID.273	///274	/// The asset instance corresponds to the Unique Network's token ID if it is in the following format:275	/// `AssetInstance::Index(<token ID>)`.276	///277	/// If the asset instance is not in the valid format or the `<token ID>` can't fit into the valid token ID,278	/// `None` will be returned.279	fn local_asset_instance_to_token_id(asset_instance: &AssetInstance) -> Option<TokenId> {280		match asset_instance {281			AssetInstance::Index(token_id) => Some(TokenId((*token_id).try_into().ok()?)),282			_ => None,283		}284	}285286	/// Obtains the token ID of the `asset_instance` in the collection.287	fn asset_instance_to_token_id(288		collection_locality: CollectionLocality,289		asset_instance: &AssetInstance,290	) -> Option<TokenId> {291		match collection_locality {292			CollectionLocality::Local(_) => Self::local_asset_instance_to_token_id(asset_instance),293			CollectionLocality::Foreign(collection_id) => {294				Self::foreign_reserve_asset_instance_to_token_id(collection_id, asset_instance)295			}296		}297	}298299	/// Creates a foreign item in the the collection.300	fn create_foreign_asset_instance(301		xcm_ext: &dyn XcmExtensions<T>,302		collection_id: CollectionId,303		asset_instance: &AssetInstance,304		to: T::CrossAccountId,305	) -> DispatchResult {306		let derivative_token_id = xcm_ext.create_item(307			&Self::pallet_account(),308			to,309			CreateItemData::NFT(Default::default()),310			&ZeroBudget,311		)?;312313		<ForeignReserveAssetInstanceToTokenId<T>>::insert(314			collection_id,315			asset_instance,316			derivative_token_id,317		);318319		<TokenIdToForeignReserveAssetInstance<T>>::insert(320			collection_id,321			derivative_token_id,322			asset_instance,323		);324325		Ok(())326	}327328	/// Deposits an asset instance to the `to` account.329	///330	/// Either transfers an existing item from the pallet's account331	/// or creates a foreign item.332	fn deposit_asset_instance(333		xcm_ext: &dyn XcmExtensions<T>,334		collection_locality: CollectionLocality,335		asset_instance: &AssetInstance,336		to: T::CrossAccountId,337	) -> XcmResult {338		let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance);339340		let deposit_result = match (collection_locality, token_id) {341			(_, Some(token_id)) => {342				let depositor = &Self::pallet_account();343				let from = depositor;344				let amount = 1;345346				xcm_ext.transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)347			}348			(CollectionLocality::Foreign(collection_id), None) => {349				Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)350			}351			(CollectionLocality::Local(_), None) => {352				return Err(XcmExecutorError::InstanceConversionFailed.into());353			}354		};355356		deposit_result357			.map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))358	}359360	/// Withdraws an asset instance from the `from` account.361	///362	/// Transfers the asset instance to the pallet's account.363	fn withdraw_asset_instance(364		xcm_ext: &dyn XcmExtensions<T>,365		collection_locality: CollectionLocality,366		asset_instance: &AssetInstance,367		from: T::CrossAccountId,368	) -> XcmResult {369		let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance)370			.ok_or(XcmExecutorError::InstanceConversionFailed)?;371372		let depositor = &from;373		let to = Self::pallet_account();374		let amount = 1;375		xcm_ext376			.transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)377			.map_err(|_| XcmError::FailedToTransactAsset("non-fungible item withdraw failed"))?;378379		Ok(())380	}381}382383impl<T: Config> TransactAsset for Pallet<T> {384	fn can_check_in(385		_origin: &MultiLocation,386		_what: &MultiAsset,387		_context: &XcmContext,388	) -> XcmResult {389		Err(XcmError::Unimplemented)390	}391392	fn check_in(_origin: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}393394	fn can_check_out(395		_dest: &MultiLocation,396		_what: &MultiAsset,397		_context: &XcmContext,398	) -> XcmResult {399		Err(XcmError::Unimplemented)400	}401402	fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}403404	fn deposit_asset(405		what: &MultiAsset,406		to: &MultiLocation,407		_context: Option<&XcmContext>,408	) -> XcmResult {409		let to = T::LocationToAccountId::convert_location(to)410			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;411412		let collection_locality = Self::asset_to_collection(&what.id)?;413		let dispatch = T::CollectionDispatch::dispatch(*collection_locality)414			.map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;415416		let collection = dispatch.as_dyn();417		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;418419		match what.fun {420			Fungibility::Fungible(amount) => xcm_ext421				.create_item(422					&Self::pallet_account(),423					to,424					CreateItemData::Fungible(CreateFungibleData { value: amount }),425					&ZeroBudget,426				)427				.map(|_| ())428				.map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),429430			Fungibility::NonFungible(asset_instance) => {431				Self::deposit_asset_instance(xcm_ext, collection_locality, &asset_instance, to)432			}433		}434	}435436	fn withdraw_asset(437		what: &MultiAsset,438		from: &MultiLocation,439		_maybe_context: Option<&XcmContext>,440	) -> Result<staging_xcm_executor::Assets, XcmError> {441		let from = T::LocationToAccountId::convert_location(from)442			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;443444		let collection_locality = Self::asset_to_collection(&what.id)?;445		let dispatch = T::CollectionDispatch::dispatch(*collection_locality)446			.map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;447448		let collection = dispatch.as_dyn();449		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;450451		match what.fun {452			Fungibility::Fungible(amount) => xcm_ext453				.burn_item(from, TokenId::default(), amount)454				.map_err(|_| XcmError::FailedToTransactAsset("fungible item withdraw failed"))?,455456			Fungibility::NonFungible(asset_instance) => {457				Self::withdraw_asset_instance(xcm_ext, collection_locality, &asset_instance, from)?;458			}459		}460461		Ok(what.clone().into())462	}463464	fn internal_transfer_asset(465		what: &MultiAsset,466		from: &MultiLocation,467		to: &MultiLocation,468		_context: &XcmContext,469	) -> Result<staging_xcm_executor::Assets, XcmError> {470		let from = T::LocationToAccountId::convert_location(from)471			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;472473		let to = T::LocationToAccountId::convert_location(to)474			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;475476		let collection_locality = Self::asset_to_collection(&what.id)?;477478		let dispatch = T::CollectionDispatch::dispatch(*collection_locality)479			.map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;480		let collection = dispatch.as_dyn();481		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;482483		let depositor = &from;484485		let token_id;486		let amount;487		let map_error: fn(DispatchError) -> XcmError;488489		match what.fun {490			Fungibility::Fungible(fungible_amount) => {491				token_id = TokenId::default();492				amount = fungible_amount;493				map_error = |_| XcmError::FailedToTransactAsset("fungible item transfer failed");494			}495496			Fungibility::NonFungible(asset_instance) => {497				token_id = Self::asset_instance_to_token_id(collection_locality, &asset_instance)498					.ok_or(XcmExecutorError::InstanceConversionFailed)?;499500				amount = 1;501				map_error = |_| XcmError::FailedToTransactAsset("non-fungible item transfer failed")502			}503		}504505		xcm_ext506			.transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)507			.map_err(map_error)?;508509		Ok(what.clone().into())510	}511}512513#[derive(Clone, Copy)]514pub enum CollectionLocality {515	Local(CollectionId),516	Foreign(CollectionId),517}518519impl Deref for CollectionLocality {520	type Target = CollectionId;521522	fn deref(&self) -> &Self::Target {523		match self {524			Self::Local(id) => id,525			Self::Foreign(id) => id,526		}527	}528}529530pub struct CurrencyIdConvert<T: Config>(PhantomData<T>);531impl<T: Config> sp_runtime::traits::Convert<CollectionId, Option<MultiLocation>>532	for CurrencyIdConvert<T>533{534	fn convert(collection_id: CollectionId) -> Option<MultiLocation> {535		if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {536			Some(T::SelfLocation::get())537		} else {538			<Pallet<T>>::collection_to_foreign_asset(collection_id)539				.and_then(|asset_id| match asset_id {540					AssetId::Concrete(location) => Some(location),541					_ => None,542				})543				.or_else(|| {544					T::SelfLocation::get()545						.pushed_with_interior(GeneralIndex(collection_id.0.into()))546						.ok()547				})548		}549	}550}551552#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]553pub enum ForeignCollectionMode {554	NFT,555	Fungible(u8),556}557558impl From<ForeignCollectionMode> for CollectionMode {559	fn from(value: ForeignCollectionMode) -> Self {560		match value {561			ForeignCollectionMode::NFT => Self::NFT,562			ForeignCollectionMode::Fungible(decimals) => Self::Fungible(decimals),563		}564	}565}566567pub struct FreeForAll;568569impl WeightTrader for FreeForAll {570	fn new() -> Self {571		Self572	}573574	fn buy_weight(575		&mut self,576		weight: Weight,577		payment: Assets,578		_xcm: &XcmContext,579	) -> Result<Assets, XcmError> {580		log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);581		Ok(payment)582	}583}
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::{dispatch::DispatchResult, pallet_prelude::*, traits::EnsureOrigin, PalletId};29use frame_system::pallet_prelude::*;30use pallet_common::{31	dispatch::CollectionDispatch, erc::CrossAccountId, XcmExtensions, NATIVE_FUNGIBLE_COLLECTION_ID,32};33use sp_runtime::traits::AccountIdConversion;34use sp_std::{boxed::Box, vec, vec::Vec};35use staging_xcm::{36	opaque::latest::{prelude::XcmError, Weight},37	v3::{prelude::*, MultiAsset, XcmContext},38	VersionedAssetId,39};40use staging_xcm_executor::{41	traits::{ConvertLocation, Error as XcmExecutorError, TransactAsset, WeightTrader},42	Assets,43};44use up_data_structs::{45	budget::ZeroBudget, CollectionId, CollectionMode, CollectionName, CollectionTokenPrefix,46	CreateCollectionData, CreateFungibleData, CreateItemData, TokenId,47};4849pub mod weights;5051#[cfg(feature = "runtime-benchmarks")]52mod benchmarking;5354pub use module::*;55pub use weights::WeightInfo;5657#[frame_support::pallet]58pub mod module {59	use pallet_common::CollectionIssuer;60	use up_data_structs::CollectionDescription;6162	use super::*;6364	#[pallet::config]65	pub trait Config:66		frame_system::Config67		+ pallet_common::Config68		+ pallet_fungible::Config69		+ pallet_balances::Config70	{71		/// The overarching event type.72		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;7374		/// Origin for force registering of a foreign asset.75		type ForceRegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;7677		/// The ID of the foreign assets pallet.78		type PalletId: Get<PalletId>;7980		/// Self-location of this parachain.81		type SelfLocation: Get<MultiLocation>;8283		/// The converter from a MultiLocation to a CrossAccountId.84		type LocationToAccountId: ConvertLocation<Self::CrossAccountId>;8586		/// Weight information for the extrinsics in this module.87		type WeightInfo: WeightInfo;88	}8990	#[pallet::error]91	pub enum Error<T> {92		/// The foreign asset is already registered.93		ForeignAssetAlreadyRegistered,9495		/// The given asset ID could not be converted into the current XCM version.96		BadForeignAssetId,97	}9899	#[pallet::event]100	#[pallet::generate_deposit(fn deposit_event)]101	pub enum Event<T: Config> {102		/// The foreign asset registered.103		ForeignAssetRegistered {104			collection_id: CollectionId,105			asset_id: Box<VersionedAssetId>,106		},107	}108109	/// The corresponding collections of foreign assets.110	#[pallet::storage]111	#[pallet::getter(fn foreign_asset_to_collection)]112	pub type ForeignAssetToCollection<T: Config> =113		StorageMap<_, Twox64Concat, staging_xcm::v3::AssetId, CollectionId, OptionQuery>;114115	/// The corresponding foreign assets of collections.116	#[pallet::storage]117	#[pallet::getter(fn collection_to_foreign_asset)]118	pub type CollectionToForeignAsset<T: Config> =119		StorageMap<_, Twox64Concat, CollectionId, staging_xcm::v3::AssetId, OptionQuery>;120121	/// The correponding NFT token id of reserve NFTs122	#[pallet::storage]123	#[pallet::getter(fn foreign_reserve_asset_instance_to_token_id)]124	pub type ForeignReserveAssetInstanceToTokenId<T: Config> = StorageDoubleMap<125		Hasher1 = Twox64Concat,126		Key1 = CollectionId,127		Hasher2 = Blake2_128Concat,128		Key2 = staging_xcm::v3::AssetInstance,129		Value = TokenId,130		QueryKind = OptionQuery,131	>;132133	/// The correponding reserve NFT of a token ID134	#[pallet::storage]135	#[pallet::getter(fn token_id_to_foreign_reserve_asset_instance)]136	pub type TokenIdToForeignReserveAssetInstance<T: Config> = StorageDoubleMap<137		Hasher1 = Twox64Concat,138		Key1 = CollectionId,139		Hasher2 = Blake2_128Concat,140		Key2 = TokenId,141		Value = staging_xcm::v3::AssetInstance,142		QueryKind = OptionQuery,143	>;144145	#[pallet::pallet]146	pub struct Pallet<T>(_);147148	#[pallet::call]149	impl<T: Config> Pallet<T> {150		#[pallet::call_index(0)]151		#[pallet::weight(<T as Config>::WeightInfo::register_foreign_asset())]152		pub fn force_register_foreign_asset(153			origin: OriginFor<T>,154			versioned_asset_id: Box<VersionedAssetId>,155			name: CollectionName,156			token_prefix: CollectionTokenPrefix,157			mode: ForeignCollectionMode,158		) -> DispatchResult {159			T::ForceRegisterOrigin::ensure_origin(origin.clone())?;160161			let asset_id: AssetId = versioned_asset_id162				.as_ref()163				.clone()164				.try_into()165				.map_err(|()| Error::<T>::BadForeignAssetId)?;166167			ensure!(168				!<ForeignAssetToCollection<T>>::contains_key(asset_id),169				<Error<T>>::ForeignAssetAlreadyRegistered,170			);171172			let foreign_collection_owner = Self::pallet_account();173174			let description: CollectionDescription = "Foreign Assets Collection"175				.encode_utf16()176				.collect::<Vec<_>>()177				.try_into()178				.expect("description length < max description length; qed");179180			let collection_id = T::CollectionDispatch::create(181				foreign_collection_owner,182				CollectionIssuer::Internals,183				CreateCollectionData {184					name,185					token_prefix,186					description,187					mode: mode.into(),188					..Default::default()189				},190			)?;191192			<ForeignAssetToCollection<T>>::insert(asset_id, collection_id);193			<CollectionToForeignAsset<T>>::insert(collection_id, asset_id);194195			Self::deposit_event(Event::<T>::ForeignAssetRegistered {196				collection_id,197				asset_id: versioned_asset_id,198			});199200			Ok(())201		}202	}203}204205impl<T: Config> Pallet<T> {206	fn pallet_account() -> T::CrossAccountId {207		let owner: T::AccountId = T::PalletId::get().into_account_truncating();208		T::CrossAccountId::from_sub(owner)209	}210211	/// Converts a concrete asset ID (the asset multilocation) to a local collection on Unique Network.212	///213	/// The multilocation corresponds to a local collection if:214	/// * It is `Here` location that corresponds to the native token of this parachain.215	/// * It is `../Parachain(<Unique Network Para ID>)` that also corresponds to the native token of this parachain.216	/// * It is `../Parachain(<Unique Network Para ID>)/GeneralIndex(<Collection ID>)` that corresponds217	/// to the collection with the ID equal to `<Collection ID>`. The `<Collection ID>` must be in the valid range,218	/// otherwise `None` is returned.219	/// * It is `GeneralIndex(<Collection ID>)`. Same as the last one above.220	///221	/// If the multilocation doesn't match the patterns listed above,222	/// or the `<Collection ID>` points to a foreign collection,223	/// `None` is returned, identifying that the given multilocation doesn't correspond to a local collection.224	fn local_asset_id_to_collection(asset_id: &AssetId) -> Option<CollectionLocality> {225		let AssetId::Concrete(asset_location) = asset_id else {226			return None;227		};228229		let self_location = T::SelfLocation::get();230231		if *asset_location == Here.into() || *asset_location == self_location {232			return Some(CollectionLocality::Local(NATIVE_FUNGIBLE_COLLECTION_ID));233		}234235		let prefix = if asset_location.parents == 0 {236			&Here237		} else if asset_location.parents == self_location.parents {238			&self_location.interior239		} else {240			return None;241		};242243		let GeneralIndex(collection_id) = asset_location.interior.match_and_split(prefix)? else {244			return None;245		};246247		let collection_id = CollectionId((*collection_id).try_into().ok()?);248249		Self::collection_to_foreign_asset(collection_id)250			.is_none()251			.then_some(CollectionLocality::Local(collection_id))252	}253254	/// Converts an asset ID to a Unique Network's collection locality (either foreign or a local one).255	///256	/// The function will check if the asset's reserve location has the corresponding257	/// foreign collection on Unique Network,258	/// and will return the "foreign" locality containing the collection ID if found.259	///260	/// If no corresponding foreign collection is found, the function will check261	/// if the asset's reserve location corresponds to a local collection.262	/// If the local collection is found, the "local" locality with the collection ID is returned.263	///264	/// If all of the above have failed, the `AssetIdConversionFailed` error will be returned.265	fn asset_to_collection(asset_id: &AssetId) -> Result<CollectionLocality, XcmError> {266		Self::foreign_asset_to_collection(asset_id)267			.map(CollectionLocality::Foreign)268			.or_else(|| Self::local_asset_id_to_collection(asset_id))269			.ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())270	}271272	/// Converts an XCM asset instance of local collection to the Unique Network's token ID.273	///274	/// The asset instance corresponds to the Unique Network's token ID if it is in the following format:275	/// `AssetInstance::Index(<token ID>)`.276	///277	/// If the asset instance is not in the valid format or the `<token ID>` can't fit into the valid token ID,278	/// `None` will be returned.279	///280	/// Note: this function can return `Some` containing the token ID of a non-existing NFT.281	fn local_asset_instance_to_token_id(asset_instance: &AssetInstance) -> Option<TokenId> {282		match asset_instance {283			AssetInstance::Index(token_id) => Some(TokenId((*token_id).try_into().ok()?)),284			_ => None,285		}286	}287288	/// Obtains the token ID of the `asset_instance` in the collection.289	fn asset_instance_to_token_id(290		collection_locality: CollectionLocality,291		asset_instance: &AssetInstance,292	) -> Option<TokenId> {293		match collection_locality {294			CollectionLocality::Local(_) => Self::local_asset_instance_to_token_id(asset_instance),295			CollectionLocality::Foreign(collection_id) => {296				Self::foreign_reserve_asset_instance_to_token_id(collection_id, asset_instance)297			}298		}299	}300301	/// Creates a foreign item in the the collection.302	fn create_foreign_asset_instance(303		xcm_ext: &dyn XcmExtensions<T>,304		collection_id: CollectionId,305		asset_instance: &AssetInstance,306		to: T::CrossAccountId,307	) -> DispatchResult {308		let derivative_token_id = xcm_ext.create_item(309			&Self::pallet_account(),310			to,311			CreateItemData::NFT(Default::default()),312			&ZeroBudget,313		)?;314315		<ForeignReserveAssetInstanceToTokenId<T>>::insert(316			collection_id,317			asset_instance,318			derivative_token_id,319		);320321		<TokenIdToForeignReserveAssetInstance<T>>::insert(322			collection_id,323			derivative_token_id,324			asset_instance,325		);326327		Ok(())328	}329330	/// Deposits an asset instance to the `to` account.331	///332	/// Either transfers an existing item from the pallet's account333	/// or creates a foreign item.334	fn deposit_asset_instance(335		xcm_ext: &dyn XcmExtensions<T>,336		collection_locality: CollectionLocality,337		asset_instance: &AssetInstance,338		to: T::CrossAccountId,339	) -> XcmResult {340		let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance);341342		let deposit_result = match (collection_locality, token_id) {343			(_, Some(token_id)) => {344				let depositor = &Self::pallet_account();345				let from = depositor;346				let amount = 1;347348				xcm_ext.transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)349			}350			(CollectionLocality::Foreign(collection_id), None) => {351				Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)352			}353			(CollectionLocality::Local(_), None) => {354				return Err(XcmExecutorError::InstanceConversionFailed.into());355			}356		};357358		deposit_result359			.map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))360	}361362	/// Withdraws an asset instance from the `from` account.363	///364	/// Transfers the asset instance to the pallet's account.365	fn withdraw_asset_instance(366		xcm_ext: &dyn XcmExtensions<T>,367		collection_locality: CollectionLocality,368		asset_instance: &AssetInstance,369		from: T::CrossAccountId,370	) -> XcmResult {371		let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance)372			.ok_or(XcmExecutorError::InstanceConversionFailed)?;373374		let depositor = &from;375		let to = Self::pallet_account();376		let amount = 1;377		xcm_ext378			.transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)379			.map_err(|_| XcmError::FailedToTransactAsset("non-fungible item withdraw failed"))?;380381		Ok(())382	}383}384385impl<T: Config> TransactAsset for Pallet<T> {386	fn can_check_in(387		_origin: &MultiLocation,388		_what: &MultiAsset,389		_context: &XcmContext,390	) -> XcmResult {391		Err(XcmError::Unimplemented)392	}393394	fn check_in(_origin: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}395396	fn can_check_out(397		_dest: &MultiLocation,398		_what: &MultiAsset,399		_context: &XcmContext,400	) -> XcmResult {401		Err(XcmError::Unimplemented)402	}403404	fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}405406	fn deposit_asset(407		what: &MultiAsset,408		to: &MultiLocation,409		_context: Option<&XcmContext>,410	) -> XcmResult {411		let to = T::LocationToAccountId::convert_location(to)412			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;413414		let collection_locality = Self::asset_to_collection(&what.id)?;415		let dispatch = T::CollectionDispatch::dispatch(*collection_locality)416			.map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;417418		let collection = dispatch.as_dyn();419		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;420421		match what.fun {422			Fungibility::Fungible(amount) => xcm_ext423				.create_item(424					&Self::pallet_account(),425					to,426					CreateItemData::Fungible(CreateFungibleData { value: amount }),427					&ZeroBudget,428				)429				.map(|_| ())430				.map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),431432			Fungibility::NonFungible(asset_instance) => {433				Self::deposit_asset_instance(xcm_ext, collection_locality, &asset_instance, to)434			}435		}436	}437438	fn withdraw_asset(439		what: &MultiAsset,440		from: &MultiLocation,441		_maybe_context: Option<&XcmContext>,442	) -> Result<staging_xcm_executor::Assets, XcmError> {443		let from = T::LocationToAccountId::convert_location(from)444			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;445446		let collection_locality = Self::asset_to_collection(&what.id)?;447		let dispatch = T::CollectionDispatch::dispatch(*collection_locality)448			.map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;449450		let collection = dispatch.as_dyn();451		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;452453		match what.fun {454			Fungibility::Fungible(amount) => xcm_ext455				.burn_item(from, TokenId::default(), amount)456				.map_err(|_| XcmError::FailedToTransactAsset("fungible item withdraw failed"))?,457458			Fungibility::NonFungible(asset_instance) => {459				Self::withdraw_asset_instance(xcm_ext, collection_locality, &asset_instance, from)?;460			}461		}462463		Ok(what.clone().into())464	}465466	fn internal_transfer_asset(467		what: &MultiAsset,468		from: &MultiLocation,469		to: &MultiLocation,470		_context: &XcmContext,471	) -> Result<staging_xcm_executor::Assets, XcmError> {472		let from = T::LocationToAccountId::convert_location(from)473			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;474475		let to = T::LocationToAccountId::convert_location(to)476			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;477478		let collection_locality = Self::asset_to_collection(&what.id)?;479480		let dispatch = T::CollectionDispatch::dispatch(*collection_locality)481			.map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;482		let collection = dispatch.as_dyn();483		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;484485		let depositor = &from;486487		let token_id;488		let amount;489		let map_error: fn(DispatchError) -> XcmError;490491		match what.fun {492			Fungibility::Fungible(fungible_amount) => {493				token_id = TokenId::default();494				amount = fungible_amount;495				map_error = |_| XcmError::FailedToTransactAsset("fungible item transfer failed");496			}497498			Fungibility::NonFungible(asset_instance) => {499				token_id = Self::asset_instance_to_token_id(collection_locality, &asset_instance)500					.ok_or(XcmExecutorError::InstanceConversionFailed)?;501502				amount = 1;503				map_error = |_| XcmError::FailedToTransactAsset("non-fungible item transfer failed")504			}505		}506507		xcm_ext508			.transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)509			.map_err(map_error)?;510511		Ok(what.clone().into())512	}513}514515#[derive(Clone, Copy)]516pub enum CollectionLocality {517	Local(CollectionId),518	Foreign(CollectionId),519}520521impl Deref for CollectionLocality {522	type Target = CollectionId;523524	fn deref(&self) -> &Self::Target {525		match self {526			Self::Local(id) => id,527			Self::Foreign(id) => id,528		}529	}530}531532pub struct CurrencyIdConvert<T: Config>(PhantomData<T>);533impl<T: Config> sp_runtime::traits::Convert<CollectionId, Option<MultiLocation>>534	for CurrencyIdConvert<T>535{536	fn convert(collection_id: CollectionId) -> Option<MultiLocation> {537		if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {538			Some(T::SelfLocation::get())539		} else {540			<Pallet<T>>::collection_to_foreign_asset(collection_id)541				.and_then(|asset_id| match asset_id {542					AssetId::Concrete(location) => Some(location),543					_ => None,544				})545				.or_else(|| {546					T::SelfLocation::get()547						.pushed_with_interior(GeneralIndex(collection_id.0.into()))548						.ok()549				})550		}551	}552}553554#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]555pub enum ForeignCollectionMode {556	NFT,557	Fungible(u8),558}559560impl From<ForeignCollectionMode> for CollectionMode {561	fn from(value: ForeignCollectionMode) -> Self {562		match value {563			ForeignCollectionMode::NFT => Self::NFT,564			ForeignCollectionMode::Fungible(decimals) => Self::Fungible(decimals),565		}566	}567}568569pub struct FreeForAll;570571impl WeightTrader for FreeForAll {572	fn new() -> Self {573		Self574	}575576	fn buy_weight(577		&mut self,578		weight: Weight,579		payment: Assets,580		_xcm: &XcmContext,581	) -> Result<Assets, XcmError> {582		log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);583		Ok(payment)584	}585}