git.delta.rocks / unique-network / refs/commits / 68c3995040db

difftreelog

fix check token children in XCM extensions

Daniel Shiposha2023-10-25parent: #0e38a03.patch.diff
in: master

2 files changed

modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -2384,6 +2384,10 @@
 			return unsupported!(T);
 		}
 
+		if self.token_has_children(token) {
+			return unsupported!(T);
+		}
+
 		self.transfer_item_internal(depositor, from, to, token, amount, nesting_budget)
 	}
 
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::{boxed::Box, 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, CollectionTokenPrefix,43	CreateCollectionData, CreateFungibleData, CreateItemData, CreateNftData, Property, PropertyKey,44	TokenId,45};4647pub mod weights;4849#[cfg(feature = "runtime-benchmarks")]50mod benchmarking;5152pub use module::*;53pub use weights::WeightInfo;5455#[frame_support::pallet]56pub mod module {57	use up_data_structs::{58		CollectionDescription, Property, PropertyKeyPermission, PropertyPermission,59	};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			asset_id: CollectionId,101			reserve_location: Box<MultiLocation>,102		},103	}104105	/// The corresponding collections of reserve locations.106	#[pallet::storage]107	#[pallet::getter(fn foreign_reserve_location_to_collection)]108	pub type ForeignReserveLocationToCollection<T: Config> =109		StorageMap<_, Twox64Concat, staging_xcm::v3::MultiLocation, CollectionId, OptionQuery>;110111	/// The corresponding reserve location of collections.112	#[pallet::storage]113	#[pallet::getter(fn collection_to_foreign_reserve_location)]114	pub type CollectionToForeignReserveLocation<T: Config> =115		StorageMap<_, Twox64Concat, CollectionId, staging_xcm::v3::MultiLocation, 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 = Twox64Concat,124		Key2 = staging_xcm::v3::AssetInstance,125		Value = TokenId,126		QueryKind = OptionQuery,127	>;128129	#[pallet::pallet]130	pub struct Pallet<T>(_);131132	#[pallet::call]133	impl<T: Config> Pallet<T> {134		#[pallet::call_index(0)]135		#[pallet::weight(<T as Config>::WeightInfo::register_foreign_asset())]136		pub fn force_register_foreign_asset(137			origin: OriginFor<T>,138			reserve_location: Box<MultiLocation>,139			name: CollectionName,140			token_prefix: CollectionTokenPrefix,141			mode: ForeignCollectionMode,142		) -> DispatchResult {143			T::ForceRegisterOrigin::ensure_origin(origin.clone())?;144145			if <ForeignReserveLocationToCollection<T>>::contains_key(*reserve_location) {146				return Err(<Error<T>>::ForeignAssetAlreadyRegistered.into());147			}148149			let foreign_collection_owner = Self::pallet_account();150151			let description: CollectionDescription = "Foreign Assets Collection"152				.encode_utf16()153				.collect::<Vec<_>>()154				.try_into()155				.expect("description length < max description length; qed");156157			let payer = None;158			let is_special_collection = true;159			let collection_id = T::CollectionDispatch::create_raw(160				foreign_collection_owner,161				payer,162				is_special_collection,163				CreateCollectionData {164					name,165					token_prefix,166					description,167					mode: mode.into(),168169					properties: vec![Property {170						key: Self::reserve_location_property_key(),171						value: reserve_location172							.encode()173							.try_into()174							.expect("multilocation is less than 32k; qed"),175					}]176					.try_into()177					.expect("just one property can always be stored; qed"),178179					token_property_permissions: vec![PropertyKeyPermission {180						key: Self::reserve_asset_instance_property_key(),181						permission: PropertyPermission {182							mutable: false,183							collection_admin: true,184							token_owner: false,185						},186					}]187					.try_into()188					.expect("just one property permission can always be stored; qed"),189					..Default::default()190				},191			)?;192193			<ForeignReserveLocationToCollection<T>>::insert(*reserve_location, collection_id);194			<CollectionToForeignReserveLocation<T>>::insert(collection_id, *reserve_location);195196			Self::deposit_event(Event::<T>::ForeignAssetRegistered {197				asset_id: collection_id,198				reserve_location,199			});200201			Ok(())202		}203	}204}205206impl<T: Config> Pallet<T> {207	fn pallet_account() -> T::CrossAccountId {208		let owner: T::AccountId = T::PalletId::get().into_account_truncating();209		T::CrossAccountId::from_sub(owner)210	}211212	fn reserve_location_property_key() -> PropertyKey {213		b"reserve-location"214			.to_vec()215			.try_into()216			.expect("key length < max property key length; qed")217	}218219	fn reserve_asset_instance_property_key() -> PropertyKey {220		b"reserve-asset-instance"221			.to_vec()222			.try_into()223			.expect("key length < max property key length; qed")224	}225226	/// Converts a multilocation to the Unique Network's local collection227	/// (i.e. the collection originally created on the Unique Network's parachain).228	///229	/// The multilocation corresponds to a Unique Network's collection if:230	/// * It is `Here` location that corresponds to the native token of this parachain.231	/// * It is `../Parachain(<Unique Network Para ID>)` that also corresponds to the native token of this parachain.232	/// * It is `../Parachain(<Unique Network Para ID>)/GeneralIndex(<Collection ID>)` that corresponds233	/// to the collection with the ID equal to `<Collection ID>`. The `<Collection ID>` must be in the valid range,234	/// otherwise the `AssetIdConversionFailed` error will be returned.235	///236	/// If the multilocation doesn't match the patterns listed above, the function returns `Ok(None)`,237	/// identifying that the given multilocation doesn't correspond to a local collection.238	fn native_asset_location_to_collection(239		asset_location: &MultiLocation,240	) -> Result<Option<CollectionId>, XcmError> {241		let self_location = T::SelfLocation::get();242243		if *asset_location == Here.into() || *asset_location == self_location {244			Ok(Some(NATIVE_FUNGIBLE_COLLECTION_ID))245		} else if asset_location.parents == self_location.parents {246			match asset_location247				.interior248				.match_and_split(&self_location.interior)249			{250				Some(GeneralIndex(collection_id)) => Ok(Some(CollectionId(251					(*collection_id)252						.try_into()253						.map_err(|_| XcmExecutorError::AssetIdConversionFailed)?,254				))),255				_ => Ok(None),256			}257		} else {258			Ok(None)259		}260	}261262	/// Converts a multiasset to a Unique Network's collection (either local or the foreign one).263	///264	/// The function will try to convert the multiasset's reserve location265	/// to the Unique Network's local collection.266	///267	/// If the multilocation doesn't correspond to a local collection,268	/// the function will check if the reserve location has the corresponding269	/// derivative Unique Network's collection, and will return the said collection ID if found.270	///271	/// If all of the above have failed, the `AssetIdConversionFailed` error will be returned.272	fn multiasset_to_collection(asset: &MultiAsset) -> Result<CollectionId, XcmError> {273		let AssetId::Concrete(asset_reserve_location) = asset.id else {274			return Err(XcmExecutorError::AssetNotHandled.into());275		};276277		Self::native_asset_location_to_collection(&asset_reserve_location)?278			.or_else(|| Self::foreign_reserve_location_to_collection(asset_reserve_location))279			.ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())280	}281282	/// Converts an XCM asset instance to the Unique Network's token ID.283	///284	/// The asset instance corresponds to the Unique Network's token ID if it is in the following format:285	/// `AssetInstance::Index(<token ID>)`.286	///287	/// If the asset instance is not in the valid format or the `<token ID>` points to a non-existent token,288	/// the `AssetNotFound` error will be returned.289	fn native_asset_instance_to_token_id(290		asset_instance: &AssetInstance,291	) -> Result<TokenId, XcmError> {292		match asset_instance {293			AssetInstance::Index(token_id) => Ok(TokenId(294				(*token_id)295					.try_into()296					.map_err(|_| XcmError::AssetNotFound)?,297			)),298			_ => Err(XcmError::AssetNotFound),299		}300	}301302	/// Obtains the token ID of the `asset_instance` in the collection.303	///304	/// Returns `Ok(None)` only if the `asset_instance` is a part of a foreign collection305	/// and the item hasn't yet been created on this blockchain.306	///307	/// If the `asset_instance` exists and it is a part of a foreign collection, the function will return `Ok(Some(<token ID>))`.308	///309	/// If the `asset_instance` is a part of a local collection,310	/// the function will return either `Ok(Some(<token ID>))` or an error if the token is not found.311	fn asset_instance_to_token_id(312		collection_id: CollectionId,313		asset_instance: &AssetInstance,314	) -> Result<Option<TokenId>, XcmError> {315		if <CollectionToForeignReserveLocation<T>>::contains_key(collection_id) {316			Ok(Self::foreign_reserve_asset_instance_to_token_id(317				collection_id,318				asset_instance,319			))320		} else {321			Self::native_asset_instance_to_token_id(asset_instance).map(Some)322		}323	}324325	/// Creates a foreign item in the the collection.326	fn create_foreign_asset_instance(327		xcm_ext: &dyn XcmExtensions<T>,328		collection_id: CollectionId,329		asset_instance: &AssetInstance,330		to: T::CrossAccountId,331	) -> DispatchResult {332		let asset_instance_encoded = asset_instance.encode();333334		let derivative_token_id = xcm_ext.create_item(335			&Self::pallet_account(),336			to,337			CreateItemData::NFT(CreateNftData {338				properties: vec![Property {339					key: Self::reserve_asset_instance_property_key(),340					value: asset_instance_encoded341						.try_into()342						.expect("asset instance length <= 32 bytes which is less than value length limit; qed"),343				}]344				.try_into()345				.expect("just one property can always be stored; qed"),346			}),347			&ZeroBudget,348		)?;349350		<ForeignReserveAssetInstanceToTokenId<T>>::insert(351			collection_id,352			asset_instance,353			derivative_token_id,354		);355356		Ok(())357	}358359	/// Deposits an asset instance to the `to` account.360	///361	/// Either transfers an existing item from the pallet's account362	/// or creates a foreign item.363	fn deposit_asset_instance(364		xcm_ext: &dyn XcmExtensions<T>,365		collection_id: CollectionId,366		asset_instance: &AssetInstance,367		to: T::CrossAccountId,368	) -> XcmResult {369		let deposit_result = if let Some(token_id) =370			Self::asset_instance_to_token_id(collection_id, asset_instance)?371		{372			let depositor = &Self::pallet_account();373			let from = depositor;374			let amount = 1;375376			xcm_ext.transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)377		} else {378			Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)379		};380381		deposit_result382			.map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))383	}384385	/// Withdraws an asset instance from the `from` account.386	///387	/// Transfers the asset instance to the pallet's account.388	///389	/// Won't withdraw the instance if it has children.390	fn withdraw_asset_instance(391		xcm_ext: &dyn XcmExtensions<T>,392		collection_id: CollectionId,393		asset_instance: &AssetInstance,394		from: T::CrossAccountId,395	) -> XcmResult {396		let token_id = Self::asset_instance_to_token_id(collection_id, asset_instance)?397			.ok_or(XcmError::AssetNotFound)?;398399		if xcm_ext.token_has_children(token_id) {400			return Err(XcmError::Unimplemented);401		}402403		let depositor = &from;404		let to = Self::pallet_account();405		let amount = 1;406		xcm_ext407			.transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)408			.map_err(|_| XcmError::FailedToTransactAsset("nonfungible item withdraw failed"))?;409410		Ok(())411	}412}413414impl<T: Config> TransactAsset for Pallet<T> {415	fn can_check_in(416		_origin: &MultiLocation,417		_what: &MultiAsset,418		_context: &XcmContext,419	) -> XcmResult {420		Err(XcmError::Unimplemented)421	}422423	fn check_in(_origin: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}424425	fn can_check_out(426		_dest: &MultiLocation,427		_what: &MultiAsset,428		_context: &XcmContext,429	) -> XcmResult {430		Err(XcmError::Unimplemented)431	}432433	fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}434435	fn deposit_asset(what: &MultiAsset, to: &MultiLocation, _context: &XcmContext) -> XcmResult {436		let to = T::LocationToAccountId::convert_location(to)437			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;438439		let collection_id = Self::multiasset_to_collection(what)?;440		let dispatch =441			T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;442443		let collection = dispatch.as_dyn();444		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;445446		match what.fun {447			Fungibility::Fungible(amount) => xcm_ext448				.create_item(449					&Self::pallet_account(),450					to,451					CreateItemData::Fungible(CreateFungibleData { value: amount }),452					&ZeroBudget,453				)454				.map(|_| ())455				.map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),456457			Fungibility::NonFungible(asset_instance) => {458				Self::deposit_asset_instance(xcm_ext, collection_id, &asset_instance, to)459			}460		}461	}462463	fn withdraw_asset(464		what: &MultiAsset,465		from: &MultiLocation,466		_maybe_context: Option<&XcmContext>,467	) -> Result<staging_xcm_executor::Assets, XcmError> {468		let from = T::LocationToAccountId::convert_location(from)469			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;470471		let collection_id = Self::multiasset_to_collection(what)?;472		let dispatch =473			T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;474475		let collection = dispatch.as_dyn();476		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;477478		match what.fun {479			Fungibility::Fungible(amount) => xcm_ext480				.burn_item(from, TokenId::default(), amount)481				.map_err(|_| XcmError::FailedToTransactAsset("fungible item withdraw failed"))?,482483			Fungibility::NonFungible(asset_instance) => {484				Self::withdraw_asset_instance(xcm_ext, collection_id, &asset_instance, from)?;485			}486		}487488		Ok(what.clone().into())489	}490491	fn internal_transfer_asset(492		what: &MultiAsset,493		from: &MultiLocation,494		to: &MultiLocation,495		_context: &XcmContext,496	) -> Result<staging_xcm_executor::Assets, XcmError> {497		let from = T::LocationToAccountId::convert_location(from)498			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;499500		let to = T::LocationToAccountId::convert_location(to)501			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;502503		let collection_id = Self::multiasset_to_collection(what)?;504505		let dispatch =506			T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;507		let collection = dispatch.as_dyn();508		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;509510		let depositor = &from;511512		let token_id;513		let amount;514		let map_error: fn(DispatchError) -> XcmError;515516		match what.fun {517			Fungibility::Fungible(fungible_amount) => {518				token_id = TokenId::default();519				amount = fungible_amount;520				map_error = |_| XcmError::FailedToTransactAsset("fungible item transfer failed");521			}522523			Fungibility::NonFungible(asset_instance) => {524				token_id = Self::asset_instance_to_token_id(collection_id, &asset_instance)?525					.ok_or(XcmError::AssetNotFound)?;526527				amount = 1;528				map_error = |_| XcmError::FailedToTransactAsset("nonfungible item transfer failed")529			}530		}531532		xcm_ext533			.transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)534			.map_err(map_error)?;535536		Ok(what.clone().into())537	}538}539540pub struct CurrencyIdConvert<T: Config>(PhantomData<T>);541impl<T: Config> sp_runtime::traits::Convert<CollectionId, Option<MultiLocation>>542	for CurrencyIdConvert<T>543{544	fn convert(collection_id: CollectionId) -> Option<MultiLocation> {545		if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {546			Some(T::SelfLocation::get())547		} else {548			<Pallet<T>>::collection_to_foreign_reserve_location(collection_id).or_else(|| {549				T::SelfLocation::get()550					.pushed_with_interior(GeneralIndex(collection_id.0.into()))551					.ok()552			})553		}554	}555}556557pub use frame_support::{558	traits::{559		fungibles::Balanced, tokens::currency::Currency as CurrencyT, OnUnbalanced as OnUnbalancedT,560	},561	weights::{WeightToFee, WeightToFeePolynomial},562};563564#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]565pub enum ForeignCollectionMode {566	NFT,567	Fungible(u8),568}569570impl From<ForeignCollectionMode> for CollectionMode {571	fn from(value: ForeignCollectionMode) -> Self {572		match value {573			ForeignCollectionMode::NFT => Self::NFT,574			ForeignCollectionMode::Fungible(decimals) => Self::Fungible(decimals),575		}576	}577}578579pub struct FreeForAll;580581impl WeightTrader for FreeForAll {582	fn new() -> Self {583		Self584	}585586	fn buy_weight(587		&mut self,588		weight: Weight,589		payment: Assets,590		_xcm: &XcmContext,591	) -> Result<Assets, XcmError> {592		log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);593		Ok(payment)594	}595}