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

difftreelog

source

pallets/foreign-assets/src/lib.rs14.4 KiBsourcehistory
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//! - [`Config`]20//! - [`Call`]21//! - [`Pallet`]22//!23//! ## Overview24//!25//! The foreign assests pallet provides functions for:26//!27//! - Local and foreign assets management. The foreign assets can be updated without runtime upgrade.28//! - Bounds between asset and target collection for cross chain transfer and inner transfers.29//!30//! ## Overview31//!32//! Under construction3334#![cfg_attr(not(feature = "std"), no_std)]35#![allow(clippy::unused_unit)]3637use frame_support::{38	dispatch::DispatchResult,39	ensure,40	pallet_prelude::*,41	traits::{fungible, fungibles, Currency, EnsureOrigin},42	RuntimeDebug,43};44use frame_system::pallet_prelude::*;45use up_data_structs::CollectionMode;46use pallet_fungible::Pallet as PalletFungible;47use scale_info::TypeInfo;48use sp_runtime::{49	traits::{One, Zero},50	ArithmeticError,51};52use sp_std::{boxed::Box, vec::Vec};53use up_data_structs::{CollectionId, TokenId, CreateCollectionData};5455// NOTE: MultiLocation is used in storages, we will need to do migration if upgrade the56// MultiLocation to the XCM v3.57use xcm::opaque::latest::{prelude::XcmError, Weight};58use xcm::{latest::MultiLocation, VersionedMultiLocation};59use xcm_executor::{traits::WeightTrader, Assets};6061use pallet_common::erc::CrossAccountId;6263#[cfg(feature = "std")]64use serde::{Deserialize, Serialize};6566// TODO: Move to primitives67// Id of native currency.68// 0 - QTZ\UNQ69// 1 - KSM\DOT70#[derive(71	Clone,72	Copy,73	Eq,74	PartialEq,75	PartialOrd,76	Ord,77	MaxEncodedLen,78	RuntimeDebug,79	Encode,80	Decode,81	TypeInfo,82	Serialize,83	Deserialize,84)]85pub enum NativeCurrency {86	Here = 0,87	Parent = 1,88}8990#[derive(91	Clone,92	Copy,93	Eq,94	PartialEq,95	PartialOrd,96	Ord,97	MaxEncodedLen,98	RuntimeDebug,99	Encode,100	Decode,101	TypeInfo,102	Serialize,103	Deserialize,104)]105pub enum AssetId {106	ForeignAssetId(ForeignAssetId),107	NativeAssetId(NativeCurrency),108}109110pub trait TryAsForeign<T, F> {111	fn try_as_foreign(asset: T) -> Option<F>;112}113114impl TryAsForeign<AssetId, ForeignAssetId> for AssetId {115	fn try_as_foreign(asset: AssetId) -> Option<ForeignAssetId> {116		match asset {117			Self::ForeignAssetId(id) => Some(id),118			_ => None,119		}120	}121}122123pub type ForeignAssetId = u32;124pub type CurrencyId = AssetId;125126mod impl_fungibles;127pub mod weights;128129#[cfg(feature = "runtime-benchmarks")]130mod benchmarking;131132pub use module::*;133pub use weights::WeightInfo;134135/// Type alias for currency balance.136pub type BalanceOf<T> =137	<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;138139/// A mapping between ForeignAssetId and AssetMetadata.140pub trait AssetIdMapping<ForeignAssetId, MultiLocation, AssetMetadata> {141	/// Returns the AssetMetadata associated with a given ForeignAssetId.142	fn get_asset_metadata(foreign_asset_id: ForeignAssetId) -> Option<AssetMetadata>;143	/// Returns the MultiLocation associated with a given ForeignAssetId.144	fn get_multi_location(foreign_asset_id: ForeignAssetId) -> Option<MultiLocation>;145	/// Returns the CurrencyId associated with a given MultiLocation.146	fn get_currency_id(multi_location: MultiLocation) -> Option<CurrencyId>;147}148149pub struct XcmForeignAssetIdMapping<T>(sp_std::marker::PhantomData<T>);150151impl<T: Config> AssetIdMapping<ForeignAssetId, MultiLocation, AssetMetadata<BalanceOf<T>>>152	for XcmForeignAssetIdMapping<T>153{154	fn get_asset_metadata(foreign_asset_id: ForeignAssetId) -> Option<AssetMetadata<BalanceOf<T>>> {155		log::trace!(target: "fassets::asset_metadatas", "call");156		Pallet::<T>::asset_metadatas(AssetId::ForeignAssetId(foreign_asset_id))157	}158159	fn get_multi_location(foreign_asset_id: ForeignAssetId) -> Option<MultiLocation> {160		log::trace!(target: "fassets::get_multi_location", "call");161		Pallet::<T>::foreign_asset_locations(foreign_asset_id)162	}163164	fn get_currency_id(multi_location: MultiLocation) -> Option<CurrencyId> {165		log::trace!(target: "fassets::get_currency_id", "call");166		Pallet::<T>::location_to_currency_ids(multi_location).map(AssetId::ForeignAssetId)167	}168}169170#[frame_support::pallet]171pub mod module {172	use super::*;173174	#[pallet::config]175	pub trait Config:176		frame_system::Config177		+ pallet_common::Config178		+ pallet_fungible::Config179		+ orml_tokens::Config180		+ pallet_balances::Config181	{182		/// The overarching event type.183		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;184185		/// Currency type for withdraw and balance storage.186		type Currency: Currency<Self::AccountId>;187188		/// Required origin for registering asset.189		type RegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;190191		/// Weight information for the extrinsics in this module.192		type WeightInfo: WeightInfo;193	}194195	pub type AssetName = BoundedVec<u8, ConstU32<32>>;196	pub type AssetSymbol = BoundedVec<u8, ConstU32<7>>;197198	#[derive(Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)]199	pub struct AssetMetadata<Balance> {200		pub name: AssetName,201		pub symbol: AssetSymbol,202		pub decimals: u8,203		pub minimal_balance: Balance,204	}205206	#[pallet::error]207	pub enum Error<T> {208		/// The given location could not be used (e.g. because it cannot be expressed in the209		/// desired version of XCM).210		BadLocation,211		/// MultiLocation existed212		MultiLocationExisted,213		/// AssetId not exists214		AssetIdNotExists,215		/// AssetId exists216		AssetIdExisted,217	}218219	#[pallet::event]220	#[pallet::generate_deposit(fn deposit_event)]221	pub enum Event<T: Config> {222		/// The foreign asset registered.223		ForeignAssetRegistered {224			asset_id: ForeignAssetId,225			asset_address: MultiLocation,226			metadata: AssetMetadata<BalanceOf<T>>,227		},228		/// The foreign asset updated.229		ForeignAssetUpdated {230			asset_id: ForeignAssetId,231			asset_address: MultiLocation,232			metadata: AssetMetadata<BalanceOf<T>>,233		},234		/// The asset registered.235		AssetRegistered {236			asset_id: AssetId,237			metadata: AssetMetadata<BalanceOf<T>>,238		},239		/// The asset updated.240		AssetUpdated {241			asset_id: AssetId,242			metadata: AssetMetadata<BalanceOf<T>>,243		},244	}245246	/// Next available Foreign AssetId ID.247	///248	/// NextForeignAssetId: ForeignAssetId249	#[pallet::storage]250	#[pallet::getter(fn next_foreign_asset_id)]251	pub type NextForeignAssetId<T: Config> = StorageValue<_, ForeignAssetId, ValueQuery>;252	/// The storages for MultiLocations.253	///254	/// ForeignAssetLocations: map ForeignAssetId => Option<MultiLocation>255	#[pallet::storage]256	#[pallet::getter(fn foreign_asset_locations)]257	pub type ForeignAssetLocations<T: Config> =258		StorageMap<_, Twox64Concat, ForeignAssetId, staging_xcm::v3::MultiLocation, OptionQuery>;259260	/// The storages for CurrencyIds.261	///262	/// LocationToCurrencyIds: map MultiLocation => Option<ForeignAssetId>263	#[pallet::storage]264	#[pallet::getter(fn location_to_currency_ids)]265	pub type LocationToCurrencyIds<T: Config> =266		StorageMap<_, Twox64Concat, staging_xcm::v3::MultiLocation, ForeignAssetId, OptionQuery>;267268	/// The storages for AssetMetadatas.269	///270	/// AssetMetadatas: map AssetIds => Option<AssetMetadata>271	#[pallet::storage]272	#[pallet::getter(fn asset_metadatas)]273	pub type AssetMetadatas<T: Config> =274		StorageMap<_, Twox64Concat, AssetId, AssetMetadata<BalanceOf<T>>, OptionQuery>;275276	/// The storages for assets to fungible collection binding277	///278	#[pallet::storage]279	#[pallet::getter(fn asset_binding)]280	pub type AssetBinding<T: Config> =281		StorageMap<_, Twox64Concat, ForeignAssetId, CollectionId, OptionQuery>;282283	#[pallet::pallet]284	pub struct Pallet<T>(_);285286	#[pallet::call]287	impl<T: Config> Pallet<T> {288		#[pallet::call_index(0)]289		#[pallet::weight(<T as Config>::WeightInfo::register_foreign_asset())]290		pub fn register_foreign_asset(291			origin: OriginFor<T>,292			owner: T::AccountId,293			location: Box<VersionedMultiLocation>,294			metadata: Box<AssetMetadata<BalanceOf<T>>>,295		) -> DispatchResult {296			T::RegisterOrigin::ensure_origin(origin.clone())?;297298			let location: MultiLocation = (*location)299				.try_into()300				.map_err(|()| Error::<T>::BadLocation)?;301302			let md = metadata.clone();303			let name: Vec<u16> = md.name.into_iter().map(|x| x as u16).collect::<Vec<u16>>();304			let mut description: Vec<u16> = "Foreign assets collection for "305				.encode_utf16()306				.collect::<Vec<u16>>();307			description.append(&mut name.clone());308309			let data: CreateCollectionData<T::CrossAccountId> = CreateCollectionData {310				name: name.try_into().unwrap(),311				description: description.try_into().unwrap(),312				mode: CollectionMode::Fungible(md.decimals),313				..Default::default()314			};315			let owner = T::CrossAccountId::from_sub(owner);316			let bounded_collection_id =317				<PalletFungible<T>>::init_foreign_collection(owner.clone(), owner, data)?;318			let foreign_asset_id =319				Self::do_register_foreign_asset(&location, &metadata, bounded_collection_id)?;320321			Self::deposit_event(Event::<T>::ForeignAssetRegistered {322				asset_id: foreign_asset_id,323				asset_address: location,324				metadata: *metadata,325			});326			Ok(())327		}328329		#[pallet::call_index(1)]330		#[pallet::weight(<T as Config>::WeightInfo::update_foreign_asset())]331		pub fn update_foreign_asset(332			origin: OriginFor<T>,333			foreign_asset_id: ForeignAssetId,334			location: Box<VersionedMultiLocation>,335			metadata: Box<AssetMetadata<BalanceOf<T>>>,336		) -> DispatchResult {337			T::RegisterOrigin::ensure_origin(origin)?;338339			let location: MultiLocation = (*location)340				.try_into()341				.map_err(|()| Error::<T>::BadLocation)?;342			Self::do_update_foreign_asset(foreign_asset_id, &location, &metadata)?;343344			Self::deposit_event(Event::<T>::ForeignAssetUpdated {345				asset_id: foreign_asset_id,346				asset_address: location,347				metadata: *metadata,348			});349			Ok(())350		}351	}352}353354impl<T: Config> Pallet<T> {355	fn get_next_foreign_asset_id() -> Result<ForeignAssetId, DispatchError> {356		NextForeignAssetId::<T>::try_mutate(|current| -> Result<ForeignAssetId, DispatchError> {357			let id = *current;358			*current = current359				.checked_add(One::one())360				.ok_or(ArithmeticError::Overflow)?;361			Ok(id)362		})363	}364365	fn do_register_foreign_asset(366		location: &MultiLocation,367		metadata: &AssetMetadata<BalanceOf<T>>,368		bounded_collection_id: CollectionId,369	) -> Result<ForeignAssetId, DispatchError> {370		let foreign_asset_id = Self::get_next_foreign_asset_id()?;371		LocationToCurrencyIds::<T>::try_mutate(location, |maybe_currency_ids| -> DispatchResult {372			ensure!(373				maybe_currency_ids.is_none(),374				Error::<T>::MultiLocationExisted375			);376			*maybe_currency_ids = Some(foreign_asset_id);377			// *maybe_currency_ids = Some(CurrencyId::ForeignAsset(foreign_asset_id));378379			ForeignAssetLocations::<T>::try_mutate(380				foreign_asset_id,381				|maybe_location| -> DispatchResult {382					ensure!(maybe_location.is_none(), Error::<T>::MultiLocationExisted);383					*maybe_location = Some(*location);384385					AssetMetadatas::<T>::try_mutate(386						AssetId::ForeignAssetId(foreign_asset_id),387						|maybe_asset_metadatas| -> DispatchResult {388							ensure!(maybe_asset_metadatas.is_none(), Error::<T>::AssetIdExisted);389							*maybe_asset_metadatas = Some(metadata.clone());390							Ok(())391						},392					)393				},394			)?;395396			AssetBinding::<T>::try_mutate(foreign_asset_id, |collection_id| -> DispatchResult {397				*collection_id = Some(bounded_collection_id);398				Ok(())399			})400		})?;401402		Ok(foreign_asset_id)403	}404405	fn do_update_foreign_asset(406		foreign_asset_id: ForeignAssetId,407		location: &MultiLocation,408		metadata: &AssetMetadata<BalanceOf<T>>,409	) -> DispatchResult {410		ForeignAssetLocations::<T>::try_mutate(411			foreign_asset_id,412			|maybe_multi_locations| -> DispatchResult {413				let old_multi_locations = maybe_multi_locations414					.as_mut()415					.ok_or(Error::<T>::AssetIdNotExists)?;416417				AssetMetadatas::<T>::try_mutate(418					AssetId::ForeignAssetId(foreign_asset_id),419					|maybe_asset_metadatas| -> DispatchResult {420						ensure!(421							maybe_asset_metadatas.is_some(),422							Error::<T>::AssetIdNotExists423						);424425						// modify location426						if location != old_multi_locations {427							LocationToCurrencyIds::<T>::remove(*old_multi_locations);428							LocationToCurrencyIds::<T>::try_mutate(429								location,430								|maybe_currency_ids| -> DispatchResult {431									ensure!(432										maybe_currency_ids.is_none(),433										Error::<T>::MultiLocationExisted434									);435									// *maybe_currency_ids = Some(CurrencyId::ForeignAsset(foreign_asset_id));436									*maybe_currency_ids = Some(foreign_asset_id);437									Ok(())438								},439							)?;440						}441						*maybe_asset_metadatas = Some(metadata.clone());442						*old_multi_locations = *location;443						Ok(())444					},445				)446			},447		)448	}449}450451pub use frame_support::{452	traits::{453		fungibles::Balanced, tokens::currency::Currency as CurrencyT, OnUnbalanced as OnUnbalancedT,454	},455	weights::{WeightToFee, WeightToFeePolynomial},456};457458pub struct FreeForAll<459	WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,460	AssetId: Get<MultiLocation>,461	AccountId,462	Currency: CurrencyT<AccountId>,463	OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,464>(465	Weight,466	Currency::Balance,467	PhantomData<(WeightToFee, AssetId, AccountId, Currency, OnUnbalanced)>,468);469470impl<471		WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,472		AssetId: Get<MultiLocation>,473		AccountId,474		Currency: CurrencyT<AccountId>,475		OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,476	> WeightTrader for FreeForAll<WeightToFee, AssetId, AccountId, Currency, OnUnbalanced>477{478	fn new() -> Self {479		Self(Weight::default(), Zero::zero(), PhantomData)480	}481482	fn buy_weight(483		&mut self,484		weight: Weight,485		payment: Assets,486		_xcm: &XcmContext,487	) -> Result<Assets, XcmError> {488		log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);489		Ok(payment)490	}491}492impl<WeightToFee, AssetId, AccountId, Currency, OnUnbalanced> Drop493	for FreeForAll<WeightToFee, AssetId, AccountId, Currency, OnUnbalanced>494where495	WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,496	AssetId: Get<MultiLocation>,497	Currency: CurrencyT<AccountId>,498	OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,499{500	fn drop(&mut self) {501		OnUnbalanced::on_unbalanced(Currency::issue(self.1));502	}503}