git.delta.rocks / unique-network / refs/commits / 332bbaac859c

difftreelog

fix use AssetId in the mapping

Daniel Shiposha2023-11-07parent: #0880691.patch.diff
in: master

2 files changed

modifiedpallets/foreign-assets/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/foreign-assets/src/benchmarking.rs
+++ b/pallets/foreign-assets/src/benchmarking.rs
@@ -19,7 +19,7 @@
 use frame_benchmarking::v2::*;
 use frame_system::RawOrigin;
 use pallet_common::benchmarking::{create_data, create_u16_data};
-use sp_std::vec;
+use sp_std::{vec, boxed::Box};
 use staging_xcm::prelude::*;
 use up_data_structs::{MAX_COLLECTION_NAME_LENGTH, MAX_TOKEN_PREFIX_LENGTH};
 
@@ -41,7 +41,7 @@
 		#[extrinsic_call]
 		_(
 			RawOrigin::Root,
-			Box::new(location),
+			Box::new(location.into()),
 			name,
 			token_prefix,
 			mode,
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};39use staging_xcm_executor::{40	traits::{ConvertLocation, Error as XcmExecutorError, TransactAsset, WeightTrader},41	Assets,42};43use up_data_structs::{44	budget::ZeroBudget, CollectionId, CollectionMode, CollectionName, CollectionTokenPrefix,45	CreateCollectionData, CreateFungibleData, CreateItemData, CreateNftData, Property, PropertyKey,46	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 up_data_structs::{60		CollectionDescription, Property, PropertyKeyPermission, PropertyPermission,61	};6263	use super::*;6465	#[pallet::config]66	pub trait Config:67		frame_system::Config68		+ pallet_common::Config69		+ pallet_fungible::Config70		+ pallet_balances::Config71	{72		/// The overarching event type.73		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;7475		/// Origin for force registering of a foreign asset.76		type ForceRegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;7778		/// The ID of the foreign assets pallet.79		type PalletId: Get<PalletId>;8081		/// Self-location of this parachain.82		type SelfLocation: Get<MultiLocation>;8384		/// The converter from a MultiLocation to a CrossAccountId.85		type LocationToAccountId: ConvertLocation<Self::CrossAccountId>;8687		/// Weight information for the extrinsics in this module.88		type WeightInfo: WeightInfo;89	}9091	#[pallet::error]92	pub enum Error<T> {93		/// The foreign asset is already registered.94		ForeignAssetAlreadyRegistered,95	}9697	#[pallet::event]98	#[pallet::generate_deposit(fn deposit_event)]99	pub enum Event<T: Config> {100		/// The foreign asset registered.101		ForeignAssetRegistered {102			asset_id: CollectionId,103			reserve_location: Box<MultiLocation>,104		},105	}106107	/// The corresponding collections of reserve locations.108	#[pallet::storage]109	#[pallet::getter(fn foreign_reserve_location_to_collection)]110	pub type ForeignReserveLocationToCollection<T: Config> =111		StorageMap<_, Twox64Concat, staging_xcm::v3::MultiLocation, CollectionId, OptionQuery>;112113	/// The corresponding reserve location of collections.114	#[pallet::storage]115	#[pallet::getter(fn collection_to_foreign_reserve_location)]116	pub type CollectionToForeignReserveLocation<T: Config> =117		StorageMap<_, Twox64Concat, CollectionId, staging_xcm::v3::MultiLocation, OptionQuery>;118119	/// The correponding NFT token id of reserve NFTs120	#[pallet::storage]121	#[pallet::getter(fn foreign_reserve_asset_instance_to_token_id)]122	pub type ForeignReserveAssetInstanceToTokenId<T: Config> = StorageDoubleMap<123		Hasher1 = Twox64Concat,124		Key1 = CollectionId,125		Hasher2 = Twox64Concat,126		Key2 = staging_xcm::v3::AssetInstance,127		Value = TokenId,128		QueryKind = OptionQuery,129	>;130131	/// The correponding reserve NFT of a token ID132	#[pallet::storage]133	#[pallet::getter(fn token_id_to_foreign_reserve_asset_instance)]134	pub type TokenIdToForeignReserveAssetInstance<T: Config> = StorageDoubleMap<135		Hasher1 = Twox64Concat,136		Key1 = CollectionId,137		Hasher2 = Twox64Concat,138		Key2 = TokenId,139		Value = staging_xcm::v3::AssetInstance,140		QueryKind = OptionQuery,141	>;142143	#[pallet::pallet]144	pub struct Pallet<T>(_);145146	#[pallet::call]147	impl<T: Config> Pallet<T> {148		#[pallet::call_index(0)]149		#[pallet::weight(<T as Config>::WeightInfo::register_foreign_asset())]150		pub fn force_register_foreign_asset(151			origin: OriginFor<T>,152			reserve_location: Box<MultiLocation>,153			name: CollectionName,154			token_prefix: CollectionTokenPrefix,155			mode: ForeignCollectionMode,156		) -> DispatchResult {157			T::ForceRegisterOrigin::ensure_origin(origin.clone())?;158159			if <ForeignReserveLocationToCollection<T>>::contains_key(*reserve_location) {160				return Err(<Error<T>>::ForeignAssetAlreadyRegistered.into());161			}162163			let foreign_collection_owner = Self::pallet_account();164165			let description: CollectionDescription = "Foreign Assets Collection"166				.encode_utf16()167				.collect::<Vec<_>>()168				.try_into()169				.expect("description length < max description length; qed");170171			let payer = None;172			let is_special_collection = true;173			let collection_id = T::CollectionDispatch::create_raw(174				foreign_collection_owner,175				payer,176				is_special_collection,177				CreateCollectionData {178					name,179					token_prefix,180					description,181					mode: mode.into(),182183					properties: vec![Property {184						key: Self::reserve_location_property_key(),185						value: reserve_location186							.encode()187							.try_into()188							.expect("multilocation is less than 32k; qed"),189					}]190					.try_into()191					.expect("just one property can always be stored; qed"),192193					token_property_permissions: vec![PropertyKeyPermission {194						key: Self::reserve_asset_instance_property_key(),195						permission: PropertyPermission {196							mutable: false,197							collection_admin: true,198							token_owner: false,199						},200					}]201					.try_into()202					.expect("just one property permission can always be stored; qed"),203					..Default::default()204				},205			)?;206207			<ForeignReserveLocationToCollection<T>>::insert(*reserve_location, collection_id);208			<CollectionToForeignReserveLocation<T>>::insert(collection_id, *reserve_location);209210			Self::deposit_event(Event::<T>::ForeignAssetRegistered {211				asset_id: collection_id,212				reserve_location,213			});214215			Ok(())216		}217	}218}219220impl<T: Config> Pallet<T> {221	fn pallet_account() -> T::CrossAccountId {222		let owner: T::AccountId = T::PalletId::get().into_account_truncating();223		T::CrossAccountId::from_sub(owner)224	}225226	fn reserve_location_property_key() -> PropertyKey {227		b"reserve-location"228			.to_vec()229			.try_into()230			.expect("key length < max property key length; qed")231	}232233	fn reserve_asset_instance_property_key() -> PropertyKey {234		b"reserve-asset-instance"235			.to_vec()236			.try_into()237			.expect("key length < max property key length; qed")238	}239240	/// Converts a multilocation to a local collection on Unique Network.241	///242	/// The multilocation corresponds to a local collection if:243	/// * It is `Here` location that corresponds to the native token of this parachain.244	/// * It is `../Parachain(<Unique Network Para ID>)` that also corresponds to the native token of this parachain.245	/// * It is `../Parachain(<Unique Network Para ID>)/GeneralIndex(<Collection ID>)` that corresponds246	/// to the collection with the ID equal to `<Collection ID>`. The `<Collection ID>` must be in the valid range,247	/// otherwise `None` is returned.248	///249	/// If the multilocation doesn't match the patterns listed above,250	/// or the `<Collection ID>` points to a foreign collection,251	/// `None` is returned, identifying that the given multilocation doesn't correspond to a local collection.252	fn local_asset_location_to_collection(253		asset_location: &MultiLocation,254	) -> Option<CollectionLocality> {255		let self_location = T::SelfLocation::get();256257		if *asset_location == Here.into() || *asset_location == self_location {258			Some(CollectionLocality::Local(NATIVE_FUNGIBLE_COLLECTION_ID))259		} else if asset_location.parents == self_location.parents {260			match asset_location261				.interior262				.match_and_split(&self_location.interior)263			{264				Some(GeneralIndex(collection_id)) => {265					let collection_id = CollectionId((*collection_id).try_into().ok()?);266267					Self::collection_to_foreign_reserve_location(collection_id)268						.is_none()269						.then_some(CollectionLocality::Local(collection_id))270				}271				_ => None,272			}273		} else {274			None275		}276	}277278	/// Converts an asset ID to a Unique Network's collection locality (either foreign or a local one).279	///280	/// The function will check if the asset's reserve location has the corresponding281	/// foreign collection on Unique Network,282	/// and will return the "foreign" locality containing the collection ID if found.283	///284	/// If no corresponding foreign collection is found, the function will check285	/// if the asset's reserve location corresponds to a local collection.286	/// If the local collection is found, the "local" locality with the collection ID is returned.287	///288	/// If all of the above have failed, the `AssetIdConversionFailed` error will be returned.289	fn asset_to_collection(asset_id: &AssetId) -> Result<CollectionLocality, XcmError> {290		let AssetId::Concrete(asset_reserve_location) = asset_id else {291			return Err(XcmExecutorError::AssetNotHandled.into());292		};293294		Self::foreign_reserve_location_to_collection(asset_reserve_location)295			.map(CollectionLocality::Foreign)296			.or_else(|| Self::local_asset_location_to_collection(asset_reserve_location))297			.ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())298	}299300	/// Converts an XCM asset instance of local collection to the Unique Network's token ID.301	///302	/// The asset instance corresponds to the Unique Network's token ID if it is in the following format:303	/// `AssetInstance::Index(<token ID>)`.304	///305	/// If the asset instance is not in the valid format or the `<token ID>` can't fit into the valid token ID,306	/// `None` will be returned.307	fn local_asset_instance_to_token_id(asset_instance: &AssetInstance) -> Option<TokenId> {308		match asset_instance {309			AssetInstance::Index(token_id) => Some(TokenId((*token_id).try_into().ok()?)),310			_ => None,311		}312	}313314	/// Obtains the token ID of the `asset_instance` in the collection.315	fn asset_instance_to_token_id(316		collection_locality: CollectionLocality,317		asset_instance: &AssetInstance,318	) -> Option<TokenId> {319		match collection_locality {320			CollectionLocality::Local(_) => Self::local_asset_instance_to_token_id(asset_instance),321			CollectionLocality::Foreign(collection_id) => {322				Self::foreign_reserve_asset_instance_to_token_id(collection_id, asset_instance)323			}324		}325	}326327	/// Creates a foreign item in the the collection.328	fn create_foreign_asset_instance(329		xcm_ext: &dyn XcmExtensions<T>,330		collection_id: CollectionId,331		asset_instance: &AssetInstance,332		to: T::CrossAccountId,333	) -> DispatchResult {334		let asset_instance_encoded = asset_instance.encode();335336		let derivative_token_id = xcm_ext.create_item(337			&Self::pallet_account(),338			to,339			CreateItemData::NFT(CreateNftData {340				properties: vec![Property {341					key: Self::reserve_asset_instance_property_key(),342					value: asset_instance_encoded343						.try_into()344						.expect("asset instance length <= 32 bytes which is less than value length limit; qed"),345				}]346				.try_into()347				.expect("just one property can always be stored; qed"),348			}),349			&ZeroBudget,350		)?;351352		<ForeignReserveAssetInstanceToTokenId<T>>::insert(353			collection_id,354			asset_instance,355			derivative_token_id,356		);357358		<TokenIdToForeignReserveAssetInstance<T>>::insert(359			collection_id,360			derivative_token_id,361			asset_instance,362		);363364		Ok(())365	}366367	/// Deposits an asset instance to the `to` account.368	///369	/// Either transfers an existing item from the pallet's account370	/// or creates a foreign item.371	fn deposit_asset_instance(372		xcm_ext: &dyn XcmExtensions<T>,373		collection_locality: CollectionLocality,374		asset_instance: &AssetInstance,375		to: T::CrossAccountId,376	) -> XcmResult {377		let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance);378379		let deposit_result = match (collection_locality, token_id) {380			(_, Some(token_id)) => {381				let depositor = &Self::pallet_account();382				let from = depositor;383				let amount = 1;384385				xcm_ext.transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)386			}387			(CollectionLocality::Foreign(collection_id), None) => {388				Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)389			}390			(CollectionLocality::Local(_), None) => {391				return Err(XcmError::AssetNotFound);392			}393		};394395		deposit_result396			.map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))397	}398399	/// Withdraws an asset instance from the `from` account.400	///401	/// Transfers the asset instance to the pallet's account.402	fn withdraw_asset_instance(403		xcm_ext: &dyn XcmExtensions<T>,404		collection_locality: CollectionLocality,405		asset_instance: &AssetInstance,406		from: T::CrossAccountId,407	) -> XcmResult {408		let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance)409			.ok_or(XcmError::AssetNotFound)?;410411		let depositor = &from;412		let to = Self::pallet_account();413		let amount = 1;414		xcm_ext415			.transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)416			.map_err(|_| XcmError::FailedToTransactAsset("non-fungible item withdraw failed"))?;417418		Ok(())419	}420}421422impl<T: Config> TransactAsset for Pallet<T> {423	fn can_check_in(424		_origin: &MultiLocation,425		_what: &MultiAsset,426		_context: &XcmContext,427	) -> XcmResult {428		Err(XcmError::Unimplemented)429	}430431	fn check_in(_origin: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}432433	fn can_check_out(434		_dest: &MultiLocation,435		_what: &MultiAsset,436		_context: &XcmContext,437	) -> XcmResult {438		Err(XcmError::Unimplemented)439	}440441	fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}442443	fn deposit_asset(what: &MultiAsset, to: &MultiLocation, _context: &XcmContext) -> XcmResult {444		let to = T::LocationToAccountId::convert_location(to)445			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;446447		let collection_locality = Self::asset_to_collection(&what.id)?;448		let dispatch = T::CollectionDispatch::dispatch(*collection_locality)449			.map_err(|_| XcmError::AssetNotFound)?;450451		let collection = dispatch.as_dyn();452		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;453454		match what.fun {455			Fungibility::Fungible(amount) => xcm_ext456				.create_item(457					&Self::pallet_account(),458					to,459					CreateItemData::Fungible(CreateFungibleData { value: amount }),460					&ZeroBudget,461				)462				.map(|_| ())463				.map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),464465			Fungibility::NonFungible(asset_instance) => {466				Self::deposit_asset_instance(xcm_ext, collection_locality, &asset_instance, to)467			}468		}469	}470471	fn withdraw_asset(472		what: &MultiAsset,473		from: &MultiLocation,474		_maybe_context: Option<&XcmContext>,475	) -> Result<staging_xcm_executor::Assets, XcmError> {476		let from = T::LocationToAccountId::convert_location(from)477			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;478479		let collection_locality = Self::asset_to_collection(&what.id)?;480		let dispatch = T::CollectionDispatch::dispatch(*collection_locality)481			.map_err(|_| XcmError::AssetNotFound)?;482483		let collection = dispatch.as_dyn();484		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;485486		match what.fun {487			Fungibility::Fungible(amount) => xcm_ext488				.burn_item(from, TokenId::default(), amount)489				.map_err(|_| XcmError::FailedToTransactAsset("fungible item withdraw failed"))?,490491			Fungibility::NonFungible(asset_instance) => {492				Self::withdraw_asset_instance(xcm_ext, collection_locality, &asset_instance, from)?;493			}494		}495496		Ok(what.clone().into())497	}498499	fn internal_transfer_asset(500		what: &MultiAsset,501		from: &MultiLocation,502		to: &MultiLocation,503		_context: &XcmContext,504	) -> Result<staging_xcm_executor::Assets, XcmError> {505		let from = T::LocationToAccountId::convert_location(from)506			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;507508		let to = T::LocationToAccountId::convert_location(to)509			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;510511		let collection_locality = Self::asset_to_collection(&what.id)?;512513		let dispatch = T::CollectionDispatch::dispatch(*collection_locality)514			.map_err(|_| XcmError::AssetNotFound)?;515		let collection = dispatch.as_dyn();516		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;517518		let depositor = &from;519520		let token_id;521		let amount;522		let map_error: fn(DispatchError) -> XcmError;523524		match what.fun {525			Fungibility::Fungible(fungible_amount) => {526				token_id = TokenId::default();527				amount = fungible_amount;528				map_error = |_| XcmError::FailedToTransactAsset("fungible item transfer failed");529			}530531			Fungibility::NonFungible(asset_instance) => {532				token_id = Self::asset_instance_to_token_id(collection_locality, &asset_instance)533					.ok_or(XcmError::AssetNotFound)?;534535				amount = 1;536				map_error = |_| XcmError::FailedToTransactAsset("non-fungible item transfer failed")537			}538		}539540		xcm_ext541			.transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)542			.map_err(map_error)?;543544		Ok(what.clone().into())545	}546}547548#[derive(Clone, Copy)]549pub enum CollectionLocality {550	Local(CollectionId),551	Foreign(CollectionId),552}553554impl Deref for CollectionLocality {555	type Target = CollectionId;556557	fn deref(&self) -> &Self::Target {558		match self {559			Self::Local(id) => id,560			Self::Foreign(id) => id,561		}562	}563}564565pub struct CurrencyIdConvert<T: Config>(PhantomData<T>);566impl<T: Config> sp_runtime::traits::Convert<CollectionId, Option<MultiLocation>>567	for CurrencyIdConvert<T>568{569	fn convert(collection_id: CollectionId) -> Option<MultiLocation> {570		if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {571			Some(T::SelfLocation::get())572		} else {573			<Pallet<T>>::collection_to_foreign_reserve_location(collection_id).or_else(|| {574				T::SelfLocation::get()575					.pushed_with_interior(GeneralIndex(collection_id.0.into()))576					.ok()577			})578		}579	}580}581582#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]583pub enum ForeignCollectionMode {584	NFT,585	Fungible(u8),586}587588impl From<ForeignCollectionMode> for CollectionMode {589	fn from(value: ForeignCollectionMode) -> Self {590		match value {591			ForeignCollectionMode::NFT => Self::NFT,592			ForeignCollectionMode::Fungible(decimals) => Self::Fungible(decimals),593		}594	}595}596597pub struct FreeForAll;598599impl WeightTrader for FreeForAll {600	fn new() -> Self {601		Self602	}603604	fn buy_weight(605		&mut self,606		weight: Weight,607		payment: Assets,608		_xcm: &XcmContext,609	) -> Result<Assets, XcmError> {610		log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);611		Ok(payment)612	}613}
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};39use staging_xcm_executor::{40	traits::{ConvertLocation, Error as XcmExecutorError, TransactAsset, WeightTrader},41	Assets,42};43use up_data_structs::{44	budget::ZeroBudget, CollectionId, CollectionMode, CollectionName, CollectionTokenPrefix,45	CreateCollectionData, CreateFungibleData, CreateItemData, CreateNftData, Property, PropertyKey,46	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 up_data_structs::{60		CollectionDescription, Property, PropertyKeyPermission, PropertyPermission,61	};6263	use super::*;6465	#[pallet::config]66	pub trait Config:67		frame_system::Config68		+ pallet_common::Config69		+ pallet_fungible::Config70		+ pallet_balances::Config71	{72		/// The overarching event type.73		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;7475		/// Origin for force registering of a foreign asset.76		type ForceRegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;7778		/// The ID of the foreign assets pallet.79		type PalletId: Get<PalletId>;8081		/// Self-location of this parachain.82		type SelfLocation: Get<MultiLocation>;8384		/// The converter from a MultiLocation to a CrossAccountId.85		type LocationToAccountId: ConvertLocation<Self::CrossAccountId>;8687		/// Weight information for the extrinsics in this module.88		type WeightInfo: WeightInfo;89	}9091	#[pallet::error]92	pub enum Error<T> {93		/// The foreign asset is already registered.94		ForeignAssetAlreadyRegistered,95	}9697	#[pallet::event]98	#[pallet::generate_deposit(fn deposit_event)]99	pub enum Event<T: Config> {100		/// The foreign asset registered.101		ForeignAssetRegistered {102			collection_id: CollectionId,103			asset_id: Box<AssetId>,104		},105	}106107	/// The corresponding collections of foreign assets.108	#[pallet::storage]109	#[pallet::getter(fn foreign_asset_to_collection)]110	pub type ForeignAssetToCollection<T: Config> =111		StorageMap<_, Twox64Concat, staging_xcm::v3::AssetId, CollectionId, OptionQuery>;112113	/// The corresponding foreign assets of collections.114	#[pallet::storage]115	#[pallet::getter(fn collection_to_foreign_asset)]116	pub type CollectionToForeignAsset<T: Config> =117		StorageMap<_, Twox64Concat, CollectionId, staging_xcm::v3::AssetId, OptionQuery>;118119	/// The correponding NFT token id of reserve NFTs120	#[pallet::storage]121	#[pallet::getter(fn foreign_reserve_asset_instance_to_token_id)]122	pub type ForeignReserveAssetInstanceToTokenId<T: Config> = StorageDoubleMap<123		Hasher1 = Twox64Concat,124		Key1 = CollectionId,125		Hasher2 = Twox64Concat,126		Key2 = staging_xcm::v3::AssetInstance,127		Value = TokenId,128		QueryKind = OptionQuery,129	>;130131	/// The correponding reserve NFT of a token ID132	#[pallet::storage]133	#[pallet::getter(fn token_id_to_foreign_reserve_asset_instance)]134	pub type TokenIdToForeignReserveAssetInstance<T: Config> = StorageDoubleMap<135		Hasher1 = Twox64Concat,136		Key1 = CollectionId,137		Hasher2 = Twox64Concat,138		Key2 = TokenId,139		Value = staging_xcm::v3::AssetInstance,140		QueryKind = OptionQuery,141	>;142143	#[pallet::pallet]144	pub struct Pallet<T>(_);145146	#[pallet::call]147	impl<T: Config> Pallet<T> {148		#[pallet::call_index(0)]149		#[pallet::weight(<T as Config>::WeightInfo::register_foreign_asset())]150		pub fn force_register_foreign_asset(151			origin: OriginFor<T>,152			asset_id: Box<AssetId>,153			name: CollectionName,154			token_prefix: CollectionTokenPrefix,155			mode: ForeignCollectionMode,156		) -> DispatchResult {157			T::ForceRegisterOrigin::ensure_origin(origin.clone())?;158159			ensure!(160				!<ForeignAssetToCollection<T>>::contains_key(*asset_id),161				<Error<T>>::ForeignAssetAlreadyRegistered,162			);163164			let foreign_collection_owner = Self::pallet_account();165166			let description: CollectionDescription = "Foreign Assets Collection"167				.encode_utf16()168				.collect::<Vec<_>>()169				.try_into()170				.expect("description length < max description length; qed");171172			let payer = None;173			let is_special_collection = true;174			let collection_id = T::CollectionDispatch::create_raw(175				foreign_collection_owner,176				payer,177				is_special_collection,178				CreateCollectionData {179					name,180					token_prefix,181					description,182					mode: mode.into(),183184					properties: vec![Property {185						key: Self::reserve_location_property_key(),186						value: asset_id187							.encode()188							.try_into()189							.expect("multilocation is less than 32k; qed"),190					}]191					.try_into()192					.expect("just one property can always be stored; qed"),193194					token_property_permissions: vec![PropertyKeyPermission {195						key: Self::reserve_asset_instance_property_key(),196						permission: PropertyPermission {197							mutable: false,198							collection_admin: true,199							token_owner: false,200						},201					}]202					.try_into()203					.expect("just one property permission can always be stored; qed"),204					..Default::default()205				},206			)?;207208			<ForeignAssetToCollection<T>>::insert(*asset_id, collection_id);209			<CollectionToForeignAsset<T>>::insert(collection_id, *asset_id);210211			Self::deposit_event(Event::<T>::ForeignAssetRegistered {212				collection_id,213				asset_id,214			});215216			Ok(())217		}218	}219}220221impl<T: Config> Pallet<T> {222	fn pallet_account() -> T::CrossAccountId {223		let owner: T::AccountId = T::PalletId::get().into_account_truncating();224		T::CrossAccountId::from_sub(owner)225	}226227	fn reserve_location_property_key() -> PropertyKey {228		b"reserve-location"229			.to_vec()230			.try_into()231			.expect("key length < max property key length; qed")232	}233234	fn reserve_asset_instance_property_key() -> PropertyKey {235		b"reserve-asset-instance"236			.to_vec()237			.try_into()238			.expect("key length < max property key length; qed")239	}240241	/// Converts a concrete asset ID (the asset multilocation) to a local collection on Unique Network.242	///243	/// The multilocation corresponds to a local collection if:244	/// * It is `Here` location that corresponds to the native token of this parachain.245	/// * It is `../Parachain(<Unique Network Para ID>)` that also corresponds to the native token of this parachain.246	/// * It is `../Parachain(<Unique Network Para ID>)/GeneralIndex(<Collection ID>)` that corresponds247	/// to the collection with the ID equal to `<Collection ID>`. The `<Collection ID>` must be in the valid range,248	/// otherwise `None` is returned.249	///250	/// If the multilocation doesn't match the patterns listed above,251	/// or the `<Collection ID>` points to a foreign collection,252	/// `None` is returned, identifying that the given multilocation doesn't correspond to a local collection.253	fn local_asset_id_to_collection(asset_id: &AssetId) -> Option<CollectionLocality> {254		let AssetId::Concrete(asset_location) = asset_id else {255			return None;256		};257258		let self_location = T::SelfLocation::get();259260		if *asset_location == Here.into() || *asset_location == self_location {261			Some(CollectionLocality::Local(NATIVE_FUNGIBLE_COLLECTION_ID))262		} else if asset_location.parents == self_location.parents {263			match asset_location264				.interior265				.match_and_split(&self_location.interior)266			{267				Some(GeneralIndex(collection_id)) => {268					let collection_id = CollectionId((*collection_id).try_into().ok()?);269270					Self::collection_to_foreign_asset(collection_id)271						.is_none()272						.then_some(CollectionLocality::Local(collection_id))273				}274				_ => None,275			}276		} else {277			None278		}279	}280281	/// Converts an asset ID to a Unique Network's collection locality (either foreign or a local one).282	///283	/// The function will check if the asset's reserve location has the corresponding284	/// foreign collection on Unique Network,285	/// and will return the "foreign" locality containing the collection ID if found.286	///287	/// If no corresponding foreign collection is found, the function will check288	/// if the asset's reserve location corresponds to a local collection.289	/// If the local collection is found, the "local" locality with the collection ID is returned.290	///291	/// If all of the above have failed, the `AssetIdConversionFailed` error will be returned.292	fn asset_to_collection(asset_id: &AssetId) -> Result<CollectionLocality, XcmError> {293		Self::foreign_asset_to_collection(asset_id)294			.map(CollectionLocality::Foreign)295			.or_else(|| Self::local_asset_id_to_collection(asset_id))296			.ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())297	}298299	/// Converts an XCM asset instance of local collection to the Unique Network's token ID.300	///301	/// The asset instance corresponds to the Unique Network's token ID if it is in the following format:302	/// `AssetInstance::Index(<token ID>)`.303	///304	/// If the asset instance is not in the valid format or the `<token ID>` can't fit into the valid token ID,305	/// `None` will be returned.306	fn local_asset_instance_to_token_id(asset_instance: &AssetInstance) -> Option<TokenId> {307		match asset_instance {308			AssetInstance::Index(token_id) => Some(TokenId((*token_id).try_into().ok()?)),309			_ => None,310		}311	}312313	/// Obtains the token ID of the `asset_instance` in the collection.314	fn asset_instance_to_token_id(315		collection_locality: CollectionLocality,316		asset_instance: &AssetInstance,317	) -> Option<TokenId> {318		match collection_locality {319			CollectionLocality::Local(_) => Self::local_asset_instance_to_token_id(asset_instance),320			CollectionLocality::Foreign(collection_id) => {321				Self::foreign_reserve_asset_instance_to_token_id(collection_id, asset_instance)322			}323		}324	}325326	/// Creates a foreign item in the the collection.327	fn create_foreign_asset_instance(328		xcm_ext: &dyn XcmExtensions<T>,329		collection_id: CollectionId,330		asset_instance: &AssetInstance,331		to: T::CrossAccountId,332	) -> DispatchResult {333		let asset_instance_encoded = asset_instance.encode();334335		let derivative_token_id = xcm_ext.create_item(336			&Self::pallet_account(),337			to,338			CreateItemData::NFT(CreateNftData {339				properties: vec![Property {340					key: Self::reserve_asset_instance_property_key(),341					value: asset_instance_encoded342						.try_into()343						.expect("asset instance length <= 32 bytes which is less than value length limit; qed"),344				}]345				.try_into()346				.expect("just one property can always be stored; qed"),347			}),348			&ZeroBudget,349		)?;350351		<ForeignReserveAssetInstanceToTokenId<T>>::insert(352			collection_id,353			asset_instance,354			derivative_token_id,355		);356357		<TokenIdToForeignReserveAssetInstance<T>>::insert(358			collection_id,359			derivative_token_id,360			asset_instance,361		);362363		Ok(())364	}365366	/// Deposits an asset instance to the `to` account.367	///368	/// Either transfers an existing item from the pallet's account369	/// or creates a foreign item.370	fn deposit_asset_instance(371		xcm_ext: &dyn XcmExtensions<T>,372		collection_locality: CollectionLocality,373		asset_instance: &AssetInstance,374		to: T::CrossAccountId,375	) -> XcmResult {376		let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance);377378		let deposit_result = match (collection_locality, token_id) {379			(_, Some(token_id)) => {380				let depositor = &Self::pallet_account();381				let from = depositor;382				let amount = 1;383384				xcm_ext.transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)385			}386			(CollectionLocality::Foreign(collection_id), None) => {387				Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)388			}389			(CollectionLocality::Local(_), None) => {390				return Err(XcmError::AssetNotFound);391			}392		};393394		deposit_result395			.map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))396	}397398	/// Withdraws an asset instance from the `from` account.399	///400	/// Transfers the asset instance to the pallet's account.401	fn withdraw_asset_instance(402		xcm_ext: &dyn XcmExtensions<T>,403		collection_locality: CollectionLocality,404		asset_instance: &AssetInstance,405		from: T::CrossAccountId,406	) -> XcmResult {407		let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance)408			.ok_or(XcmError::AssetNotFound)?;409410		let depositor = &from;411		let to = Self::pallet_account();412		let amount = 1;413		xcm_ext414			.transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)415			.map_err(|_| XcmError::FailedToTransactAsset("non-fungible item withdraw failed"))?;416417		Ok(())418	}419}420421impl<T: Config> TransactAsset for Pallet<T> {422	fn can_check_in(423		_origin: &MultiLocation,424		_what: &MultiAsset,425		_context: &XcmContext,426	) -> XcmResult {427		Err(XcmError::Unimplemented)428	}429430	fn check_in(_origin: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}431432	fn can_check_out(433		_dest: &MultiLocation,434		_what: &MultiAsset,435		_context: &XcmContext,436	) -> XcmResult {437		Err(XcmError::Unimplemented)438	}439440	fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}441442	fn deposit_asset(what: &MultiAsset, to: &MultiLocation, _context: &XcmContext) -> XcmResult {443		let to = T::LocationToAccountId::convert_location(to)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(|_| XcmError::AssetNotFound)?;449450		let collection = dispatch.as_dyn();451		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;452453		match what.fun {454			Fungibility::Fungible(amount) => xcm_ext455				.create_item(456					&Self::pallet_account(),457					to,458					CreateItemData::Fungible(CreateFungibleData { value: amount }),459					&ZeroBudget,460				)461				.map(|_| ())462				.map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),463464			Fungibility::NonFungible(asset_instance) => {465				Self::deposit_asset_instance(xcm_ext, collection_locality, &asset_instance, to)466			}467		}468	}469470	fn withdraw_asset(471		what: &MultiAsset,472		from: &MultiLocation,473		_maybe_context: Option<&XcmContext>,474	) -> Result<staging_xcm_executor::Assets, XcmError> {475		let from = T::LocationToAccountId::convert_location(from)476			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;477478		let collection_locality = Self::asset_to_collection(&what.id)?;479		let dispatch = T::CollectionDispatch::dispatch(*collection_locality)480			.map_err(|_| XcmError::AssetNotFound)?;481482		let collection = dispatch.as_dyn();483		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;484485		match what.fun {486			Fungibility::Fungible(amount) => xcm_ext487				.burn_item(from, TokenId::default(), amount)488				.map_err(|_| XcmError::FailedToTransactAsset("fungible item withdraw failed"))?,489490			Fungibility::NonFungible(asset_instance) => {491				Self::withdraw_asset_instance(xcm_ext, collection_locality, &asset_instance, from)?;492			}493		}494495		Ok(what.clone().into())496	}497498	fn internal_transfer_asset(499		what: &MultiAsset,500		from: &MultiLocation,501		to: &MultiLocation,502		_context: &XcmContext,503	) -> Result<staging_xcm_executor::Assets, XcmError> {504		let from = T::LocationToAccountId::convert_location(from)505			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;506507		let to = T::LocationToAccountId::convert_location(to)508			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;509510		let collection_locality = Self::asset_to_collection(&what.id)?;511512		let dispatch = T::CollectionDispatch::dispatch(*collection_locality)513			.map_err(|_| XcmError::AssetNotFound)?;514		let collection = dispatch.as_dyn();515		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;516517		let depositor = &from;518519		let token_id;520		let amount;521		let map_error: fn(DispatchError) -> XcmError;522523		match what.fun {524			Fungibility::Fungible(fungible_amount) => {525				token_id = TokenId::default();526				amount = fungible_amount;527				map_error = |_| XcmError::FailedToTransactAsset("fungible item transfer failed");528			}529530			Fungibility::NonFungible(asset_instance) => {531				token_id = Self::asset_instance_to_token_id(collection_locality, &asset_instance)532					.ok_or(XcmError::AssetNotFound)?;533534				amount = 1;535				map_error = |_| XcmError::FailedToTransactAsset("non-fungible item transfer failed")536			}537		}538539		xcm_ext540			.transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)541			.map_err(map_error)?;542543		Ok(what.clone().into())544	}545}546547#[derive(Clone, Copy)]548pub enum CollectionLocality {549	Local(CollectionId),550	Foreign(CollectionId),551}552553impl Deref for CollectionLocality {554	type Target = CollectionId;555556	fn deref(&self) -> &Self::Target {557		match self {558			Self::Local(id) => id,559			Self::Foreign(id) => id,560		}561	}562}563564pub struct CurrencyIdConvert<T: Config>(PhantomData<T>);565impl<T: Config> sp_runtime::traits::Convert<CollectionId, Option<MultiLocation>>566	for CurrencyIdConvert<T>567{568	fn convert(collection_id: CollectionId) -> Option<MultiLocation> {569		if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {570			Some(T::SelfLocation::get())571		} else {572			<Pallet<T>>::collection_to_foreign_asset(collection_id)573				.and_then(|asset_id| match asset_id {574					AssetId::Concrete(location) => Some(location),575					_ => None,576				})577				.or_else(|| {578					T::SelfLocation::get()579						.pushed_with_interior(GeneralIndex(collection_id.0.into()))580						.ok()581				})582		}583	}584}585586#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]587pub enum ForeignCollectionMode {588	NFT,589	Fungible(u8),590}591592impl From<ForeignCollectionMode> for CollectionMode {593	fn from(value: ForeignCollectionMode) -> Self {594		match value {595			ForeignCollectionMode::NFT => Self::NFT,596			ForeignCollectionMode::Fungible(decimals) => Self::Fungible(decimals),597		}598	}599}600601pub struct FreeForAll;602603impl WeightTrader for FreeForAll {604	fn new() -> Self {605		Self606	}607608	fn buy_weight(609		&mut self,610		weight: Weight,611		payment: Assets,612		_xcm: &XcmContext,613	) -> Result<Assets, XcmError> {614		log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);615		Ok(payment)616	}617}