git.delta.rocks / unique-network / refs/commits / 05c886f6b558

difftreelog

fix take parents:0 multilocations into account

Daniel Shiposha2023-11-16parent: #046e9b6.patch.diff
in: master

1 file changed

modifiedpallets/foreign-assets/src/lib.rsdiffbeforeafterboth
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	/// * It is `GeneralIndex(<Collection ID>)`. Same as the last one above.250	///251	/// If the multilocation doesn't match the patterns listed above,252	/// or the `<Collection ID>` points to a foreign collection,253	/// `None` is returned, identifying that the given multilocation doesn't correspond to a local collection.254	fn local_asset_id_to_collection(asset_id: &AssetId) -> Option<CollectionLocality> {255		let AssetId::Concrete(asset_location) = asset_id else {256			return None;257		};258259		let self_location = T::SelfLocation::get();260261		if *asset_location == Here.into() || *asset_location == self_location {262			return Some(CollectionLocality::Local(NATIVE_FUNGIBLE_COLLECTION_ID));263		}264265		let collection_junction = if asset_location.parents == 0 {266			match &asset_location.interior {267				X1(junction) => junction,268				_ => return None,269			}270		} else if asset_location.parents == self_location.parents {271			asset_location272				.interior273				.match_and_split(&self_location.interior)?274		} else {275			return None;276		};277278		let GeneralIndex(collection_id) = collection_junction else {279			return None;280		};281282		let collection_id = CollectionId((*collection_id).try_into().ok()?);283284		Self::collection_to_foreign_asset(collection_id)285			.is_none()286			.then_some(CollectionLocality::Local(collection_id))287	}288289	/// Converts an asset ID to a Unique Network's collection locality (either foreign or a local one).290	///291	/// The function will check if the asset's reserve location has the corresponding292	/// foreign collection on Unique Network,293	/// and will return the "foreign" locality containing the collection ID if found.294	///295	/// If no corresponding foreign collection is found, the function will check296	/// if the asset's reserve location corresponds to a local collection.297	/// If the local collection is found, the "local" locality with the collection ID is returned.298	///299	/// If all of the above have failed, the `AssetIdConversionFailed` error will be returned.300	fn asset_to_collection(asset_id: &AssetId) -> Result<CollectionLocality, XcmError> {301		Self::foreign_asset_to_collection(asset_id)302			.map(CollectionLocality::Foreign)303			.or_else(|| Self::local_asset_id_to_collection(asset_id))304			.ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())305	}306307	/// Converts an XCM asset instance of local collection to the Unique Network's token ID.308	///309	/// The asset instance corresponds to the Unique Network's token ID if it is in the following format:310	/// `AssetInstance::Index(<token ID>)`.311	///312	/// If the asset instance is not in the valid format or the `<token ID>` can't fit into the valid token ID,313	/// `None` will be returned.314	fn local_asset_instance_to_token_id(asset_instance: &AssetInstance) -> Option<TokenId> {315		match asset_instance {316			AssetInstance::Index(token_id) => Some(TokenId((*token_id).try_into().ok()?)),317			_ => None,318		}319	}320321	/// Obtains the token ID of the `asset_instance` in the collection.322	fn asset_instance_to_token_id(323		collection_locality: CollectionLocality,324		asset_instance: &AssetInstance,325	) -> Option<TokenId> {326		match collection_locality {327			CollectionLocality::Local(_) => Self::local_asset_instance_to_token_id(asset_instance),328			CollectionLocality::Foreign(collection_id) => {329				Self::foreign_reserve_asset_instance_to_token_id(collection_id, asset_instance)330			}331		}332	}333334	/// Creates a foreign item in the the collection.335	fn create_foreign_asset_instance(336		xcm_ext: &dyn XcmExtensions<T>,337		collection_id: CollectionId,338		asset_instance: &AssetInstance,339		to: T::CrossAccountId,340	) -> DispatchResult {341		let asset_instance_encoded = asset_instance.encode();342343		let derivative_token_id = xcm_ext.create_item(344			&Self::pallet_account(),345			to,346			CreateItemData::NFT(CreateNftData {347				properties: vec![Property {348					key: Self::reserve_asset_instance_property_key(),349					value: asset_instance_encoded350						.try_into()351						.expect("asset instance length <= 32 bytes which is less than value length limit; qed"),352				}]353				.try_into()354				.expect("just one property can always be stored; qed"),355			}),356			&ZeroBudget,357		)?;358359		<ForeignReserveAssetInstanceToTokenId<T>>::insert(360			collection_id,361			asset_instance,362			derivative_token_id,363		);364365		<TokenIdToForeignReserveAssetInstance<T>>::insert(366			collection_id,367			derivative_token_id,368			asset_instance,369		);370371		Ok(())372	}373374	/// Deposits an asset instance to the `to` account.375	///376	/// Either transfers an existing item from the pallet's account377	/// or creates a foreign item.378	fn deposit_asset_instance(379		xcm_ext: &dyn XcmExtensions<T>,380		collection_locality: CollectionLocality,381		asset_instance: &AssetInstance,382		to: T::CrossAccountId,383	) -> XcmResult {384		let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance);385386		let deposit_result = match (collection_locality, token_id) {387			(_, Some(token_id)) => {388				let depositor = &Self::pallet_account();389				let from = depositor;390				let amount = 1;391392				xcm_ext.transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)393			}394			(CollectionLocality::Foreign(collection_id), None) => {395				Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)396			}397			(CollectionLocality::Local(_), None) => {398				return Err(XcmError::AssetNotFound);399			}400		};401402		deposit_result403			.map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))404	}405406	/// Withdraws an asset instance from the `from` account.407	///408	/// Transfers the asset instance to the pallet's account.409	fn withdraw_asset_instance(410		xcm_ext: &dyn XcmExtensions<T>,411		collection_locality: CollectionLocality,412		asset_instance: &AssetInstance,413		from: T::CrossAccountId,414	) -> XcmResult {415		let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance)416			.ok_or(XcmError::AssetNotFound)?;417418		let depositor = &from;419		let to = Self::pallet_account();420		let amount = 1;421		xcm_ext422			.transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)423			.map_err(|_| XcmError::FailedToTransactAsset("non-fungible item withdraw failed"))?;424425		Ok(())426	}427}428429impl<T: Config> TransactAsset for Pallet<T> {430	fn can_check_in(431		_origin: &MultiLocation,432		_what: &MultiAsset,433		_context: &XcmContext,434	) -> XcmResult {435		Err(XcmError::Unimplemented)436	}437438	fn check_in(_origin: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}439440	fn can_check_out(441		_dest: &MultiLocation,442		_what: &MultiAsset,443		_context: &XcmContext,444	) -> XcmResult {445		Err(XcmError::Unimplemented)446	}447448	fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}449450	fn deposit_asset(what: &MultiAsset, to: &MultiLocation, _context: &XcmContext) -> XcmResult {451		let to = T::LocationToAccountId::convert_location(to)452			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;453454		let collection_locality = Self::asset_to_collection(&what.id)?;455		let dispatch = T::CollectionDispatch::dispatch(*collection_locality)456			.map_err(|_| XcmError::AssetNotFound)?;457458		let collection = dispatch.as_dyn();459		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;460461		match what.fun {462			Fungibility::Fungible(amount) => xcm_ext463				.create_item(464					&Self::pallet_account(),465					to,466					CreateItemData::Fungible(CreateFungibleData { value: amount }),467					&ZeroBudget,468				)469				.map(|_| ())470				.map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),471472			Fungibility::NonFungible(asset_instance) => {473				Self::deposit_asset_instance(xcm_ext, collection_locality, &asset_instance, to)474			}475		}476	}477478	fn withdraw_asset(479		what: &MultiAsset,480		from: &MultiLocation,481		_maybe_context: Option<&XcmContext>,482	) -> Result<staging_xcm_executor::Assets, XcmError> {483		let from = T::LocationToAccountId::convert_location(from)484			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;485486		let collection_locality = Self::asset_to_collection(&what.id)?;487		let dispatch = T::CollectionDispatch::dispatch(*collection_locality)488			.map_err(|_| XcmError::AssetNotFound)?;489490		let collection = dispatch.as_dyn();491		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;492493		match what.fun {494			Fungibility::Fungible(amount) => xcm_ext495				.burn_item(from, TokenId::default(), amount)496				.map_err(|_| XcmError::FailedToTransactAsset("fungible item withdraw failed"))?,497498			Fungibility::NonFungible(asset_instance) => {499				Self::withdraw_asset_instance(xcm_ext, collection_locality, &asset_instance, from)?;500			}501		}502503		Ok(what.clone().into())504	}505506	fn internal_transfer_asset(507		what: &MultiAsset,508		from: &MultiLocation,509		to: &MultiLocation,510		_context: &XcmContext,511	) -> Result<staging_xcm_executor::Assets, XcmError> {512		let from = T::LocationToAccountId::convert_location(from)513			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;514515		let to = T::LocationToAccountId::convert_location(to)516			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;517518		let collection_locality = Self::asset_to_collection(&what.id)?;519520		let dispatch = T::CollectionDispatch::dispatch(*collection_locality)521			.map_err(|_| XcmError::AssetNotFound)?;522		let collection = dispatch.as_dyn();523		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;524525		let depositor = &from;526527		let token_id;528		let amount;529		let map_error: fn(DispatchError) -> XcmError;530531		match what.fun {532			Fungibility::Fungible(fungible_amount) => {533				token_id = TokenId::default();534				amount = fungible_amount;535				map_error = |_| XcmError::FailedToTransactAsset("fungible item transfer failed");536			}537538			Fungibility::NonFungible(asset_instance) => {539				token_id = Self::asset_instance_to_token_id(collection_locality, &asset_instance)540					.ok_or(XcmError::AssetNotFound)?;541542				amount = 1;543				map_error = |_| XcmError::FailedToTransactAsset("non-fungible item transfer failed")544			}545		}546547		xcm_ext548			.transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)549			.map_err(map_error)?;550551		Ok(what.clone().into())552	}553}554555#[derive(Clone, Copy)]556pub enum CollectionLocality {557	Local(CollectionId),558	Foreign(CollectionId),559}560561impl Deref for CollectionLocality {562	type Target = CollectionId;563564	fn deref(&self) -> &Self::Target {565		match self {566			Self::Local(id) => id,567			Self::Foreign(id) => id,568		}569	}570}571572pub struct CurrencyIdConvert<T: Config>(PhantomData<T>);573impl<T: Config> sp_runtime::traits::Convert<CollectionId, Option<MultiLocation>>574	for CurrencyIdConvert<T>575{576	fn convert(collection_id: CollectionId) -> Option<MultiLocation> {577		if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {578			Some(T::SelfLocation::get())579		} else {580			<Pallet<T>>::collection_to_foreign_asset(collection_id)581				.and_then(|asset_id| match asset_id {582					AssetId::Concrete(location) => Some(location),583					_ => None,584				})585				.or_else(|| {586					T::SelfLocation::get()587						.pushed_with_interior(GeneralIndex(collection_id.0.into()))588						.ok()589				})590		}591	}592}593594#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]595pub enum ForeignCollectionMode {596	NFT,597	Fungible(u8),598}599600impl From<ForeignCollectionMode> for CollectionMode {601	fn from(value: ForeignCollectionMode) -> Self {602		match value {603			ForeignCollectionMode::NFT => Self::NFT,604			ForeignCollectionMode::Fungible(decimals) => Self::Fungible(decimals),605		}606	}607}608609pub struct FreeForAll;610611impl WeightTrader for FreeForAll {612	fn new() -> Self {613		Self614	}615616	fn buy_weight(617		&mut self,618		weight: Weight,619		payment: Assets,620		_xcm: &XcmContext,621	) -> Result<Assets, XcmError> {622		log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);623		Ok(payment)624	}625}