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

difftreelog

chore migrate foreign-assets to v4

Daniel Shiposha2024-05-27parent: #f78f490.patch.diff
in: master

1 file changed

modifiedpallets/foreign-assets/src/lib.rsdiffbeforeafterboth
before · pallets/foreign-assets/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Foreign Assets18//!19//! ## Overview20//!21//! The Foreign Assets is a proxy that maps XCM operations to the Unique Network's pallets logic.2223#![cfg_attr(not(feature = "std"), no_std)]24#![allow(clippy::unused_unit)]2526use core::ops::Deref;2728use frame_support::{dispatch::DispatchResult, pallet_prelude::*, traits::EnsureOrigin, PalletId};29use frame_system::pallet_prelude::*;30use pallet_common::{31	dispatch::CollectionDispatch, erc::CrossAccountId, XcmExtensions, NATIVE_FUNGIBLE_COLLECTION_ID,32};33use sp_runtime::traits::AccountIdConversion;34use sp_std::{boxed::Box, vec, vec::Vec};35use staging_xcm::{v4::prelude::*, VersionedAssetId};36use staging_xcm_executor::{37	traits::{ConvertLocation, Error as XcmExecutorError, TransactAsset, WeightTrader},38	AssetsInHolding,39};40use up_data_structs::{41	budget::ZeroBudget, CollectionId, CollectionMode, CollectionName, CollectionTokenPrefix,42	CreateCollectionData, CreateFungibleData, CreateItemData, TokenId,43};4445pub mod weights;4647#[cfg(feature = "runtime-benchmarks")]48mod benchmarking;4950pub use module::*;51pub use weights::WeightInfo;5253#[frame_support::pallet]54pub mod module {55	use pallet_common::CollectionIssuer;56	use up_data_structs::CollectionDescription;5758	use super::*;5960	#[pallet::config]61	pub trait Config:62		frame_system::Config63		+ pallet_common::Config64		+ pallet_fungible::Config65		+ pallet_balances::Config66	{67		/// The overarching event type.68		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;6970		/// Origin for force registering of a foreign asset.71		type ForceRegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;7273		/// The ID of the foreign assets pallet.74		type PalletId: Get<PalletId>;7576		/// Self-location of this parachain.77		type SelfLocation: Get<Location>;7879		/// The converter from a Location to a CrossAccountId.80		type LocationToAccountId: ConvertLocation<Self::CrossAccountId>;8182		/// Weight information for the extrinsics in this module.83		type WeightInfo: WeightInfo;84	}8586	#[pallet::error]87	pub enum Error<T> {88		/// The foreign asset is already registered.89		ForeignAssetAlreadyRegistered,9091		/// The given asset ID could not be converted into the current XCM version.92		BadForeignAssetId,93	}9495	#[pallet::event]96	#[pallet::generate_deposit(fn deposit_event)]97	pub enum Event<T: Config> {98		/// The foreign asset registered.99		ForeignAssetRegistered {100			collection_id: CollectionId,101			asset_id: Box<VersionedAssetId>,102		},103	}104105	/// The corresponding collections of foreign assets.106	#[pallet::storage]107	#[pallet::getter(fn foreign_asset_to_collection)]108	pub type ForeignAssetToCollection<T: Config> =109		StorageMap<_, Twox64Concat, staging_xcm::v4::AssetId, CollectionId, OptionQuery>;110111	/// The corresponding foreign assets of collections.112	#[pallet::storage]113	#[pallet::getter(fn collection_to_foreign_asset)]114	pub type CollectionToForeignAsset<T: Config> =115		StorageMap<_, Twox64Concat, CollectionId, staging_xcm::v4::AssetId, OptionQuery>;116117	/// The correponding NFT token id of reserve NFTs118	#[pallet::storage]119	#[pallet::getter(fn foreign_reserve_asset_instance_to_token_id)]120	pub type ForeignReserveAssetInstanceToTokenId<T: Config> = StorageDoubleMap<121		Hasher1 = Twox64Concat,122		Key1 = CollectionId,123		Hasher2 = Blake2_128Concat,124		// Key2 = staging_xcm::v3::AssetInstance,125		Key2 = staging_xcm::v4::AssetInstance,126		Value = TokenId,127		QueryKind = OptionQuery,128	>;129130	/// The correponding reserve NFT of a token ID131	#[pallet::storage]132	#[pallet::getter(fn token_id_to_foreign_reserve_asset_instance)]133	pub type TokenIdToForeignReserveAssetInstance<T: Config> = StorageDoubleMap<134		Hasher1 = Twox64Concat,135		Key1 = CollectionId,136		Hasher2 = Blake2_128Concat,137		Key2 = TokenId,138		// Value = staging_xcm::v3::AssetInstance,139		Value = staging_xcm::v4::AssetInstance,140		QueryKind = OptionQuery,141	>;142143	#[pallet::pallet]144	pub struct Pallet<T>(_);145146	#[pallet::call]147	impl<T: Config> Pallet<T> {148		#[pallet::call_index(0)]149		#[pallet::weight(<T as Config>::WeightInfo::force_register_foreign_asset())]150		pub fn force_register_foreign_asset(151			origin: OriginFor<T>,152			versioned_asset_id: Box<VersionedAssetId>,153			name: CollectionName,154			token_prefix: CollectionTokenPrefix,155			mode: ForeignCollectionMode,156		) -> DispatchResult {157			T::ForceRegisterOrigin::ensure_origin(origin.clone())?;158159			let asset_id: AssetId = versioned_asset_id160				.as_ref()161				.clone()162				.try_into()163				.map_err(|()| Error::<T>::BadForeignAssetId)?;164165			ensure!(166				!<ForeignAssetToCollection<T>>::contains_key(&asset_id),167				<Error<T>>::ForeignAssetAlreadyRegistered,168			);169170			let foreign_collection_owner = Self::pallet_account();171172			let description: CollectionDescription = "Foreign Assets Collection"173				.encode_utf16()174				.collect::<Vec<_>>()175				.try_into()176				.expect("description length < max description length; qed");177178			let collection_id = T::CollectionDispatch::create(179				foreign_collection_owner,180				CollectionIssuer::Internals,181				CreateCollectionData {182					name,183					token_prefix,184					description,185					mode: mode.into(),186					..Default::default()187				},188			)?;189190			<ForeignAssetToCollection<T>>::insert(&asset_id, collection_id);191			<CollectionToForeignAsset<T>>::insert(collection_id, asset_id);192193			Self::deposit_event(Event::<T>::ForeignAssetRegistered {194				collection_id,195				asset_id: versioned_asset_id,196			});197198			Ok(())199		}200	}201}202203impl<T: Config> Pallet<T> {204	fn pallet_account() -> T::CrossAccountId {205		let owner: T::AccountId = T::PalletId::get().into_account_truncating();206		T::CrossAccountId::from_sub(owner)207	}208209	/// Converts a concrete asset ID (the asset multilocation) to a local collection on Unique Network.210	///211	/// The multilocation corresponds to a local collection if:212	/// * It is `Here` location that corresponds to the native token of this parachain.213	/// * It is `../Parachain(<Unique Network Para ID>)` that also corresponds to the native token of this parachain.214	/// * It is `../Parachain(<Unique Network Para ID>)/GeneralIndex(<Collection ID>)` that corresponds215	/// to the collection with the ID equal to `<Collection ID>`. The `<Collection ID>` must be in the valid range,216	/// otherwise `None` is returned.217	/// * It is `GeneralIndex(<Collection ID>)`. Same as the last one above.218	///219	/// If the multilocation doesn't match the patterns listed above,220	/// or the `<Collection ID>` points to a foreign collection,221	/// `None` is returned, identifying that the given multilocation doesn't correspond to a local collection.222	fn local_asset_id_to_collection(223		AssetId(asset_location): &AssetId,224	) -> Option<CollectionLocality> {225		let self_location = T::SelfLocation::get();226227		if *asset_location == Here.into() || *asset_location == self_location {228			return Some(CollectionLocality::Local(NATIVE_FUNGIBLE_COLLECTION_ID));229		}230231		let prefix = if asset_location.parents == 0 {232			&Here233		} else if asset_location.parents == self_location.parents {234			&self_location.interior235		} else {236			return None;237		};238239		let GeneralIndex(collection_id) = asset_location.interior.match_and_split(prefix)? else {240			return None;241		};242243		let collection_id = CollectionId((*collection_id).try_into().ok()?);244245		Self::collection_to_foreign_asset(collection_id)246			.is_none()247			.then_some(CollectionLocality::Local(collection_id))248	}249250	/// Converts an asset ID to a Unique Network's collection locality (either foreign or a local one).251	///252	/// The function will check if the asset's reserve location has the corresponding253	/// foreign collection on Unique Network,254	/// and will return the "foreign" locality containing the collection ID if found.255	///256	/// If no corresponding foreign collection is found, the function will check257	/// if the asset's reserve location corresponds to a local collection.258	/// If the local collection is found, the "local" locality with the collection ID is returned.259	///260	/// If all of the above have failed, the `AssetIdConversionFailed` error will be returned.261	fn asset_to_collection(asset_id: &AssetId) -> Result<CollectionLocality, XcmError> {262		Self::foreign_asset_to_collection(asset_id)263			.map(CollectionLocality::Foreign)264			.or_else(|| Self::local_asset_id_to_collection(asset_id))265			.ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())266	}267268	/// Converts an XCM asset instance of local collection to the Unique Network's token ID.269	///270	/// The asset instance corresponds to the Unique Network's token ID if it is in the following format:271	/// `AssetInstance::Index(<token ID>)`.272	///273	/// If the asset instance is not in the valid format or the `<token ID>` can't fit into the valid token ID,274	/// `None` will be returned.275	///276	/// Note: this function can return `Some` containing the token ID of a non-existing NFT.277	/// It returns `None` when it failed to convert the `asset_instance` to a local ID.278	fn local_asset_instance_to_token_id(asset_instance: &AssetInstance) -> Option<TokenId> {279		match asset_instance {280			AssetInstance::Index(token_id) => Some(TokenId((*token_id).try_into().ok()?)),281			_ => None,282		}283	}284285	/// Obtains the token ID of the `asset_instance` in the collection.286	///287	/// Note: this function can return `Some` containing the token ID of a non-existing NFT.288	/// It returns `None` when it failed to convert the `asset_instance` to a local ID.289	fn asset_instance_to_token_id(290		collection_locality: CollectionLocality,291		asset_instance: &AssetInstance,292	) -> Option<TokenId> {293		match collection_locality {294			CollectionLocality::Local(_) => Self::local_asset_instance_to_token_id(asset_instance),295			CollectionLocality::Foreign(collection_id) => {296				Self::foreign_reserve_asset_instance_to_token_id(collection_id, asset_instance)297			}298		}299	}300301	/// Creates a foreign item in the the collection.302	fn create_foreign_asset_instance(303		xcm_ext: &dyn XcmExtensions<T>,304		collection_id: CollectionId,305		asset_instance: &AssetInstance,306		to: T::CrossAccountId,307	) -> DispatchResult {308		let derivative_token_id = xcm_ext.create_item(309			&Self::pallet_account(),310			to,311			CreateItemData::NFT(Default::default()),312			&ZeroBudget,313		)?;314315		<ForeignReserveAssetInstanceToTokenId<T>>::insert(316			collection_id,317			asset_instance,318			derivative_token_id,319		);320321		<TokenIdToForeignReserveAssetInstance<T>>::insert(322			collection_id,323			derivative_token_id,324			asset_instance,325		);326327		Ok(())328	}329330	/// Deposits an asset instance to the `to` account.331	///332	/// Either transfers an existing item from the pallet's account333	/// or creates a foreign item.334	fn deposit_asset_instance(335		xcm_ext: &dyn XcmExtensions<T>,336		collection_locality: CollectionLocality,337		asset_instance: &AssetInstance,338		to: T::CrossAccountId,339	) -> XcmResult {340		let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance);341342		let deposit_result = match (collection_locality, token_id) {343			(_, Some(token_id)) => {344				let depositor = &Self::pallet_account();345				let from = depositor;346				let amount = 1;347348				xcm_ext.transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)349			}350			(CollectionLocality::Foreign(collection_id), None) => {351				Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)352			}353			(CollectionLocality::Local(_), None) => {354				return Err(XcmExecutorError::InstanceConversionFailed.into());355			}356		};357358		deposit_result359			.map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))360	}361362	/// Withdraws an asset instance from the `from` account.363	///364	/// Transfers the asset instance to the pallet's account.365	fn withdraw_asset_instance(366		xcm_ext: &dyn XcmExtensions<T>,367		collection_locality: CollectionLocality,368		asset_instance: &AssetInstance,369		from: T::CrossAccountId,370	) -> XcmResult {371		let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance)372			.ok_or(XcmExecutorError::InstanceConversionFailed)?;373374		let depositor = &from;375		let to = Self::pallet_account();376		let amount = 1;377		xcm_ext378			.transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)379			.map_err(|_| XcmError::FailedToTransactAsset("non-fungible item withdraw failed"))?;380381		Ok(())382	}383}384385// #[derive()]386// pub enum Migration {387388// }389390impl<T: Config> TransactAsset for Pallet<T> {391	fn can_check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult {392		Err(XcmError::Unimplemented)393	}394395	fn check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) {}396397	fn can_check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult {398		Err(XcmError::Unimplemented)399	}400401	fn check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) {}402403	fn deposit_asset(what: &Asset, to: &Location, _context: Option<&XcmContext>) -> XcmResult {404		let to = T::LocationToAccountId::convert_location(to)405			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;406407		let collection_locality = Self::asset_to_collection(&what.id)?;408		let dispatch = T::CollectionDispatch::dispatch(*collection_locality)409			.map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;410411		let collection = dispatch.as_dyn();412		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;413414		match what.fun {415			Fungibility::Fungible(amount) => xcm_ext416				.create_item(417					&Self::pallet_account(),418					to,419					CreateItemData::Fungible(CreateFungibleData { value: amount }),420					&ZeroBudget,421				)422				.map(|_| ())423				.map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),424425			Fungibility::NonFungible(asset_instance) => {426				Self::deposit_asset_instance(xcm_ext, collection_locality, &asset_instance, to)427			}428		}429	}430431	fn withdraw_asset(432		what: &Asset,433		from: &Location,434		_maybe_context: Option<&XcmContext>,435	) -> Result<AssetsInHolding, XcmError> {436		let from = T::LocationToAccountId::convert_location(from)437			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;438439		let collection_locality = Self::asset_to_collection(&what.id)?;440		let dispatch = T::CollectionDispatch::dispatch(*collection_locality)441			.map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;442443		let collection = dispatch.as_dyn();444		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;445446		match what.fun {447			Fungibility::Fungible(amount) => xcm_ext448				.burn_item(from, TokenId::default(), amount)449				.map_err(|_| XcmError::FailedToTransactAsset("fungible item withdraw failed"))?,450451			Fungibility::NonFungible(asset_instance) => {452				Self::withdraw_asset_instance(xcm_ext, collection_locality, &asset_instance, from)?;453			}454		}455456		Ok(what.clone().into())457	}458459	fn internal_transfer_asset(460		what: &Asset,461		from: &Location,462		to: &Location,463		_context: &XcmContext,464	) -> Result<AssetsInHolding, XcmError> {465		let from = T::LocationToAccountId::convert_location(from)466			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;467468		let to = T::LocationToAccountId::convert_location(to)469			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;470471		let collection_locality = Self::asset_to_collection(&what.id)?;472473		let dispatch = T::CollectionDispatch::dispatch(*collection_locality)474			.map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;475		let collection = dispatch.as_dyn();476		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;477478		let depositor = &from;479480		let token_id;481		let amount;482		let map_error: fn(DispatchError) -> XcmError;483484		match what.fun {485			Fungibility::Fungible(fungible_amount) => {486				token_id = TokenId::default();487				amount = fungible_amount;488				map_error = |_| XcmError::FailedToTransactAsset("fungible item transfer failed");489			}490491			Fungibility::NonFungible(asset_instance) => {492				token_id = Self::asset_instance_to_token_id(collection_locality, &asset_instance)493					.ok_or(XcmExecutorError::InstanceConversionFailed)?;494495				amount = 1;496				map_error = |_| XcmError::FailedToTransactAsset("non-fungible item transfer failed")497			}498		}499500		xcm_ext501			.transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)502			.map_err(map_error)?;503504		Ok(what.clone().into())505	}506}507508#[derive(Clone, Copy)]509pub enum CollectionLocality {510	Local(CollectionId),511	Foreign(CollectionId),512}513514impl Deref for CollectionLocality {515	type Target = CollectionId;516517	fn deref(&self) -> &Self::Target {518		match self {519			Self::Local(id) => id,520			Self::Foreign(id) => id,521		}522	}523}524525pub struct CurrencyIdConvert<T: Config>(PhantomData<T>);526impl<T: Config> sp_runtime::traits::Convert<CollectionId, Option<Location>>527	for CurrencyIdConvert<T>528{529	fn convert(collection_id: CollectionId) -> Option<Location> {530		if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {531			Some(T::SelfLocation::get())532		} else {533			<Pallet<T>>::collection_to_foreign_asset(collection_id)534				.map(|AssetId(location)| location)535				.or_else(|| {536					T::SelfLocation::get()537						.pushed_with_interior(GeneralIndex(collection_id.0.into()))538						.ok()539				})540		}541	}542}543544#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]545pub enum ForeignCollectionMode {546	NFT,547	Fungible(u8),548}549550impl From<ForeignCollectionMode> for CollectionMode {551	fn from(value: ForeignCollectionMode) -> Self {552		match value {553			ForeignCollectionMode::NFT => Self::NFT,554			ForeignCollectionMode::Fungible(decimals) => Self::Fungible(decimals),555		}556	}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: AssetsInHolding,570		_xcm: &XcmContext,571	) -> Result<AssetsInHolding, XcmError> {572		log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);573		Ok(payment)574	}575}