git.delta.rocks / unique-network / refs/commits / 05427f7b990f

difftreelog

fix rfts are unsupported in XCM

Daniel Shiposha2023-10-23parent: #a8213d4.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
@@ -18,12 +18,12 @@
 
 use frame_benchmarking::v2::*;
 use frame_system::RawOrigin;
-use pallet_common::benchmarking::create_u16_data;
+use pallet_common::benchmarking::{create_data, create_u16_data};
 use sp_std::vec;
 use staging_xcm::prelude::*;
-use up_data_structs::{CollectionMode, MAX_COLLECTION_NAME_LENGTH};
+use up_data_structs::{MAX_COLLECTION_NAME_LENGTH, MAX_TOKEN_PREFIX_LENGTH};
 
-use super::{Call, Config, Pallet};
+use super::{Call, Config, ForeignCollectionMode, Pallet};
 
 #[benchmarks]
 mod benchmarks {
@@ -35,10 +35,11 @@
 		let location =
 			MultiLocation::from(X3(Parachain(1000), PalletInstance(42), GeneralIndex(1)));
 		let name = create_u16_data::<MAX_COLLECTION_NAME_LENGTH>();
-		let mode = CollectionMode::NFT;
+		let token_prefix = create_data::<MAX_TOKEN_PREFIX_LENGTH>();
+		let mode = ForeignCollectionMode::NFT;
 
 		#[extrinsic_call]
-		_(RawOrigin::Root, location, name, mode);
+		_(RawOrigin::Root, location, name, token_prefix, mode);
 
 		Ok(())
 	}
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 frame_support::{dispatch::DispatchResult, pallet_prelude::*, traits::EnsureOrigin, PalletId};27use frame_system::pallet_prelude::*;28use pallet_common::{29	dispatch::CollectionDispatch, erc::CrossAccountId, XcmExtensions, NATIVE_FUNGIBLE_COLLECTION_ID,30};31use sp_runtime::traits::AccountIdConversion;32use sp_std::{vec, vec::Vec};33use staging_xcm::{34	opaque::latest::{prelude::XcmError, Weight},35	v3::{prelude::*, MultiAsset, XcmContext},36};37use staging_xcm_executor::{38	traits::{ConvertLocation, Error as XcmExecutorError, TransactAsset, WeightTrader},39	Assets,40};41use up_data_structs::{42	budget::ZeroBudget, CollectionId, CollectionMode, CollectionName, CreateCollectionData,43	CreateFungibleData, CreateItemData, CreateNftData, Property, PropertyKey, TokenId,44};4546pub mod weights;4748#[cfg(feature = "runtime-benchmarks")]49mod benchmarking;5051pub use module::*;52pub use weights::WeightInfo;5354#[frame_support::pallet]55pub mod module {56	use up_data_structs::{57		CollectionDescription, Property, PropertyKeyPermission, PropertyPermission,58	};5960	use super::*;6162	#[pallet::config]63	pub trait Config:64		frame_system::Config65		+ pallet_common::Config66		+ pallet_fungible::Config67		+ pallet_balances::Config68	{69		/// The overarching event type.70		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;7172		/// Origin for force registering of a foreign asset.73		type ForceRegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;7475		/// The ID of the foreign assets pallet.76		type PalletId: Get<PalletId>;7778		/// Self-location of this parachain.79		type SelfLocation: Get<MultiLocation>;8081		/// The converter from a MultiLocation to a CrossAccountId.82		type LocationToAccountId: ConvertLocation<Self::CrossAccountId>;8384		/// Weight information for the extrinsics in this module.85		type WeightInfo: WeightInfo;86	}8788	#[pallet::error]89	pub enum Error<T> {90		/// The foreign asset is already registered91		ForeignAssetAlreadyRegistered,92	}9394	#[pallet::event]95	#[pallet::generate_deposit(fn deposit_event)]96	pub enum Event<T: Config> {97		/// The foreign asset registered.98		ForeignAssetRegistered {99			asset_id: CollectionId,100			reserve_location: MultiLocation,101		},102	}103104	/// The corresponding collections of reserve locations.105	#[pallet::storage]106	#[pallet::getter(fn foreign_reserve_location_to_collection)]107	pub type ForeignReserveLocationToCollection<T: Config> =108		StorageMap<_, Twox64Concat, staging_xcm::v3::MultiLocation, CollectionId, OptionQuery>;109110	/// The corresponding reserve location of collections.111	#[pallet::storage]112	#[pallet::getter(fn collection_to_foreign_reserve_location)]113	pub type CollectionToForeignReserveLocation<T: Config> =114		StorageMap<_, Twox64Concat, CollectionId, staging_xcm::v3::MultiLocation, OptionQuery>;115116	/// The correponding NFT token id of reserve NFTs117	#[pallet::storage]118	#[pallet::getter(fn foreign_reserve_asset_instance_to_token_id)]119	pub type ForeignReserveAssetInstanceToTokenId<T: Config> = StorageDoubleMap<120		Hasher1 = Twox64Concat,121		Key1 = CollectionId,122		Hasher2 = Twox64Concat,123		Key2 = staging_xcm::v3::AssetInstance,124		Value = TokenId,125		QueryKind = OptionQuery,126	>;127128	#[pallet::pallet]129	pub struct Pallet<T>(_);130131	#[pallet::call]132	impl<T: Config> Pallet<T> {133		#[pallet::call_index(0)]134		#[pallet::weight(<T as Config>::WeightInfo::register_foreign_asset())]135		pub fn force_register_foreign_asset(136			origin: OriginFor<T>,137			reserve_location: MultiLocation,138			name: CollectionName,139			mode: CollectionMode,140		) -> DispatchResult {141			T::ForceRegisterOrigin::ensure_origin(origin.clone())?;142143			if <ForeignReserveLocationToCollection<T>>::contains_key(reserve_location) {144				return Err(<Error<T>>::ForeignAssetAlreadyRegistered.into());145			}146147			let foreign_collection_owner = Self::pallet_account();148149			let description: CollectionDescription = "Foreign Assets Collection"150				.encode_utf16()151				.collect::<Vec<_>>()152				.try_into()153				.expect("description length < max description length; qed");154155			let payer = None;156			let is_special_collection = true;157			let collection_id = T::CollectionDispatch::create_internal(158				foreign_collection_owner,159				payer,160				is_special_collection,161				CreateCollectionData {162					name,163					description,164					mode,165166					properties: vec![Property {167						key: Self::reserve_location_property_key(),168						value: reserve_location169							.encode()170							.try_into()171							.expect("multilocation is less than 32k; qed"),172					}]173					.try_into()174					.expect("just one property can always be stored; qed"),175176					token_property_permissions: vec![PropertyKeyPermission {177						key: Self::reserve_asset_instance_property_key(),178						permission: PropertyPermission {179							mutable: false,180							collection_admin: true,181							token_owner: false,182						},183					}]184					.try_into()185					.expect("just one property permission can always be stored; qed"),186					..Default::default()187				},188			)?;189190			<ForeignReserveLocationToCollection<T>>::insert(reserve_location, collection_id);191			<CollectionToForeignReserveLocation<T>>::insert(collection_id, reserve_location);192193			Self::deposit_event(Event::<T>::ForeignAssetRegistered {194				asset_id: collection_id,195				reserve_location,196			});197198			Ok(())199		}200	}201}202203impl<T: Config> Pallet<T> {204	fn pallet_account() -> T::CrossAccountId {205		let owner: T::AccountId = T::PalletId::get().into_account_truncating();206		T::CrossAccountId::from_sub(owner)207	}208209	fn reserve_location_property_key() -> PropertyKey {210		b"reserve-location"211			.to_vec()212			.try_into()213			.expect("key length < max property key length; qed")214	}215216	fn reserve_asset_instance_property_key() -> PropertyKey {217		b"reserve-asset-instance"218			.to_vec()219			.try_into()220			.expect("key length < max property key length; qed")221	}222223	/// Converts a multilocation to the Unique Network's local collection224	/// (i.e. the collection originally created on the Unique Network's parachain).225	///226	/// The multilocation corresponds to a Unique Network's collection if:227	/// * It is `Here` location that corresponds to the native token of this parachain.228	/// * It is `../Parachain(<Unique Network Para ID>)` that also corresponds to the native token of this parachain.229	/// * It is `../Parachain(<Unique Network Para ID>)/GeneralIndex(<Collection ID>)` that corresponds230	/// to the collection with the ID equal to `<Collection ID>`. The `<Collection ID>` must be in the valid range,231	/// otherwise the `AssetIdConversionFailed` error will be returned.232	///233	/// If the multilocation doesn't match the patterns listed above, the function returns `Ok(None)`,234	/// identifying that the given multilocation doesn't correspond to a local collection.235	fn native_asset_location_to_collection(236		asset_location: &MultiLocation,237	) -> Result<Option<CollectionId>, XcmError> {238		let self_location = T::SelfLocation::get();239240		if *asset_location == Here.into() {241			Ok(Some(NATIVE_FUNGIBLE_COLLECTION_ID))242		} else if *asset_location == self_location {243			Ok(Some(NATIVE_FUNGIBLE_COLLECTION_ID))244		} else if asset_location.parents == self_location.parents {245			match asset_location246				.interior247				.match_and_split(&self_location.interior)248			{249				Some(GeneralIndex(collection_id)) => Ok(Some(CollectionId(250					(*collection_id)251						.try_into()252						.map_err(|_| XcmExecutorError::AssetIdConversionFailed)?,253				))),254				_ => Ok(None),255			}256		} else {257			Ok(None)258		}259	}260261	/// Converts a multiasset to a Unique Network's collection (either local or the foreign one).262	///263	/// The function will try to convert the multiasset's reserve location264	/// to the Unique Network's local collection.265	///266	/// If the multilocation doesn't correspond to a local collection,267	/// the function will check if the reserve location has the corresponding268	/// derivative Unique Network's collection, and will return the said collection ID if found.269	///270	/// If all of the above have failed, the `AssetIdConversionFailed` error will be returned.271	fn multiasset_to_collection(asset: &MultiAsset) -> Result<CollectionId, XcmError> {272		let AssetId::Concrete(asset_reserve_location) = asset.id else {273			return Err(XcmExecutorError::AssetNotHandled.into());274		};275276		Self::native_asset_location_to_collection(&asset_reserve_location)?277			.or_else(|| Self::foreign_reserve_location_to_collection(asset_reserve_location))278			.ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())279	}280281	/// Converts an XCM asset instance to the Unique Network's token ID.282	///283	/// The asset instance corresponds to the Unique Network's token ID if it is in the following format:284	/// `AssetInstance::Index(<token ID>)`.285	///286	/// If the asset instance is not in the valid format or the `<token ID>` points to a non-existent token,287	/// the `AssetNotFound` error will be returned.288	fn native_asset_instance_to_token_id(289		asset_instance: &AssetInstance,290	) -> Result<TokenId, XcmError> {291		match asset_instance {292			AssetInstance::Index(token_id) => Ok(TokenId(293				(*token_id)294					.try_into()295					.map_err(|_| XcmError::AssetNotFound)?,296			)),297			_ => Err(XcmError::AssetNotFound),298		}299	}300301	/// Obtains the token ID of the `asset_instance` in the collection.302	///303	/// Returns `Ok(None)` only if the `asset_instance` is a part of a foreign collection304	/// and the item hasn't yet been created on this blockchain.305	///306	/// If the `asset_instance` exists and it is a part of a foreign collection, the function will return `Ok(Some(<token ID>))`.307	///308	/// If the `asset_instance` is a part of a local collection,309	/// the function will return either `Ok(Some(<token ID>))` or an error if the token is not found.310	fn asset_instance_to_token_id(311		collection_id: CollectionId,312		asset_instance: &AssetInstance,313	) -> Result<Option<TokenId>, XcmError> {314		if <CollectionToForeignReserveLocation<T>>::contains_key(collection_id) {315			Ok(Self::foreign_reserve_asset_instance_to_token_id(316				collection_id,317				asset_instance,318			))319		} else {320			Self::native_asset_instance_to_token_id(asset_instance).map(Some)321		}322	}323324	/// Creates a foreign item in the the collection.325	fn create_foreign_asset_instance(326		xcm_ext: &dyn XcmExtensions<T>,327		collection_id: CollectionId,328		asset_instance: &AssetInstance,329		to: T::CrossAccountId,330	) -> DispatchResult {331		let asset_instance_encoded = asset_instance.encode();332333		let derivative_token_id = xcm_ext.create_item(334			&Self::pallet_account(),335			to,336			CreateItemData::NFT(CreateNftData {337				properties: vec![Property {338					key: Self::reserve_asset_instance_property_key(),339					value: asset_instance_encoded340						.try_into()341						.expect("asset instance length <= 32 bytes which is less than value length limit; qed"),342				}]343				.try_into()344				.expect("just one property can always be stored; qed"),345			}),346			&ZeroBudget,347		)?;348349		<ForeignReserveAssetInstanceToTokenId<T>>::insert(350			collection_id,351			asset_instance,352			derivative_token_id,353		);354355		Ok(())356	}357358	/// Deposits an asset instance to the `to` account.359	///360	/// Either transfers an existing item from the pallet's account361	/// or creates a foreign item.362	fn deposit_asset_instance(363		xcm_ext: &dyn XcmExtensions<T>,364		collection_id: CollectionId,365		asset_instance: &AssetInstance,366		to: T::CrossAccountId,367	) -> XcmResult {368		let deposit_result = if let Some(token_id) =369			Self::asset_instance_to_token_id(collection_id, asset_instance)?370		{371			let depositor = &Self::pallet_account();372			let from = depositor;373			let amount = 1;374375			xcm_ext.transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)376		} else {377			Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)378		};379380		deposit_result381			.map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))382	}383384	/// Withdraws an asset instance from the `from` account.385	///386	/// Transfers the asset instance to the pallet's account.387	///388	/// Won't withdraw the instance if it has children.389	fn withdraw_asset_instance(390		xcm_ext: &dyn XcmExtensions<T>,391		collection_id: CollectionId,392		asset_instance: &AssetInstance,393		from: T::CrossAccountId,394	) -> XcmResult {395		let token_id = Self::asset_instance_to_token_id(collection_id, &asset_instance)?396			.ok_or(XcmError::AssetNotFound)?;397398		if xcm_ext.token_has_children(token_id) {399			return Err(XcmError::Unimplemented);400		}401402		let depositor = &from;403		let to = Self::pallet_account();404		let amount = 1;405		xcm_ext406			.transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)407			.map_err(|_| XcmError::FailedToTransactAsset("nonfungible item withdraw failed"))?;408409		Ok(())410	}411}412413impl<T: Config> TransactAsset for Pallet<T> {414	fn can_check_in(415		_origin: &MultiLocation,416		_what: &MultiAsset,417		_context: &XcmContext,418	) -> XcmResult {419		Err(XcmError::Unimplemented)420	}421422	fn check_in(_origin: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}423424	fn can_check_out(425		_dest: &MultiLocation,426		_what: &MultiAsset,427		_context: &XcmContext,428	) -> XcmResult {429		Err(XcmError::Unimplemented)430	}431432	fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}433434	fn deposit_asset(what: &MultiAsset, to: &MultiLocation, _context: &XcmContext) -> XcmResult {435		let to = T::LocationToAccountId::convert_location(to)436			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;437438		let collection_id = Self::multiasset_to_collection(what)?;439		let dispatch =440			T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;441442		let collection = dispatch.as_dyn();443		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;444445		match what.fun {446			Fungibility::Fungible(amount) => xcm_ext447				.create_item(448					&Self::pallet_account(),449					to,450					CreateItemData::Fungible(CreateFungibleData { value: amount }),451					&ZeroBudget,452				)453				.map(|_| ())454				.map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),455456			Fungibility::NonFungible(asset_instance) => {457				Self::deposit_asset_instance(xcm_ext, collection_id, &asset_instance, to)458			}459		}460	}461462	fn withdraw_asset(463		what: &MultiAsset,464		from: &MultiLocation,465		_maybe_context: Option<&XcmContext>,466	) -> Result<staging_xcm_executor::Assets, XcmError> {467		let from = T::LocationToAccountId::convert_location(from)468			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;469470		let collection_id = Self::multiasset_to_collection(what)?;471		let dispatch =472			T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;473474		let collection = dispatch.as_dyn();475		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;476477		match what.fun {478			Fungibility::Fungible(amount) => xcm_ext479				.burn_item(from, TokenId::default(), amount)480				.map_err(|_| XcmError::FailedToTransactAsset("fungible item withdraw failed"))?,481482			Fungibility::NonFungible(asset_instance) => {483				Self::withdraw_asset_instance(xcm_ext, collection_id, &asset_instance, from)?;484			}485		}486487		Ok(what.clone().into())488	}489490	fn internal_transfer_asset(491		what: &MultiAsset,492		from: &MultiLocation,493		to: &MultiLocation,494		_context: &XcmContext,495	) -> Result<staging_xcm_executor::Assets, XcmError> {496		let from = T::LocationToAccountId::convert_location(from)497			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;498499		let to = T::LocationToAccountId::convert_location(to)500			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;501502		let collection_id = Self::multiasset_to_collection(what)?;503504		let dispatch =505			T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;506		let collection = dispatch.as_dyn();507		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;508509		let depositor = &from;510511		let token_id;512		let amount;513		let map_error: fn(DispatchError) -> XcmError;514515		match what.fun {516			Fungibility::Fungible(fungible_amount) => {517				token_id = TokenId::default();518				amount = fungible_amount;519				map_error = |_| XcmError::FailedToTransactAsset("fungible item transfer failed");520			}521522			Fungibility::NonFungible(asset_instance) => {523				token_id = Self::asset_instance_to_token_id(collection_id, &asset_instance)?524					.ok_or(XcmError::AssetNotFound)?;525526				amount = 1;527				map_error = |_| XcmError::FailedToTransactAsset("nonfungible item transfer failed")528			}529		}530531		xcm_ext532			.transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)533			.map_err(map_error)?;534535		Ok(what.clone().into())536	}537}538539pub struct CurrencyIdConvert<T: Config>(PhantomData<T>);540impl<T: Config> sp_runtime::traits::Convert<CollectionId, Option<MultiLocation>>541	for CurrencyIdConvert<T>542{543	fn convert(collection_id: CollectionId) -> Option<MultiLocation> {544		if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {545			Some(Here.into())546		} else {547			<Pallet<T>>::collection_to_foreign_reserve_location(collection_id).or_else(|| {548				T::SelfLocation::get()549					.pushed_with_interior(GeneralIndex(collection_id.0.into()))550					.ok()551			})552		}553	}554}555556pub use frame_support::{557	traits::{558		fungibles::Balanced, tokens::currency::Currency as CurrencyT, OnUnbalanced as OnUnbalancedT,559	},560	weights::{WeightToFee, WeightToFeePolynomial},561};562563pub struct FreeForAll;564565impl WeightTrader for FreeForAll {566	fn new() -> Self {567		Self568	}569570	fn buy_weight(571		&mut self,572		weight: Weight,573		payment: Assets,574		_xcm: &XcmContext,575	) -> Result<Assets, XcmError> {576		log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);577		Ok(payment)578	}579}