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

difftreelog

refactor create collection with optional payer

Daniel Shiposha2023-10-18parent: #383b7ef.patch.diff
in: master

5 files changed

modifiedpallets/common/src/dispatch.rsdiffbeforeafterboth
--- a/pallets/common/src/dispatch.rs
+++ b/pallets/common/src/dispatch.rs
@@ -72,20 +72,11 @@
 	/// Create a collection. The collection will be created according to the value of [`data.mode`](CreateCollectionData::mode).
 	///
 	/// * `sender` - The user who will become the owner of the collection.
-	/// * `payer` - The user who pays the collection creation fee.
+	/// * `payer` - If set, the user who pays the collection creation deposit.
 	/// * `data` - Description of the created collection.
 	fn create(
 		sender: T::CrossAccountId,
-		payer: T::CrossAccountId,
-		data: CreateCollectionData<T::CrossAccountId>,
-	) -> Result<CollectionId, DispatchError>;
-
-	/// Create a foreign collection. The collection will be created according to the value of [`data.mode`](CreateCollectionData::mode).
-	///
-	/// * `sender` - The user who will become the owner of the collection.
-	/// * `data` - Description of the created collection.
-	fn create_foreign(
-		sender: T::CrossAccountId,
+		payer: Option<T::CrossAccountId>,
 		data: CreateCollectionData<T::CrossAccountId>,
 	) -> Result<CollectionId, DispatchError>;
 
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 collection_id = T::CollectionDispatch::create_foreign(156				foreign_collection_owner,157				CreateCollectionData {158					name,159					description,160					mode,161162					properties: vec![Property {163						key: Self::reserve_location_property_key(),164						value: reserve_location165							.encode()166							.try_into()167							.expect("multilocation is less than 32k; qed"),168					}]169					.try_into()170					.expect("just one property can always be stored; qed"),171172					token_property_permissions: vec![PropertyKeyPermission {173						key: Self::reserve_asset_instance_property_key(),174						permission: PropertyPermission {175							mutable: false,176							collection_admin: true,177							token_owner: false,178						},179					}]180					.try_into()181					.expect("just one property permission can always be stored; qed"),182					..Default::default()183				},184			)?;185186			<ForeignReserveLocationToCollection<T>>::insert(reserve_location, collection_id);187			<CollectionToForeignReserveLocation<T>>::insert(collection_id, reserve_location);188189			Self::deposit_event(Event::<T>::ForeignAssetRegistered {190				asset_id: collection_id,191				reserve_location,192			});193194			Ok(())195		}196	}197}198199impl<T: Config> Pallet<T> {200	fn pallet_account() -> T::CrossAccountId {201		let owner: T::AccountId = T::PalletId::get().into_account_truncating();202		T::CrossAccountId::from_sub(owner)203	}204205	fn reserve_location_property_key() -> PropertyKey {206		b"reserve-location"207			.to_vec()208			.try_into()209			.expect("key length < max property key length; qed")210	}211212	fn reserve_asset_instance_property_key() -> PropertyKey {213		b"reserve-asset-instance"214			.to_vec()215			.try_into()216			.expect("key length < max property key length; qed")217	}218219	/// Converts a multilocation to the Unique Network's local collection220	/// (i.e. the collection originally created on the Unique Network's parachain).221	///222	/// The multilocation corresponds to a Unique Network's collection if:223	/// * It is `Here` location that corresponds to the native token of this parachain.224	/// * It is `../Parachain(<Unique Network Para ID>)` that also corresponds to the native token of this parachain.225	/// * It is `../Parachain(<Unique Network Para ID>)/GeneralIndex(<Collection ID>)` that corresponds226	/// to the collection with the ID equal to `<Collection ID>`. The `<Collection ID>` must be in the valid range,227	/// otherwise the `AssetIdConversionFailed` error will be returned.228	///229	/// If the multilocation doesn't match the patterns listed above, the function returns `Ok(None)`,230	/// identifying that the given multilocation doesn't correspond to a local collection.231	fn native_asset_location_to_collection(232		asset_location: &MultiLocation,233	) -> Result<Option<CollectionId>, XcmError> {234		let self_location = T::SelfLocation::get();235236		if *asset_location == Here.into() {237			Ok(Some(NATIVE_FUNGIBLE_COLLECTION_ID))238		} else if *asset_location == self_location {239			Ok(Some(NATIVE_FUNGIBLE_COLLECTION_ID))240		} else if asset_location.parents == self_location.parents {241			match asset_location242				.interior243				.match_and_split(&self_location.interior)244			{245				Some(GeneralIndex(collection_id)) => Ok(Some(CollectionId(246					(*collection_id)247						.try_into()248						.map_err(|_| XcmExecutorError::AssetIdConversionFailed)?,249				))),250				_ => Ok(None),251			}252		} else {253			Ok(None)254		}255	}256257	/// Converts a multiasset to a Unique Network's collection (either local or the foreign one).258	///259	/// The function will try to convert the multiasset's reserve location260	/// to the Unique Network's local collection.261	///262	/// If the multilocation doesn't correspond to a local collection,263	/// the function will check if the reserve location has the corresponding264	/// derivative Unique Network's collection, and will return the said collection ID if found.265	///266	/// If all of the above have failed, the `AssetIdConversionFailed` error will be returned.267	fn multiasset_to_collection(asset: &MultiAsset) -> Result<CollectionId, XcmError> {268		let AssetId::Concrete(asset_reserve_location) = asset.id else {269			return Err(XcmExecutorError::AssetNotHandled.into());270		};271272		Self::native_asset_location_to_collection(&asset_reserve_location)?273			.or_else(|| Self::foreign_reserve_location_to_collection(asset_reserve_location))274			.ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())275	}276277	/// Converts an XCM asset instance to the Unique Network's token ID.278	///279	/// The asset instance corresponds to the Unique Network's token ID if it is in the following format:280	/// `AssetInstance::Index(<token ID>)`.281	///282	/// If the asset instance is not in the valid format or the `<token ID>` points to a non-existent token,283	/// the `AssetNotFound` error will be returned.284	fn native_asset_instance_to_token_id(285		asset_instance: &AssetInstance,286	) -> Result<TokenId, XcmError> {287		match asset_instance {288			AssetInstance::Index(token_id) => Ok(TokenId(289				(*token_id)290					.try_into()291					.map_err(|_| XcmError::AssetNotFound)?,292			)),293			_ => Err(XcmError::AssetNotFound),294		}295	}296297	/// Obtains the token ID of the `asset_instance` in the collection.298	///299	/// Returns `Ok(None)` only if the `asset_instance` is a part of a foreign collection300	/// and the item hasn't yet been created on this blockchain.301	///302	/// If the `asset_instance` exists and it is a part of a foreign collection, the function will return `Ok(Some(<token ID>))`.303	///304	/// If the `asset_instance` is a part of a local collection,305	/// the function will return either `Ok(Some(<token ID>))` or an error if the token is not found.306	fn asset_instance_to_token_id(307		collection_id: CollectionId,308		asset_instance: &AssetInstance,309	) -> Result<Option<TokenId>, XcmError> {310		if <CollectionToForeignReserveLocation<T>>::contains_key(collection_id) {311			Ok(Self::foreign_reserve_asset_instance_to_token_id(312				collection_id,313				asset_instance,314			))315		} else {316			Self::native_asset_instance_to_token_id(asset_instance).map(Some)317		}318	}319320	/// Creates a foreign item in the the collection.321	fn create_foreign_asset_instance(322		xcm_ext: &dyn XcmExtensions<T>,323		collection_id: CollectionId,324		asset_instance: &AssetInstance,325		to: T::CrossAccountId,326	) -> DispatchResult {327		let asset_instance_encoded = asset_instance.encode();328329		let derivative_token_id = xcm_ext.create_item(330			&Self::pallet_account(),331			to,332			CreateItemData::NFT(CreateNftData {333				properties: vec![Property {334					key: Self::reserve_asset_instance_property_key(),335					value: asset_instance_encoded336						.try_into()337						.expect("asset instance length <= 32 bytes which is less than value length limit; qed"),338				}]339				.try_into()340				.expect("just one property can always be stored; qed"),341			}),342			&ZeroBudget,343		)?;344345		<ForeignReserveAssetInstanceToTokenId<T>>::insert(346			collection_id,347			asset_instance,348			derivative_token_id,349		);350351		Ok(())352	}353354	/// Deposits an asset instance to the `to` account.355	///356	/// Either transfers an existing item from the pallet's account357	/// or creates a foreign item.358	fn deposit_asset_instance(359		xcm_ext: &dyn XcmExtensions<T>,360		collection_id: CollectionId,361		asset_instance: &AssetInstance,362		to: T::CrossAccountId,363	) -> XcmResult {364		let deposit_result = if let Some(token_id) =365			Self::asset_instance_to_token_id(collection_id, asset_instance)?366		{367			let depositor = &Self::pallet_account();368			let from = depositor;369			let amount = 1;370371			xcm_ext.transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)372		} else {373			Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)374		};375376		deposit_result377			.map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))378	}379380	/// Withdraws an asset instance from the `from` account.381	///382	/// Transfers the asset instance to the pallet's account.383	///384	/// Won't withdraw the instance if it has children.385	fn withdraw_asset_instance(386		xcm_ext: &dyn XcmExtensions<T>,387		collection_id: CollectionId,388		asset_instance: &AssetInstance,389		from: T::CrossAccountId,390	) -> XcmResult {391		let token_id = Self::asset_instance_to_token_id(collection_id, &asset_instance)?392			.ok_or(XcmError::AssetNotFound)?;393394		if xcm_ext.token_has_children(token_id) {395			return Err(XcmError::Unimplemented);396		}397398		let depositor = &from;399		let to = Self::pallet_account();400		let amount = 1;401		xcm_ext402			.transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)403			.map_err(|_| XcmError::FailedToTransactAsset("nonfungible item withdraw failed"))?;404405		Ok(())406	}407}408409impl<T: Config> TransactAsset for Pallet<T> {410	fn can_check_in(411		_origin: &MultiLocation,412		_what: &MultiAsset,413		_context: &XcmContext,414	) -> XcmResult {415		Err(XcmError::Unimplemented)416	}417418	fn check_in(_origin: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}419420	fn can_check_out(421		_dest: &MultiLocation,422		_what: &MultiAsset,423		_context: &XcmContext,424	) -> XcmResult {425		Err(XcmError::Unimplemented)426	}427428	fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}429430	fn deposit_asset(what: &MultiAsset, to: &MultiLocation, _context: &XcmContext) -> XcmResult {431		let to = T::LocationToAccountId::convert_location(to)432			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;433434		let collection_id = Self::multiasset_to_collection(what)?;435		let dispatch =436			T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;437438		let collection = dispatch.as_dyn();439		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;440441		match what.fun {442			Fungibility::Fungible(amount) => xcm_ext443				.create_item(444					&Self::pallet_account(),445					to,446					CreateItemData::Fungible(CreateFungibleData { value: amount }),447					&ZeroBudget,448				)449				.map(|_| ())450				.map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),451452			Fungibility::NonFungible(asset_instance) => {453				Self::deposit_asset_instance(xcm_ext, collection_id, &asset_instance, to)454			}455		}456	}457458	fn withdraw_asset(459		what: &MultiAsset,460		from: &MultiLocation,461		_maybe_context: Option<&XcmContext>,462	) -> Result<staging_xcm_executor::Assets, XcmError> {463		let from = T::LocationToAccountId::convert_location(from)464			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;465466		let collection_id = Self::multiasset_to_collection(what)?;467		let dispatch =468			T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;469470		let collection = dispatch.as_dyn();471		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;472473		match what.fun {474			Fungibility::Fungible(amount) => xcm_ext475				.burn_item(from, TokenId::default(), amount)476				.map_err(|_| XcmError::FailedToTransactAsset("fungible item withdraw failed"))?,477478			Fungibility::NonFungible(asset_instance) => {479				Self::withdraw_asset_instance(xcm_ext, collection_id, &asset_instance, from)?;480			}481		}482483		Ok(what.clone().into())484	}485486	fn internal_transfer_asset(487		what: &MultiAsset,488		from: &MultiLocation,489		to: &MultiLocation,490		_context: &XcmContext,491	) -> Result<staging_xcm_executor::Assets, XcmError> {492		let from = T::LocationToAccountId::convert_location(from)493			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;494495		let to = T::LocationToAccountId::convert_location(to)496			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;497498		let collection_id = Self::multiasset_to_collection(what)?;499500		let dispatch =501			T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;502		let collection = dispatch.as_dyn();503		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;504505		let depositor = &from;506507		let token_id;508		let amount;509		let map_error: fn(DispatchError) -> XcmError;510511		match what.fun {512			Fungibility::Fungible(fungible_amount) => {513				token_id = TokenId::default();514				amount = fungible_amount;515				map_error = |_| XcmError::FailedToTransactAsset("fungible item transfer failed");516			}517518			Fungibility::NonFungible(asset_instance) => {519				token_id = Self::asset_instance_to_token_id(collection_id, &asset_instance)?520					.ok_or(XcmError::AssetNotFound)?;521522				amount = 1;523				map_error = |_| XcmError::FailedToTransactAsset("nonfungible item transfer failed")524			}525		}526527		xcm_ext528			.transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)529			.map_err(map_error)?;530531		Ok(what.clone().into())532	}533}534535pub struct CurrencyIdConvert<T: Config>(PhantomData<T>);536impl<T: Config> sp_runtime::traits::Convert<CollectionId, Option<MultiLocation>>537	for CurrencyIdConvert<T>538{539	fn convert(collection_id: CollectionId) -> Option<MultiLocation> {540		if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {541			Some(Here.into())542		} else {543			<Pallet<T>>::collection_to_foreign_reserve_location(collection_id).or_else(|| {544				T::SelfLocation::get()545					.pushed_with_interior(GeneralIndex(collection_id.0.into()))546					.ok()547			})548		}549	}550}551552pub use frame_support::{553	traits::{554		fungibles::Balanced, tokens::currency::Currency as CurrencyT, OnUnbalanced as OnUnbalancedT,555	},556	weights::{WeightToFee, WeightToFeePolynomial},557};558559pub struct FreeForAll;560561impl WeightTrader for FreeForAll {562	fn new() -> Self {563		Self564	}565566	fn buy_weight(567		&mut self,568		weight: Weight,569		payment: Assets,570		_xcm: &XcmContext,571	) -> Result<Assets, XcmError> {572		log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);573		Ok(payment)574	}575}
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 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 collection_id = T::CollectionDispatch::create(157				foreign_collection_owner,158				payer,159				CreateCollectionData {160					name,161					description,162					mode,163164					properties: vec![Property {165						key: Self::reserve_location_property_key(),166						value: reserve_location167							.encode()168							.try_into()169							.expect("multilocation is less than 32k; qed"),170					}]171					.try_into()172					.expect("just one property can always be stored; qed"),173174					token_property_permissions: vec![PropertyKeyPermission {175						key: Self::reserve_asset_instance_property_key(),176						permission: PropertyPermission {177							mutable: false,178							collection_admin: true,179							token_owner: false,180						},181					}]182					.try_into()183					.expect("just one property permission can always be stored; qed"),184					..Default::default()185				},186			)?;187188			<ForeignReserveLocationToCollection<T>>::insert(reserve_location, collection_id);189			<CollectionToForeignReserveLocation<T>>::insert(collection_id, reserve_location);190191			Self::deposit_event(Event::<T>::ForeignAssetRegistered {192				asset_id: collection_id,193				reserve_location,194			});195196			Ok(())197		}198	}199}200201impl<T: Config> Pallet<T> {202	fn pallet_account() -> T::CrossAccountId {203		let owner: T::AccountId = T::PalletId::get().into_account_truncating();204		T::CrossAccountId::from_sub(owner)205	}206207	fn reserve_location_property_key() -> PropertyKey {208		b"reserve-location"209			.to_vec()210			.try_into()211			.expect("key length < max property key length; qed")212	}213214	fn reserve_asset_instance_property_key() -> PropertyKey {215		b"reserve-asset-instance"216			.to_vec()217			.try_into()218			.expect("key length < max property key length; qed")219	}220221	/// Converts a multilocation to the Unique Network's local collection222	/// (i.e. the collection originally created on the Unique Network's parachain).223	///224	/// The multilocation corresponds to a Unique Network's collection if:225	/// * It is `Here` location that corresponds to the native token of this parachain.226	/// * It is `../Parachain(<Unique Network Para ID>)` that also corresponds to the native token of this parachain.227	/// * It is `../Parachain(<Unique Network Para ID>)/GeneralIndex(<Collection ID>)` that corresponds228	/// to the collection with the ID equal to `<Collection ID>`. The `<Collection ID>` must be in the valid range,229	/// otherwise the `AssetIdConversionFailed` error will be returned.230	///231	/// If the multilocation doesn't match the patterns listed above, the function returns `Ok(None)`,232	/// identifying that the given multilocation doesn't correspond to a local collection.233	fn native_asset_location_to_collection(234		asset_location: &MultiLocation,235	) -> Result<Option<CollectionId>, XcmError> {236		let self_location = T::SelfLocation::get();237238		if *asset_location == Here.into() {239			Ok(Some(NATIVE_FUNGIBLE_COLLECTION_ID))240		} else if *asset_location == self_location {241			Ok(Some(NATIVE_FUNGIBLE_COLLECTION_ID))242		} else if asset_location.parents == self_location.parents {243			match asset_location244				.interior245				.match_and_split(&self_location.interior)246			{247				Some(GeneralIndex(collection_id)) => Ok(Some(CollectionId(248					(*collection_id)249						.try_into()250						.map_err(|_| XcmExecutorError::AssetIdConversionFailed)?,251				))),252				_ => Ok(None),253			}254		} else {255			Ok(None)256		}257	}258259	/// Converts a multiasset to a Unique Network's collection (either local or the foreign one).260	///261	/// The function will try to convert the multiasset's reserve location262	/// to the Unique Network's local collection.263	///264	/// If the multilocation doesn't correspond to a local collection,265	/// the function will check if the reserve location has the corresponding266	/// derivative Unique Network's collection, and will return the said collection ID if found.267	///268	/// If all of the above have failed, the `AssetIdConversionFailed` error will be returned.269	fn multiasset_to_collection(asset: &MultiAsset) -> Result<CollectionId, XcmError> {270		let AssetId::Concrete(asset_reserve_location) = asset.id else {271			return Err(XcmExecutorError::AssetNotHandled.into());272		};273274		Self::native_asset_location_to_collection(&asset_reserve_location)?275			.or_else(|| Self::foreign_reserve_location_to_collection(asset_reserve_location))276			.ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())277	}278279	/// Converts an XCM asset instance to the Unique Network's token ID.280	///281	/// The asset instance corresponds to the Unique Network's token ID if it is in the following format:282	/// `AssetInstance::Index(<token ID>)`.283	///284	/// If the asset instance is not in the valid format or the `<token ID>` points to a non-existent token,285	/// the `AssetNotFound` error will be returned.286	fn native_asset_instance_to_token_id(287		asset_instance: &AssetInstance,288	) -> Result<TokenId, XcmError> {289		match asset_instance {290			AssetInstance::Index(token_id) => Ok(TokenId(291				(*token_id)292					.try_into()293					.map_err(|_| XcmError::AssetNotFound)?,294			)),295			_ => Err(XcmError::AssetNotFound),296		}297	}298299	/// Obtains the token ID of the `asset_instance` in the collection.300	///301	/// Returns `Ok(None)` only if the `asset_instance` is a part of a foreign collection302	/// and the item hasn't yet been created on this blockchain.303	///304	/// If the `asset_instance` exists and it is a part of a foreign collection, the function will return `Ok(Some(<token ID>))`.305	///306	/// If the `asset_instance` is a part of a local collection,307	/// the function will return either `Ok(Some(<token ID>))` or an error if the token is not found.308	fn asset_instance_to_token_id(309		collection_id: CollectionId,310		asset_instance: &AssetInstance,311	) -> Result<Option<TokenId>, XcmError> {312		if <CollectionToForeignReserveLocation<T>>::contains_key(collection_id) {313			Ok(Self::foreign_reserve_asset_instance_to_token_id(314				collection_id,315				asset_instance,316			))317		} else {318			Self::native_asset_instance_to_token_id(asset_instance).map(Some)319		}320	}321322	/// Creates a foreign item in the the collection.323	fn create_foreign_asset_instance(324		xcm_ext: &dyn XcmExtensions<T>,325		collection_id: CollectionId,326		asset_instance: &AssetInstance,327		to: T::CrossAccountId,328	) -> DispatchResult {329		let asset_instance_encoded = asset_instance.encode();330331		let derivative_token_id = xcm_ext.create_item(332			&Self::pallet_account(),333			to,334			CreateItemData::NFT(CreateNftData {335				properties: vec![Property {336					key: Self::reserve_asset_instance_property_key(),337					value: asset_instance_encoded338						.try_into()339						.expect("asset instance length <= 32 bytes which is less than value length limit; qed"),340				}]341				.try_into()342				.expect("just one property can always be stored; qed"),343			}),344			&ZeroBudget,345		)?;346347		<ForeignReserveAssetInstanceToTokenId<T>>::insert(348			collection_id,349			asset_instance,350			derivative_token_id,351		);352353		Ok(())354	}355356	/// Deposits an asset instance to the `to` account.357	///358	/// Either transfers an existing item from the pallet's account359	/// or creates a foreign item.360	fn deposit_asset_instance(361		xcm_ext: &dyn XcmExtensions<T>,362		collection_id: CollectionId,363		asset_instance: &AssetInstance,364		to: T::CrossAccountId,365	) -> XcmResult {366		let deposit_result = if let Some(token_id) =367			Self::asset_instance_to_token_id(collection_id, asset_instance)?368		{369			let depositor = &Self::pallet_account();370			let from = depositor;371			let amount = 1;372373			xcm_ext.transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)374		} else {375			Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)376		};377378		deposit_result379			.map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))380	}381382	/// Withdraws an asset instance from the `from` account.383	///384	/// Transfers the asset instance to the pallet's account.385	///386	/// Won't withdraw the instance if it has children.387	fn withdraw_asset_instance(388		xcm_ext: &dyn XcmExtensions<T>,389		collection_id: CollectionId,390		asset_instance: &AssetInstance,391		from: T::CrossAccountId,392	) -> XcmResult {393		let token_id = Self::asset_instance_to_token_id(collection_id, &asset_instance)?394			.ok_or(XcmError::AssetNotFound)?;395396		if xcm_ext.token_has_children(token_id) {397			return Err(XcmError::Unimplemented);398		}399400		let depositor = &from;401		let to = Self::pallet_account();402		let amount = 1;403		xcm_ext404			.transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)405			.map_err(|_| XcmError::FailedToTransactAsset("nonfungible item withdraw failed"))?;406407		Ok(())408	}409}410411impl<T: Config> TransactAsset for Pallet<T> {412	fn can_check_in(413		_origin: &MultiLocation,414		_what: &MultiAsset,415		_context: &XcmContext,416	) -> XcmResult {417		Err(XcmError::Unimplemented)418	}419420	fn check_in(_origin: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}421422	fn can_check_out(423		_dest: &MultiLocation,424		_what: &MultiAsset,425		_context: &XcmContext,426	) -> XcmResult {427		Err(XcmError::Unimplemented)428	}429430	fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}431432	fn deposit_asset(what: &MultiAsset, to: &MultiLocation, _context: &XcmContext) -> XcmResult {433		let to = T::LocationToAccountId::convert_location(to)434			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;435436		let collection_id = Self::multiasset_to_collection(what)?;437		let dispatch =438			T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;439440		let collection = dispatch.as_dyn();441		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;442443		match what.fun {444			Fungibility::Fungible(amount) => xcm_ext445				.create_item(446					&Self::pallet_account(),447					to,448					CreateItemData::Fungible(CreateFungibleData { value: amount }),449					&ZeroBudget,450				)451				.map(|_| ())452				.map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),453454			Fungibility::NonFungible(asset_instance) => {455				Self::deposit_asset_instance(xcm_ext, collection_id, &asset_instance, to)456			}457		}458	}459460	fn withdraw_asset(461		what: &MultiAsset,462		from: &MultiLocation,463		_maybe_context: Option<&XcmContext>,464	) -> Result<staging_xcm_executor::Assets, XcmError> {465		let from = T::LocationToAccountId::convert_location(from)466			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;467468		let collection_id = Self::multiasset_to_collection(what)?;469		let dispatch =470			T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;471472		let collection = dispatch.as_dyn();473		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;474475		match what.fun {476			Fungibility::Fungible(amount) => xcm_ext477				.burn_item(from, TokenId::default(), amount)478				.map_err(|_| XcmError::FailedToTransactAsset("fungible item withdraw failed"))?,479480			Fungibility::NonFungible(asset_instance) => {481				Self::withdraw_asset_instance(xcm_ext, collection_id, &asset_instance, from)?;482			}483		}484485		Ok(what.clone().into())486	}487488	fn internal_transfer_asset(489		what: &MultiAsset,490		from: &MultiLocation,491		to: &MultiLocation,492		_context: &XcmContext,493	) -> Result<staging_xcm_executor::Assets, XcmError> {494		let from = T::LocationToAccountId::convert_location(from)495			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;496497		let to = T::LocationToAccountId::convert_location(to)498			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;499500		let collection_id = Self::multiasset_to_collection(what)?;501502		let dispatch =503			T::CollectionDispatch::dispatch(collection_id).map_err(|_| XcmError::AssetNotFound)?;504		let collection = dispatch.as_dyn();505		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;506507		let depositor = &from;508509		let token_id;510		let amount;511		let map_error: fn(DispatchError) -> XcmError;512513		match what.fun {514			Fungibility::Fungible(fungible_amount) => {515				token_id = TokenId::default();516				amount = fungible_amount;517				map_error = |_| XcmError::FailedToTransactAsset("fungible item transfer failed");518			}519520			Fungibility::NonFungible(asset_instance) => {521				token_id = Self::asset_instance_to_token_id(collection_id, &asset_instance)?522					.ok_or(XcmError::AssetNotFound)?;523524				amount = 1;525				map_error = |_| XcmError::FailedToTransactAsset("nonfungible item transfer failed")526			}527		}528529		xcm_ext530			.transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)531			.map_err(map_error)?;532533		Ok(what.clone().into())534	}535}536537pub struct CurrencyIdConvert<T: Config>(PhantomData<T>);538impl<T: Config> sp_runtime::traits::Convert<CollectionId, Option<MultiLocation>>539	for CurrencyIdConvert<T>540{541	fn convert(collection_id: CollectionId) -> Option<MultiLocation> {542		if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {543			Some(Here.into())544		} else {545			<Pallet<T>>::collection_to_foreign_reserve_location(collection_id).or_else(|| {546				T::SelfLocation::get()547					.pushed_with_interior(GeneralIndex(collection_id.0.into()))548					.ok()549			})550		}551	}552}553554pub use frame_support::{555	traits::{556		fungibles::Balanced, tokens::currency::Currency as CurrencyT, OnUnbalanced as OnUnbalancedT,557	},558	weights::{WeightToFee, WeightToFeePolynomial},559};560561pub struct FreeForAll;562563impl WeightTrader for FreeForAll {564	fn new() -> Self {565		Self566	}567568	fn buy_weight(569		&mut self,570		weight: Weight,571		payment: Assets,572		_xcm: &XcmContext,573	) -> Result<Assets, XcmError> {574		log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);575		Ok(payment)576	}577}
modifiedpallets/unique/src/eth/mod.rsdiffbeforeafterboth
--- a/pallets/unique/src/eth/mod.rs
+++ b/pallets/unique/src/eth/mod.rs
@@ -110,8 +110,9 @@
 	let collection_helpers_address =
 		T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());
 
-	let collection_id = T::CollectionDispatch::create(caller, collection_helpers_address, data)
-		.map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;
+	let collection_id =
+		T::CollectionDispatch::create(caller, Some(collection_helpers_address), data)
+			.map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;
 	let address = pallet_common::eth::collection_id_to_address(collection_id);
 	Ok(address)
 }
@@ -240,8 +241,9 @@
 		let collection_helpers_address =
 			T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());
 
-		let collection_id = T::CollectionDispatch::create(caller, collection_helpers_address, data)
-			.map_err(dispatch_to_evm::<T>)?;
+		let collection_id =
+			T::CollectionDispatch::create(caller, Some(collection_helpers_address), data)
+				.map_err(dispatch_to_evm::<T>)?;
 
 		let address = pallet_common::eth::collection_id_to_address(collection_id);
 		Ok(address)
@@ -274,8 +276,9 @@
 		check_sent_amount_equals_collection_creation_price::<T>(value)?;
 		let collection_helpers_address =
 			T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());
-		let collection_id = T::CollectionDispatch::create(caller, collection_helpers_address, data)
-			.map_err(dispatch_to_evm::<T>)?;
+		let collection_id =
+			T::CollectionDispatch::create(caller, Some(collection_helpers_address), data)
+				.map_err(dispatch_to_evm::<T>)?;
 
 		let address = pallet_common::eth::collection_id_to_address(collection_id);
 		Ok(address)
modifiedpallets/unique/src/lib.rsdiffbeforeafterboth
--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -401,7 +401,7 @@
 
 			// =========
 			let sender = T::CrossAccountId::from_sub(sender);
-			let _id = T::CollectionDispatch::create(sender.clone(), sender, data)?;
+			let _id = T::CollectionDispatch::create(sender.clone(), Some(sender), data)?;
 
 			Ok(())
 		}
modifiedruntime/common/dispatch.rsdiffbeforeafterboth
--- a/runtime/common/dispatch.rs
+++ b/runtime/common/dispatch.rs
@@ -18,7 +18,7 @@
 use pallet_balances_adapter::NativeFungibleHandle;
 pub use pallet_common::dispatch::CollectionDispatch;
 use pallet_common::{
-	erc::CommonEvmHandler, eth::map_eth_to_id, unsupported, CollectionById, CollectionHandle,
+	erc::CommonEvmHandler, eth::map_eth_to_id, CollectionById, CollectionHandle,
 	CommonCollectionOperations, Pallet as PalletCommon,
 };
 use pallet_evm::{PrecompileHandle, PrecompileResult};
@@ -68,7 +68,7 @@
 
 	fn create(
 		sender: T::CrossAccountId,
-		payer: T::CrossAccountId,
+		payer: Option<T::CrossAccountId>,
 		data: CreateCollectionData<T::CrossAccountId>,
 	) -> Result<CollectionId, DispatchError> {
 		match data.mode {
@@ -85,28 +85,7 @@
 
 			_ => {}
 		};
-
-		<PalletCommon<T>>::init_collection(sender, Some(payer), data)
-	}
 
-	fn create_foreign(
-		sender: <T>::CrossAccountId,
-		data: CreateCollectionData<<T>::CrossAccountId>,
-	) -> Result<CollectionId, DispatchError> {
-		match data.mode {
-			CollectionMode::Fungible(decimal_points) => {
-				// check params
-				ensure!(
-					decimal_points <= MAX_DECIMAL_POINTS,
-					pallet_unique::Error::<T>::CollectionDecimalPointLimitExceeded
-				);
-			}
-
-			CollectionMode::ReFungible => return unsupported!(T),
-			_ => {}
-		};
-
-		let payer = None;
 		<PalletCommon<T>>::init_collection(sender, payer, data)
 	}