git.delta.rocks / unique-network / refs/commits / 9d0fd83a5da6

difftreelog

fix use CollectionIssuer enum for collection creation

Daniel Shiposha2023-11-24parent: #b4d745d.patch.diff
in: master

6 files changed

modifiedpallets/common/src/dispatch.rsdiffbeforeafterboth
--- a/pallets/common/src/dispatch.rs
+++ b/pallets/common/src/dispatch.rs
@@ -11,7 +11,7 @@
 use sp_weights::Weight;
 use up_data_structs::{CollectionId, CreateCollectionData};
 
-use crate::{pallet::Config, CommonCollectionOperations};
+use crate::{pallet::Config, CollectionIssuer, CommonCollectionOperations};
 
 // TODO: move to benchmarking
 /// Price of [`dispatch_tx`] call with noop `call` argument
@@ -72,26 +72,11 @@
 	/// Create a regular 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` - If set, the user who pays the collection creation deposit.
+	/// * `issuer` - An entity that creates the collection.
 	/// * `data` - Description of the created collection.
 	fn create(
 		sender: T::CrossAccountId,
-		payer: Option<T::CrossAccountId>,
-		data: CreateCollectionData<T::CrossAccountId>,
-	) -> Result<CollectionId, DispatchError> {
-		Self::create_raw(sender, payer, false, data)
-	}
-
-	/// Function for creating regular and special collections.
-	///
-	/// * `sender` - The user who will become the owner of the collection.
-	/// * `payer` - If set, the user who pays the collection creation deposit.
-	/// * `data` - Description of the created collection.
-	/// * `is_special_collection` -- Whether this collection is a special one, i.e. can have special flags set.
-	fn create_raw(
-		sender: T::CrossAccountId,
-		payer: Option<T::CrossAccountId>,
-		is_special_collection: bool,
+		issuer: CollectionIssuer<T::CrossAccountId>,
 		data: CreateCollectionData<T::CrossAccountId>,
 	) -> Result<CollectionId, DispatchError>;
 
modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -944,6 +944,15 @@
 	}
 }
 
+/// An issuer of a collection.
+pub enum CollectionIssuer<CrossAccountId> {
+	/// A user who creates the collection.
+	User(CrossAccountId),
+
+	/// The internal mechanisms are creating the collection.
+	Internals,
+}
+
 fn check_token_permissions<T: Config>(
 	collection_admin_permitted: bool,
 	token_owner_permitted: bool,
@@ -1128,32 +1137,34 @@
 	/// Create new collection.
 	///
 	/// * `owner` - The owner of the collection.
-	/// * `payer` - If set, the user that will pay a deposit for the collection creation.
-	/// * `data` - Description of the created collection.
+	/// * `issuer` - An entity that creates the collection.
 	/// * `is_special_collection` -- Whether this collection is a special one, i.e. can have special flags set.
 	pub fn init_collection(
 		owner: T::CrossAccountId,
-		payer: Option<T::CrossAccountId>,
-		is_special_collection: bool,
+		issuer: CollectionIssuer<T::CrossAccountId>,
 		data: CreateCollectionData<T::CrossAccountId>,
 	) -> Result<CollectionId, DispatchError> {
-		if !is_special_collection {
-			ensure!(data.flags.is_allowed_for_user(), <Error<T>>::NoPermission);
-		}
+		match issuer {
+			CollectionIssuer::User(payer) => {
+				ensure!(data.flags.is_allowed_for_user(), <Error<T>>::NoPermission);
 
-		// Take a (non-refundable) deposit of collection creation
-		if let Some(payer) = payer {
-			let mut imbalance = <Debt<T::AccountId, <T as Config>::Currency>>::zero();
-			imbalance.subsume(<T as Config>::Currency::deposit(
-				&T::TreasuryAccountId::get(),
-				T::CollectionCreationPrice::get(),
-				Precision::Exact,
-			)?);
-			let credit =
-				<T as Config>::Currency::settle(payer.as_sub(), imbalance, Preservation::Preserve)
-					.map_err(|_| Error::<T>::NotSufficientFounds)?;
+				// Take a (non-refundable) deposit of collection creation
+				let mut imbalance = <Debt<T::AccountId, <T as Config>::Currency>>::zero();
+				imbalance.subsume(<T as Config>::Currency::deposit(
+					&T::TreasuryAccountId::get(),
+					T::CollectionCreationPrice::get(),
+					Precision::Exact,
+				)?);
+				let credit = <T as Config>::Currency::settle(
+					payer.as_sub(),
+					imbalance,
+					Preservation::Preserve,
+				)
+				.map_err(|_| Error::<T>::NotSufficientFounds)?;
 
-			debug_assert!(credit.peek().is_zero())
+				debug_assert!(credit.peek().is_zero());
+			}
+			CollectionIssuer::Internals => {}
 		}
 
 		{
modifiedpallets/foreign-assets/src/lib.rsdiffbeforeafterboth
after · pallets/foreign-assets/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Foreign Assets18//!19//! ## Overview20//!21//! The Foreign Assets is a proxy that maps XCM operations to the Unique Network's pallets logic.2223#![cfg_attr(not(feature = "std"), no_std)]24#![allow(clippy::unused_unit)]2526use core::ops::Deref;2728use frame_support::{dispatch::DispatchResult, pallet_prelude::*, traits::EnsureOrigin, PalletId};29use frame_system::pallet_prelude::*;30use pallet_common::{31	dispatch::CollectionDispatch, erc::CrossAccountId, XcmExtensions, NATIVE_FUNGIBLE_COLLECTION_ID,32};33use sp_runtime::traits::AccountIdConversion;34use sp_std::{boxed::Box, vec, vec::Vec};35use staging_xcm::{36	opaque::latest::{prelude::XcmError, Weight},37	v3::{prelude::*, MultiAsset, XcmContext},38};39use staging_xcm_executor::{40	traits::{ConvertLocation, Error as XcmExecutorError, TransactAsset, WeightTrader},41	Assets,42};43use up_data_structs::{44	budget::ZeroBudget, CollectionId, CollectionMode, CollectionName, CollectionTokenPrefix,45	CreateCollectionData, CreateFungibleData, CreateItemData, CreateNftData, Property, PropertyKey,46	TokenId,47};4849pub mod weights;5051#[cfg(feature = "runtime-benchmarks")]52mod benchmarking;5354pub use module::*;55pub use weights::WeightInfo;5657#[frame_support::pallet]58pub mod module {59	use pallet_common::CollectionIssuer;60	use up_data_structs::{61		CollectionDescription, Property, PropertyKeyPermission, PropertyPermission,62	};6364	use super::*;6566	#[pallet::config]67	pub trait Config:68		frame_system::Config69		+ pallet_common::Config70		+ pallet_fungible::Config71		+ pallet_balances::Config72	{73		/// The overarching event type.74		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;7576		/// Origin for force registering of a foreign asset.77		type ForceRegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;7879		/// The ID of the foreign assets pallet.80		type PalletId: Get<PalletId>;8182		/// Self-location of this parachain.83		type SelfLocation: Get<MultiLocation>;8485		/// The converter from a MultiLocation to a CrossAccountId.86		type LocationToAccountId: ConvertLocation<Self::CrossAccountId>;8788		/// Weight information for the extrinsics in this module.89		type WeightInfo: WeightInfo;90	}9192	#[pallet::error]93	pub enum Error<T> {94		/// The foreign asset is already registered.95		ForeignAssetAlreadyRegistered,96	}9798	#[pallet::event]99	#[pallet::generate_deposit(fn deposit_event)]100	pub enum Event<T: Config> {101		/// The foreign asset registered.102		ForeignAssetRegistered {103			collection_id: CollectionId,104			asset_id: Box<AssetId>,105		},106	}107108	/// The corresponding collections of foreign assets.109	#[pallet::storage]110	#[pallet::getter(fn foreign_asset_to_collection)]111	pub type ForeignAssetToCollection<T: Config> =112		StorageMap<_, Twox64Concat, staging_xcm::v3::AssetId, CollectionId, OptionQuery>;113114	/// The corresponding foreign assets of collections.115	#[pallet::storage]116	#[pallet::getter(fn collection_to_foreign_asset)]117	pub type CollectionToForeignAsset<T: Config> =118		StorageMap<_, Twox64Concat, CollectionId, staging_xcm::v3::AssetId, OptionQuery>;119120	/// The correponding NFT token id of reserve NFTs121	#[pallet::storage]122	#[pallet::getter(fn foreign_reserve_asset_instance_to_token_id)]123	pub type ForeignReserveAssetInstanceToTokenId<T: Config> = StorageDoubleMap<124		Hasher1 = Twox64Concat,125		Key1 = CollectionId,126		Hasher2 = Blake2_128Concat,127		Key2 = staging_xcm::v3::AssetInstance,128		Value = TokenId,129		QueryKind = OptionQuery,130	>;131132	/// The correponding reserve NFT of a token ID133	#[pallet::storage]134	#[pallet::getter(fn token_id_to_foreign_reserve_asset_instance)]135	pub type TokenIdToForeignReserveAssetInstance<T: Config> = StorageDoubleMap<136		Hasher1 = Twox64Concat,137		Key1 = CollectionId,138		Hasher2 = Blake2_128Concat,139		Key2 = TokenId,140		Value = staging_xcm::v3::AssetInstance,141		QueryKind = OptionQuery,142	>;143144	#[pallet::pallet]145	pub struct Pallet<T>(_);146147	#[pallet::call]148	impl<T: Config> Pallet<T> {149		#[pallet::call_index(0)]150		#[pallet::weight(<T as Config>::WeightInfo::register_foreign_asset())]151		pub fn force_register_foreign_asset(152			origin: OriginFor<T>,153			asset_id: Box<AssetId>,154			name: CollectionName,155			token_prefix: CollectionTokenPrefix,156			mode: ForeignCollectionMode,157		) -> DispatchResult {158			T::ForceRegisterOrigin::ensure_origin(origin.clone())?;159160			ensure!(161				!<ForeignAssetToCollection<T>>::contains_key(*asset_id),162				<Error<T>>::ForeignAssetAlreadyRegistered,163			);164165			let foreign_collection_owner = Self::pallet_account();166167			let description: CollectionDescription = "Foreign Assets Collection"168				.encode_utf16()169				.collect::<Vec<_>>()170				.try_into()171				.expect("description length < max description length; qed");172173			let collection_id = T::CollectionDispatch::create(174				foreign_collection_owner,175				CollectionIssuer::Internals,176				CreateCollectionData {177					name,178					token_prefix,179					description,180					mode: mode.into(),181182					properties: vec![Property {183						key: Self::reserve_location_property_key(),184						value: asset_id185							.encode()186							.try_into()187							.expect("multilocation is less than 32k; qed"),188					}]189					.try_into()190					.expect("just one property can always be stored; qed"),191192					token_property_permissions: vec![PropertyKeyPermission {193						key: Self::reserve_asset_instance_property_key(),194						permission: PropertyPermission {195							mutable: false,196							collection_admin: true,197							token_owner: false,198						},199					}]200					.try_into()201					.expect("just one property permission can always be stored; qed"),202					..Default::default()203				},204			)?;205206			<ForeignAssetToCollection<T>>::insert(*asset_id, collection_id);207			<CollectionToForeignAsset<T>>::insert(collection_id, *asset_id);208209			Self::deposit_event(Event::<T>::ForeignAssetRegistered {210				collection_id,211				asset_id,212			});213214			Ok(())215		}216	}217}218219impl<T: Config> Pallet<T> {220	fn pallet_account() -> T::CrossAccountId {221		let owner: T::AccountId = T::PalletId::get().into_account_truncating();222		T::CrossAccountId::from_sub(owner)223	}224225	fn reserve_location_property_key() -> PropertyKey {226		b"reserve-location"227			.to_vec()228			.try_into()229			.expect("key length < max property key length; qed")230	}231232	fn reserve_asset_instance_property_key() -> PropertyKey {233		b"reserve-asset-instance"234			.to_vec()235			.try_into()236			.expect("key length < max property key length; qed")237	}238239	/// Converts a concrete asset ID (the asset multilocation) to a local collection on Unique Network.240	///241	/// The multilocation corresponds to a local collection if:242	/// * It is `Here` location that corresponds to the native token of this parachain.243	/// * It is `../Parachain(<Unique Network Para ID>)` that also corresponds to the native token of this parachain.244	/// * It is `../Parachain(<Unique Network Para ID>)/GeneralIndex(<Collection ID>)` that corresponds245	/// to the collection with the ID equal to `<Collection ID>`. The `<Collection ID>` must be in the valid range,246	/// otherwise `None` is returned.247	/// * It is `GeneralIndex(<Collection ID>)`. Same as the last one above.248	///249	/// If the multilocation doesn't match the patterns listed above,250	/// or the `<Collection ID>` points to a foreign collection,251	/// `None` is returned, identifying that the given multilocation doesn't correspond to a local collection.252	fn local_asset_id_to_collection(asset_id: &AssetId) -> Option<CollectionLocality> {253		let AssetId::Concrete(asset_location) = asset_id else {254			return None;255		};256257		let self_location = T::SelfLocation::get();258259		if *asset_location == Here.into() || *asset_location == self_location {260			return Some(CollectionLocality::Local(NATIVE_FUNGIBLE_COLLECTION_ID));261		}262263		let prefix = if asset_location.parents == 0 {264			&Here265		} else if asset_location.parents == self_location.parents {266			&self_location.interior267		} else {268			return None;269		};270271		let GeneralIndex(collection_id) = asset_location.interior.match_and_split(prefix)? else {272			return None;273		};274275		let collection_id = CollectionId((*collection_id).try_into().ok()?);276277		Self::collection_to_foreign_asset(collection_id)278			.is_none()279			.then_some(CollectionLocality::Local(collection_id))280	}281282	/// Converts an asset ID to a Unique Network's collection locality (either foreign or a local one).283	///284	/// The function will check if the asset's reserve location has the corresponding285	/// foreign collection on Unique Network,286	/// and will return the "foreign" locality containing the collection ID if found.287	///288	/// If no corresponding foreign collection is found, the function will check289	/// if the asset's reserve location corresponds to a local collection.290	/// If the local collection is found, the "local" locality with the collection ID is returned.291	///292	/// If all of the above have failed, the `AssetIdConversionFailed` error will be returned.293	fn asset_to_collection(asset_id: &AssetId) -> Result<CollectionLocality, XcmError> {294		Self::foreign_asset_to_collection(asset_id)295			.map(CollectionLocality::Foreign)296			.or_else(|| Self::local_asset_id_to_collection(asset_id))297			.ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())298	}299300	/// Converts an XCM asset instance of local collection to the Unique Network's token ID.301	///302	/// The asset instance corresponds to the Unique Network's token ID if it is in the following format:303	/// `AssetInstance::Index(<token ID>)`.304	///305	/// If the asset instance is not in the valid format or the `<token ID>` can't fit into the valid token ID,306	/// `None` will be returned.307	fn local_asset_instance_to_token_id(asset_instance: &AssetInstance) -> Option<TokenId> {308		match asset_instance {309			AssetInstance::Index(token_id) => Some(TokenId((*token_id).try_into().ok()?)),310			_ => None,311		}312	}313314	/// Obtains the token ID of the `asset_instance` in the collection.315	fn asset_instance_to_token_id(316		collection_locality: CollectionLocality,317		asset_instance: &AssetInstance,318	) -> Option<TokenId> {319		match collection_locality {320			CollectionLocality::Local(_) => Self::local_asset_instance_to_token_id(asset_instance),321			CollectionLocality::Foreign(collection_id) => {322				Self::foreign_reserve_asset_instance_to_token_id(collection_id, asset_instance)323			}324		}325	}326327	/// Creates a foreign item in the the collection.328	fn create_foreign_asset_instance(329		xcm_ext: &dyn XcmExtensions<T>,330		collection_id: CollectionId,331		asset_instance: &AssetInstance,332		to: T::CrossAccountId,333	) -> DispatchResult {334		let asset_instance_encoded = asset_instance.encode();335336		let derivative_token_id = xcm_ext.create_item(337			&Self::pallet_account(),338			to,339			CreateItemData::NFT(CreateNftData {340				properties: vec![Property {341					key: Self::reserve_asset_instance_property_key(),342					value: asset_instance_encoded343						.try_into()344						.expect("asset instance length <= 32 bytes which is less than value length limit; qed"),345				}]346				.try_into()347				.expect("just one property can always be stored; qed"),348			}),349			&ZeroBudget,350		)?;351352		<ForeignReserveAssetInstanceToTokenId<T>>::insert(353			collection_id,354			asset_instance,355			derivative_token_id,356		);357358		<TokenIdToForeignReserveAssetInstance<T>>::insert(359			collection_id,360			derivative_token_id,361			asset_instance,362		);363364		Ok(())365	}366367	/// Deposits an asset instance to the `to` account.368	///369	/// Either transfers an existing item from the pallet's account370	/// or creates a foreign item.371	fn deposit_asset_instance(372		xcm_ext: &dyn XcmExtensions<T>,373		collection_locality: CollectionLocality,374		asset_instance: &AssetInstance,375		to: T::CrossAccountId,376	) -> XcmResult {377		let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance);378379		let deposit_result = match (collection_locality, token_id) {380			(_, Some(token_id)) => {381				let depositor = &Self::pallet_account();382				let from = depositor;383				let amount = 1;384385				xcm_ext.transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)386			}387			(CollectionLocality::Foreign(collection_id), None) => {388				Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)389			}390			(CollectionLocality::Local(_), None) => {391				return Err(XcmError::AssetNotFound);392			}393		};394395		deposit_result396			.map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))397	}398399	/// Withdraws an asset instance from the `from` account.400	///401	/// Transfers the asset instance to the pallet's account.402	fn withdraw_asset_instance(403		xcm_ext: &dyn XcmExtensions<T>,404		collection_locality: CollectionLocality,405		asset_instance: &AssetInstance,406		from: T::CrossAccountId,407	) -> XcmResult {408		let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance)409			.ok_or(XcmError::AssetNotFound)?;410411		let depositor = &from;412		let to = Self::pallet_account();413		let amount = 1;414		xcm_ext415			.transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)416			.map_err(|_| XcmError::FailedToTransactAsset("non-fungible item withdraw failed"))?;417418		Ok(())419	}420}421422impl<T: Config> TransactAsset for Pallet<T> {423	fn can_check_in(424		_origin: &MultiLocation,425		_what: &MultiAsset,426		_context: &XcmContext,427	) -> XcmResult {428		Err(XcmError::Unimplemented)429	}430431	fn check_in(_origin: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}432433	fn can_check_out(434		_dest: &MultiLocation,435		_what: &MultiAsset,436		_context: &XcmContext,437	) -> XcmResult {438		Err(XcmError::Unimplemented)439	}440441	fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}442443	fn deposit_asset(444		what: &MultiAsset,445		to: &MultiLocation,446		_context: Option<&XcmContext>,447	) -> XcmResult {448		let to = T::LocationToAccountId::convert_location(to)449			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;450451		let collection_locality = Self::asset_to_collection(&what.id)?;452		let dispatch = T::CollectionDispatch::dispatch(*collection_locality)453			.map_err(|_| XcmError::AssetNotFound)?;454455		let collection = dispatch.as_dyn();456		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;457458		match what.fun {459			Fungibility::Fungible(amount) => xcm_ext460				.create_item(461					&Self::pallet_account(),462					to,463					CreateItemData::Fungible(CreateFungibleData { value: amount }),464					&ZeroBudget,465				)466				.map(|_| ())467				.map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),468469			Fungibility::NonFungible(asset_instance) => {470				Self::deposit_asset_instance(xcm_ext, collection_locality, &asset_instance, to)471			}472		}473	}474475	fn withdraw_asset(476		what: &MultiAsset,477		from: &MultiLocation,478		_maybe_context: Option<&XcmContext>,479	) -> Result<staging_xcm_executor::Assets, XcmError> {480		let from = T::LocationToAccountId::convert_location(from)481			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;482483		let collection_locality = Self::asset_to_collection(&what.id)?;484		let dispatch = T::CollectionDispatch::dispatch(*collection_locality)485			.map_err(|_| XcmError::AssetNotFound)?;486487		let collection = dispatch.as_dyn();488		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;489490		match what.fun {491			Fungibility::Fungible(amount) => xcm_ext492				.burn_item(from, TokenId::default(), amount)493				.map_err(|_| XcmError::FailedToTransactAsset("fungible item withdraw failed"))?,494495			Fungibility::NonFungible(asset_instance) => {496				Self::withdraw_asset_instance(xcm_ext, collection_locality, &asset_instance, from)?;497			}498		}499500		Ok(what.clone().into())501	}502503	fn internal_transfer_asset(504		what: &MultiAsset,505		from: &MultiLocation,506		to: &MultiLocation,507		_context: &XcmContext,508	) -> Result<staging_xcm_executor::Assets, XcmError> {509		let from = T::LocationToAccountId::convert_location(from)510			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;511512		let to = T::LocationToAccountId::convert_location(to)513			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;514515		let collection_locality = Self::asset_to_collection(&what.id)?;516517		let dispatch = T::CollectionDispatch::dispatch(*collection_locality)518			.map_err(|_| XcmError::AssetNotFound)?;519		let collection = dispatch.as_dyn();520		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;521522		let depositor = &from;523524		let token_id;525		let amount;526		let map_error: fn(DispatchError) -> XcmError;527528		match what.fun {529			Fungibility::Fungible(fungible_amount) => {530				token_id = TokenId::default();531				amount = fungible_amount;532				map_error = |_| XcmError::FailedToTransactAsset("fungible item transfer failed");533			}534535			Fungibility::NonFungible(asset_instance) => {536				token_id = Self::asset_instance_to_token_id(collection_locality, &asset_instance)537					.ok_or(XcmError::AssetNotFound)?;538539				amount = 1;540				map_error = |_| XcmError::FailedToTransactAsset("non-fungible item transfer failed")541			}542		}543544		xcm_ext545			.transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)546			.map_err(map_error)?;547548		Ok(what.clone().into())549	}550}551552#[derive(Clone, Copy)]553pub enum CollectionLocality {554	Local(CollectionId),555	Foreign(CollectionId),556}557558impl Deref for CollectionLocality {559	type Target = CollectionId;560561	fn deref(&self) -> &Self::Target {562		match self {563			Self::Local(id) => id,564			Self::Foreign(id) => id,565		}566	}567}568569pub struct CurrencyIdConvert<T: Config>(PhantomData<T>);570impl<T: Config> sp_runtime::traits::Convert<CollectionId, Option<MultiLocation>>571	for CurrencyIdConvert<T>572{573	fn convert(collection_id: CollectionId) -> Option<MultiLocation> {574		if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {575			Some(T::SelfLocation::get())576		} else {577			<Pallet<T>>::collection_to_foreign_asset(collection_id)578				.and_then(|asset_id| match asset_id {579					AssetId::Concrete(location) => Some(location),580					_ => None,581				})582				.or_else(|| {583					T::SelfLocation::get()584						.pushed_with_interior(GeneralIndex(collection_id.0.into()))585						.ok()586				})587		}588	}589}590591#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]592pub enum ForeignCollectionMode {593	NFT,594	Fungible(u8),595}596597impl From<ForeignCollectionMode> for CollectionMode {598	fn from(value: ForeignCollectionMode) -> Self {599		match value {600			ForeignCollectionMode::NFT => Self::NFT,601			ForeignCollectionMode::Fungible(decimals) => Self::Fungible(decimals),602		}603	}604}605606pub struct FreeForAll;607608impl WeightTrader for FreeForAll {609	fn new() -> Self {610		Self611	}612613	fn buy_weight(614		&mut self,615		weight: Weight,616		payment: Assets,617		_xcm: &XcmContext,618	) -> Result<Assets, XcmError> {619		log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);620		Ok(payment)621	}622}
modifiedpallets/unique/src/eth/mod.rsdiffbeforeafterboth
--- a/pallets/unique/src/eth/mod.rs
+++ b/pallets/unique/src/eth/mod.rs
@@ -26,7 +26,7 @@
 	dispatch::CollectionDispatch,
 	erc::{static_property::key, CollectionHelpersEvents},
 	eth::{self, collection_id_to_address, map_eth_to_id},
-	CollectionById, CollectionHandle, Pallet as PalletCommon,
+	CollectionById, CollectionHandle, CollectionIssuer, Pallet as PalletCommon,
 };
 use pallet_evm::{account::CrossAccountId, OnMethodCall, PrecompileHandle, PrecompileResult};
 use pallet_evm_coder_substrate::{
@@ -110,9 +110,12 @@
 	let collection_helpers_address =
 		T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());
 
-	let collection_id =
-		T::CollectionDispatch::create(caller, Some(collection_helpers_address), data)
-			.map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;
+	let collection_id = T::CollectionDispatch::create(
+		caller,
+		CollectionIssuer::User(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)
 }
@@ -241,9 +244,12 @@
 		let collection_helpers_address =
 			T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());
 
-		let collection_id =
-			T::CollectionDispatch::create(caller, Some(collection_helpers_address), data)
-				.map_err(dispatch_to_evm::<T>)?;
+		let collection_id = T::CollectionDispatch::create(
+			caller,
+			CollectionIssuer::User(collection_helpers_address),
+			data,
+		)
+		.map_err(dispatch_to_evm::<T>)?;
 
 		let address = pallet_common::eth::collection_id_to_address(collection_id);
 		Ok(address)
@@ -276,9 +282,12 @@
 		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, Some(collection_helpers_address), data)
-				.map_err(dispatch_to_evm::<T>)?;
+		let collection_id = T::CollectionDispatch::create(
+			caller,
+			CollectionIssuer::User(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
@@ -93,7 +93,8 @@
 	use frame_system::{ensure_root, ensure_signed};
 	use pallet_common::{
 		dispatch::{dispatch_tx, CollectionDispatch},
-		CollectionHandle, CommonWeightInfo, Pallet as PalletCommon, RefungibleExtensionsWeightInfo,
+		CollectionHandle, CollectionIssuer, CommonWeightInfo, Pallet as PalletCommon,
+		RefungibleExtensionsWeightInfo,
 	};
 	use pallet_evm::account::CrossAccountId;
 	use pallet_structure::weights::WeightInfo as StructureWeightInfo;
@@ -401,7 +402,11 @@
 
 			// =========
 			let sender = T::CrossAccountId::from_sub(sender);
-			let _id = T::CollectionDispatch::create(sender.clone(), Some(sender), data)?;
+			let _id = T::CollectionDispatch::create(
+				sender.clone(),
+				CollectionIssuer::User(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, CollectionById, CollectionHandle,
+	erc::CommonEvmHandler, eth::map_eth_to_id, CollectionById, CollectionHandle, CollectionIssuer,
 	CommonCollectionOperations, Pallet as PalletCommon,
 };
 use pallet_evm::{PrecompileHandle, PrecompileResult};
@@ -66,10 +66,9 @@
 		}
 	}
 
-	fn create_raw(
+	fn create(
 		sender: T::CrossAccountId,
-		payer: Option<T::CrossAccountId>,
-		is_special_collection: bool,
+		issuer: CollectionIssuer<T::CrossAccountId>,
 		data: CreateCollectionData<T::CrossAccountId>,
 	) -> Result<CollectionId, DispatchError> {
 		match data.mode {
@@ -87,7 +86,7 @@
 			_ => {}
 		};
 
-		<PalletCommon<T>>::init_collection(sender, payer, is_special_collection, data)
+		<PalletCommon<T>>::init_collection(sender, issuer, data)
 	}
 
 	fn destroy(sender: T::CrossAccountId, collection_id: CollectionId) -> DispatchResult {