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

difftreelog

fix use versioned asset ID in force_register_foreign_asset

Daniel Shiposha2023-11-27parent: #b0dfc97.patch.diff
in: master

2 files changed

modifiedjs-packages/playgrounds/unique.xcm.tsdiffbeforeafterboth
--- a/js-packages/playgrounds/unique.xcm.ts
+++ b/js-packages/playgrounds/unique.xcm.ts
@@ -108,7 +108,7 @@
     await this.helper.executeExtrinsic(
       signer,
       'api.tx.foreignAssets.forceRegisterForeignAsset',
-      [assetId, this.helper.util.str2vec(name), tokenPrefix, mode],
+      [{V3: assetId}, this.helper.util.str2vec(name), tokenPrefix, mode],
       true,
     );
   }
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, TokenId,46};4748pub mod weights;4950#[cfg(feature = "runtime-benchmarks")]51mod benchmarking;5253pub use module::*;54pub use weights::WeightInfo;5556#[frame_support::pallet]57pub mod module {58	use pallet_common::CollectionIssuer;59	use up_data_structs::CollectionDescription;6061	use super::*;6263	#[pallet::config]64	pub trait Config:65		frame_system::Config66		+ pallet_common::Config67		+ pallet_fungible::Config68		+ pallet_balances::Config69	{70		/// The overarching event type.71		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;7273		/// Origin for force registering of a foreign asset.74		type ForceRegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;7576		/// The ID of the foreign assets pallet.77		type PalletId: Get<PalletId>;7879		/// Self-location of this parachain.80		type SelfLocation: Get<MultiLocation>;8182		/// The converter from a MultiLocation to a CrossAccountId.83		type LocationToAccountId: ConvertLocation<Self::CrossAccountId>;8485		/// Weight information for the extrinsics in this module.86		type WeightInfo: WeightInfo;87	}8889	#[pallet::error]90	pub enum Error<T> {91		/// The foreign asset is already registered.92		ForeignAssetAlreadyRegistered,93	}9495	#[pallet::event]96	#[pallet::generate_deposit(fn deposit_event)]97	pub enum Event<T: Config> {98		/// The foreign asset registered.99		ForeignAssetRegistered {100			collection_id: CollectionId,101			asset_id: Box<AssetId>,102		},103	}104105	/// The corresponding collections of foreign assets.106	#[pallet::storage]107	#[pallet::getter(fn foreign_asset_to_collection)]108	pub type ForeignAssetToCollection<T: Config> =109		StorageMap<_, Twox64Concat, staging_xcm::v3::AssetId, CollectionId, OptionQuery>;110111	/// The corresponding foreign assets of collections.112	#[pallet::storage]113	#[pallet::getter(fn collection_to_foreign_asset)]114	pub type CollectionToForeignAsset<T: Config> =115		StorageMap<_, Twox64Concat, CollectionId, staging_xcm::v3::AssetId, OptionQuery>;116117	/// The correponding NFT token id of reserve NFTs118	#[pallet::storage]119	#[pallet::getter(fn foreign_reserve_asset_instance_to_token_id)]120	pub type ForeignReserveAssetInstanceToTokenId<T: Config> = StorageDoubleMap<121		Hasher1 = Twox64Concat,122		Key1 = CollectionId,123		Hasher2 = Blake2_128Concat,124		Key2 = staging_xcm::v3::AssetInstance,125		Value = TokenId,126		QueryKind = OptionQuery,127	>;128129	/// The correponding reserve NFT of a token ID130	#[pallet::storage]131	#[pallet::getter(fn token_id_to_foreign_reserve_asset_instance)]132	pub type TokenIdToForeignReserveAssetInstance<T: Config> = StorageDoubleMap<133		Hasher1 = Twox64Concat,134		Key1 = CollectionId,135		Hasher2 = Blake2_128Concat,136		Key2 = TokenId,137		Value = staging_xcm::v3::AssetInstance,138		QueryKind = OptionQuery,139	>;140141	#[pallet::pallet]142	pub struct Pallet<T>(_);143144	#[pallet::call]145	impl<T: Config> Pallet<T> {146		#[pallet::call_index(0)]147		#[pallet::weight(<T as Config>::WeightInfo::register_foreign_asset())]148		pub fn force_register_foreign_asset(149			origin: OriginFor<T>,150			asset_id: Box<AssetId>,151			name: CollectionName,152			token_prefix: CollectionTokenPrefix,153			mode: ForeignCollectionMode,154		) -> DispatchResult {155			T::ForceRegisterOrigin::ensure_origin(origin.clone())?;156157			ensure!(158				!<ForeignAssetToCollection<T>>::contains_key(*asset_id),159				<Error<T>>::ForeignAssetAlreadyRegistered,160			);161162			let foreign_collection_owner = Self::pallet_account();163164			let description: CollectionDescription = "Foreign Assets Collection"165				.encode_utf16()166				.collect::<Vec<_>>()167				.try_into()168				.expect("description length < max description length; qed");169170			let collection_id = T::CollectionDispatch::create(171				foreign_collection_owner,172				CollectionIssuer::Internals,173				CreateCollectionData {174					name,175					token_prefix,176					description,177					mode: mode.into(),178					..Default::default()179				},180			)?;181182			<ForeignAssetToCollection<T>>::insert(*asset_id, collection_id);183			<CollectionToForeignAsset<T>>::insert(collection_id, *asset_id);184185			Self::deposit_event(Event::<T>::ForeignAssetRegistered {186				collection_id,187				asset_id,188			});189190			Ok(())191		}192	}193}194195impl<T: Config> Pallet<T> {196	fn pallet_account() -> T::CrossAccountId {197		let owner: T::AccountId = T::PalletId::get().into_account_truncating();198		T::CrossAccountId::from_sub(owner)199	}200201	/// Converts a concrete asset ID (the asset multilocation) to a local collection on Unique Network.202	///203	/// The multilocation corresponds to a local collection if:204	/// * It is `Here` location that corresponds to the native token of this parachain.205	/// * It is `../Parachain(<Unique Network Para ID>)` that also corresponds to the native token of this parachain.206	/// * It is `../Parachain(<Unique Network Para ID>)/GeneralIndex(<Collection ID>)` that corresponds207	/// to the collection with the ID equal to `<Collection ID>`. The `<Collection ID>` must be in the valid range,208	/// otherwise `None` is returned.209	/// * It is `GeneralIndex(<Collection ID>)`. Same as the last one above.210	///211	/// If the multilocation doesn't match the patterns listed above,212	/// or the `<Collection ID>` points to a foreign collection,213	/// `None` is returned, identifying that the given multilocation doesn't correspond to a local collection.214	fn local_asset_id_to_collection(asset_id: &AssetId) -> Option<CollectionLocality> {215		let AssetId::Concrete(asset_location) = asset_id else {216			return None;217		};218219		let self_location = T::SelfLocation::get();220221		if *asset_location == Here.into() || *asset_location == self_location {222			return Some(CollectionLocality::Local(NATIVE_FUNGIBLE_COLLECTION_ID));223		}224225		let prefix = if asset_location.parents == 0 {226			&Here227		} else if asset_location.parents == self_location.parents {228			&self_location.interior229		} else {230			return None;231		};232233		let GeneralIndex(collection_id) = asset_location.interior.match_and_split(prefix)? else {234			return None;235		};236237		let collection_id = CollectionId((*collection_id).try_into().ok()?);238239		Self::collection_to_foreign_asset(collection_id)240			.is_none()241			.then_some(CollectionLocality::Local(collection_id))242	}243244	/// Converts an asset ID to a Unique Network's collection locality (either foreign or a local one).245	///246	/// The function will check if the asset's reserve location has the corresponding247	/// foreign collection on Unique Network,248	/// and will return the "foreign" locality containing the collection ID if found.249	///250	/// If no corresponding foreign collection is found, the function will check251	/// if the asset's reserve location corresponds to a local collection.252	/// If the local collection is found, the "local" locality with the collection ID is returned.253	///254	/// If all of the above have failed, the `AssetIdConversionFailed` error will be returned.255	fn asset_to_collection(asset_id: &AssetId) -> Result<CollectionLocality, XcmError> {256		Self::foreign_asset_to_collection(asset_id)257			.map(CollectionLocality::Foreign)258			.or_else(|| Self::local_asset_id_to_collection(asset_id))259			.ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())260	}261262	/// Converts an XCM asset instance of local collection to the Unique Network's token ID.263	///264	/// The asset instance corresponds to the Unique Network's token ID if it is in the following format:265	/// `AssetInstance::Index(<token ID>)`.266	///267	/// If the asset instance is not in the valid format or the `<token ID>` can't fit into the valid token ID,268	/// `None` will be returned.269	fn local_asset_instance_to_token_id(asset_instance: &AssetInstance) -> Option<TokenId> {270		match asset_instance {271			AssetInstance::Index(token_id) => Some(TokenId((*token_id).try_into().ok()?)),272			_ => None,273		}274	}275276	/// Obtains the token ID of the `asset_instance` in the collection.277	fn asset_instance_to_token_id(278		collection_locality: CollectionLocality,279		asset_instance: &AssetInstance,280	) -> Option<TokenId> {281		match collection_locality {282			CollectionLocality::Local(_) => Self::local_asset_instance_to_token_id(asset_instance),283			CollectionLocality::Foreign(collection_id) => {284				Self::foreign_reserve_asset_instance_to_token_id(collection_id, asset_instance)285			}286		}287	}288289	/// Creates a foreign item in the the collection.290	fn create_foreign_asset_instance(291		xcm_ext: &dyn XcmExtensions<T>,292		collection_id: CollectionId,293		asset_instance: &AssetInstance,294		to: T::CrossAccountId,295	) -> DispatchResult {296		let derivative_token_id = xcm_ext.create_item(297			&Self::pallet_account(),298			to,299			CreateItemData::NFT(Default::default()),300			&ZeroBudget,301		)?;302303		<ForeignReserveAssetInstanceToTokenId<T>>::insert(304			collection_id,305			asset_instance,306			derivative_token_id,307		);308309		<TokenIdToForeignReserveAssetInstance<T>>::insert(310			collection_id,311			derivative_token_id,312			asset_instance,313		);314315		Ok(())316	}317318	/// Deposits an asset instance to the `to` account.319	///320	/// Either transfers an existing item from the pallet's account321	/// or creates a foreign item.322	fn deposit_asset_instance(323		xcm_ext: &dyn XcmExtensions<T>,324		collection_locality: CollectionLocality,325		asset_instance: &AssetInstance,326		to: T::CrossAccountId,327	) -> XcmResult {328		let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance);329330		let deposit_result = match (collection_locality, token_id) {331			(_, Some(token_id)) => {332				let depositor = &Self::pallet_account();333				let from = depositor;334				let amount = 1;335336				xcm_ext.transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)337			}338			(CollectionLocality::Foreign(collection_id), None) => {339				Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)340			}341			(CollectionLocality::Local(_), None) => {342				return Err(XcmExecutorError::InstanceConversionFailed.into());343			}344		};345346		deposit_result347			.map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))348	}349350	/// Withdraws an asset instance from the `from` account.351	///352	/// Transfers the asset instance to the pallet's account.353	fn withdraw_asset_instance(354		xcm_ext: &dyn XcmExtensions<T>,355		collection_locality: CollectionLocality,356		asset_instance: &AssetInstance,357		from: T::CrossAccountId,358	) -> XcmResult {359		let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance)360			.ok_or(XcmExecutorError::InstanceConversionFailed)?;361362		let depositor = &from;363		let to = Self::pallet_account();364		let amount = 1;365		xcm_ext366			.transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)367			.map_err(|_| XcmError::FailedToTransactAsset("non-fungible item withdraw failed"))?;368369		Ok(())370	}371}372373impl<T: Config> TransactAsset for Pallet<T> {374	fn can_check_in(375		_origin: &MultiLocation,376		_what: &MultiAsset,377		_context: &XcmContext,378	) -> XcmResult {379		Err(XcmError::Unimplemented)380	}381382	fn check_in(_origin: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}383384	fn can_check_out(385		_dest: &MultiLocation,386		_what: &MultiAsset,387		_context: &XcmContext,388	) -> XcmResult {389		Err(XcmError::Unimplemented)390	}391392	fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}393394	fn deposit_asset(395		what: &MultiAsset,396		to: &MultiLocation,397		_context: Option<&XcmContext>,398	) -> XcmResult {399		let to = T::LocationToAccountId::convert_location(to)400			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;401402		let collection_locality = Self::asset_to_collection(&what.id)?;403		let dispatch = T::CollectionDispatch::dispatch(*collection_locality)404			.map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;405406		let collection = dispatch.as_dyn();407		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;408409		match what.fun {410			Fungibility::Fungible(amount) => xcm_ext411				.create_item(412					&Self::pallet_account(),413					to,414					CreateItemData::Fungible(CreateFungibleData { value: amount }),415					&ZeroBudget,416				)417				.map(|_| ())418				.map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),419420			Fungibility::NonFungible(asset_instance) => {421				Self::deposit_asset_instance(xcm_ext, collection_locality, &asset_instance, to)422			}423		}424	}425426	fn withdraw_asset(427		what: &MultiAsset,428		from: &MultiLocation,429		_maybe_context: Option<&XcmContext>,430	) -> Result<staging_xcm_executor::Assets, XcmError> {431		let from = T::LocationToAccountId::convert_location(from)432			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;433434		let collection_locality = Self::asset_to_collection(&what.id)?;435		let dispatch = T::CollectionDispatch::dispatch(*collection_locality)436			.map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;437438		let collection = dispatch.as_dyn();439		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;440441		match what.fun {442			Fungibility::Fungible(amount) => xcm_ext443				.burn_item(from, TokenId::default(), amount)444				.map_err(|_| XcmError::FailedToTransactAsset("fungible item withdraw failed"))?,445446			Fungibility::NonFungible(asset_instance) => {447				Self::withdraw_asset_instance(xcm_ext, collection_locality, &asset_instance, from)?;448			}449		}450451		Ok(what.clone().into())452	}453454	fn internal_transfer_asset(455		what: &MultiAsset,456		from: &MultiLocation,457		to: &MultiLocation,458		_context: &XcmContext,459	) -> Result<staging_xcm_executor::Assets, XcmError> {460		let from = T::LocationToAccountId::convert_location(from)461			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;462463		let to = T::LocationToAccountId::convert_location(to)464			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;465466		let collection_locality = Self::asset_to_collection(&what.id)?;467468		let dispatch = T::CollectionDispatch::dispatch(*collection_locality)469			.map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;470		let collection = dispatch.as_dyn();471		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;472473		let depositor = &from;474475		let token_id;476		let amount;477		let map_error: fn(DispatchError) -> XcmError;478479		match what.fun {480			Fungibility::Fungible(fungible_amount) => {481				token_id = TokenId::default();482				amount = fungible_amount;483				map_error = |_| XcmError::FailedToTransactAsset("fungible item transfer failed");484			}485486			Fungibility::NonFungible(asset_instance) => {487				token_id = Self::asset_instance_to_token_id(collection_locality, &asset_instance)488					.ok_or(XcmExecutorError::InstanceConversionFailed)?;489490				amount = 1;491				map_error = |_| XcmError::FailedToTransactAsset("non-fungible item transfer failed")492			}493		}494495		xcm_ext496			.transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)497			.map_err(map_error)?;498499		Ok(what.clone().into())500	}501}502503#[derive(Clone, Copy)]504pub enum CollectionLocality {505	Local(CollectionId),506	Foreign(CollectionId),507}508509impl Deref for CollectionLocality {510	type Target = CollectionId;511512	fn deref(&self) -> &Self::Target {513		match self {514			Self::Local(id) => id,515			Self::Foreign(id) => id,516		}517	}518}519520pub struct CurrencyIdConvert<T: Config>(PhantomData<T>);521impl<T: Config> sp_runtime::traits::Convert<CollectionId, Option<MultiLocation>>522	for CurrencyIdConvert<T>523{524	fn convert(collection_id: CollectionId) -> Option<MultiLocation> {525		if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {526			Some(T::SelfLocation::get())527		} else {528			<Pallet<T>>::collection_to_foreign_asset(collection_id)529				.and_then(|asset_id| match asset_id {530					AssetId::Concrete(location) => Some(location),531					_ => None,532				})533				.or_else(|| {534					T::SelfLocation::get()535						.pushed_with_interior(GeneralIndex(collection_id.0.into()))536						.ok()537				})538		}539	}540}541542#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]543pub enum ForeignCollectionMode {544	NFT,545	Fungible(u8),546}547548impl From<ForeignCollectionMode> for CollectionMode {549	fn from(value: ForeignCollectionMode) -> Self {550		match value {551			ForeignCollectionMode::NFT => Self::NFT,552			ForeignCollectionMode::Fungible(decimals) => Self::Fungible(decimals),553		}554	}555}556557pub struct FreeForAll;558559impl WeightTrader for FreeForAll {560	fn new() -> Self {561		Self562	}563564	fn buy_weight(565		&mut self,566		weight: Weight,567		payment: Assets,568		_xcm: &XcmContext,569	) -> Result<Assets, XcmError> {570		log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);571		Ok(payment)572	}573}