git.delta.rocks / unique-network / refs/commits / 1c910af804ae

difftreelog

source

pallets/foreign-assets/src/lib.rs16.0 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)]83#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]84pub enum NativeCurrency {85	Here = 0,86	Parent = 1,87}8889#[derive(90	Clone,91	Copy,92	Eq,93	PartialEq,94	PartialOrd,95	Ord,96	MaxEncodedLen,97	RuntimeDebug,98	Encode,99	Decode,100	TypeInfo,101)]102#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]103pub enum AssetIds {104	ForeignAssetId(ForeignAssetId),105	NativeAssetId(NativeCurrency),106}107108pub trait TryAsForeign<T, F> {109	fn try_as_foreign(asset: T) -> Option<F>;110}111112impl TryAsForeign<AssetIds, ForeignAssetId> for AssetIds {113	fn try_as_foreign(asset: AssetIds) -> Option<ForeignAssetId> {114		match asset {115			AssetIds::ForeignAssetId(id) => Some(id),116			_ => None,117		}118	}119}120121pub type ForeignAssetId = u32;122pub type CurrencyId = AssetIds;123124mod impl_fungibles;125pub mod weights;126127#[cfg(feature = "runtime-benchmarks")]128mod benchmarking;129130pub use module::*;131pub use weights::WeightInfo;132133/// Type alias for currency balance.134pub type BalanceOf<T> =135	<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;136137/// A mapping between ForeignAssetId and AssetMetadata.138pub trait AssetIdMapping<ForeignAssetId, MultiLocation, AssetMetadata> {139	/// Returns the AssetMetadata associated with a given ForeignAssetId.140	fn get_asset_metadata(foreign_asset_id: ForeignAssetId) -> Option<AssetMetadata>;141	/// Returns the MultiLocation associated with a given ForeignAssetId.142	fn get_multi_location(foreign_asset_id: ForeignAssetId) -> Option<MultiLocation>;143	/// Returns the CurrencyId associated with a given MultiLocation.144	fn get_currency_id(multi_location: MultiLocation) -> Option<CurrencyId>;145}146147pub struct XcmForeignAssetIdMapping<T>(sp_std::marker::PhantomData<T>);148149impl<T: Config> AssetIdMapping<ForeignAssetId, MultiLocation, AssetMetadata<BalanceOf<T>>>150	for XcmForeignAssetIdMapping<T>151{152	fn get_asset_metadata(foreign_asset_id: ForeignAssetId) -> Option<AssetMetadata<BalanceOf<T>>> {153		log::trace!(target: "fassets::asset_metadatas", "call");154		Pallet::<T>::asset_metadatas(AssetIds::ForeignAssetId(foreign_asset_id))155	}156157	fn get_multi_location(foreign_asset_id: ForeignAssetId) -> Option<MultiLocation> {158		log::trace!(target: "fassets::get_multi_location", "call");159		Pallet::<T>::foreign_asset_locations(foreign_asset_id)160	}161162	fn get_currency_id(multi_location: MultiLocation) -> Option<CurrencyId> {163		log::trace!(target: "fassets::get_currency_id", "call");164		Pallet::<T>::location_to_currency_ids(multi_location).map(|id| AssetIds::ForeignAssetId(id))165	}166}167168#[frame_support::pallet]169pub mod module {170	use super::*;171172	#[pallet::config]173	pub trait Config:174		frame_system::Config175		+ pallet_common::Config176		+ pallet_fungible::Config177		+ orml_tokens::Config178		+ pallet_balances::Config179	{180		/// The overarching event type.181		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;182183		/// Currency type for withdraw and balance storage.184		type Currency: Currency<Self::AccountId>;185186		/// Required origin for registering asset.187		type RegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;188189		/// Weight information for the extrinsics in this module.190		type WeightInfo: WeightInfo;191	}192193	#[derive(Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, TypeInfo)]194	pub struct AssetMetadata<Balance> {195		pub name: Vec<u8>,196		pub symbol: Vec<u8>,197		pub decimals: u8,198		pub minimal_balance: Balance,199	}200201	#[pallet::error]202	pub enum Error<T> {203		/// The given location could not be used (e.g. because it cannot be expressed in the204		/// desired version of XCM).205		BadLocation,206		/// MultiLocation existed207		MultiLocationExisted,208		/// AssetId not exists209		AssetIdNotExists,210		/// AssetId exists211		AssetIdExisted,212	}213214	#[pallet::event]215	#[pallet::generate_deposit(fn deposit_event)]216	pub enum Event<T: Config> {217		/// The foreign asset registered.218		ForeignAssetRegistered {219			asset_id: ForeignAssetId,220			asset_address: MultiLocation,221			metadata: AssetMetadata<BalanceOf<T>>,222		},223		/// The foreign asset updated.224		ForeignAssetUpdated {225			asset_id: ForeignAssetId,226			asset_address: MultiLocation,227			metadata: AssetMetadata<BalanceOf<T>>,228		},229		/// The asset registered.230		AssetRegistered {231			asset_id: AssetIds,232			metadata: AssetMetadata<BalanceOf<T>>,233		},234		/// The asset updated.235		AssetUpdated {236			asset_id: AssetIds,237			metadata: AssetMetadata<BalanceOf<T>>,238		},239	}240241	/// Next available Foreign AssetId ID.242	///243	/// NextForeignAssetId: ForeignAssetId244	#[pallet::storage]245	#[pallet::getter(fn next_foreign_asset_id)]246	pub type NextForeignAssetId<T: Config> = StorageValue<_, ForeignAssetId, ValueQuery>;247	/// The storages for MultiLocations.248	///249	/// ForeignAssetLocations: map ForeignAssetId => Option<MultiLocation>250	#[pallet::storage]251	#[pallet::getter(fn foreign_asset_locations)]252	pub type ForeignAssetLocations<T: Config> =253		StorageMap<_, Twox64Concat, ForeignAssetId, xcm::v3::MultiLocation, OptionQuery>;254255	/// The storages for CurrencyIds.256	///257	/// LocationToCurrencyIds: map MultiLocation => Option<ForeignAssetId>258	#[pallet::storage]259	#[pallet::getter(fn location_to_currency_ids)]260	pub type LocationToCurrencyIds<T: Config> =261		StorageMap<_, Twox64Concat, xcm::v3::MultiLocation, ForeignAssetId, OptionQuery>;262263	/// The storages for AssetMetadatas.264	///265	/// AssetMetadatas: map AssetIds => Option<AssetMetadata>266	#[pallet::storage]267	#[pallet::getter(fn asset_metadatas)]268	pub type AssetMetadatas<T: Config> =269		StorageMap<_, Twox64Concat, AssetIds, AssetMetadata<BalanceOf<T>>, OptionQuery>;270271	/// The storages for assets to fungible collection binding272	///273	#[pallet::storage]274	#[pallet::getter(fn asset_binding)]275	pub type AssetBinding<T: Config> =276		StorageMap<_, Twox64Concat, ForeignAssetId, CollectionId, OptionQuery>;277278	#[pallet::pallet]279	#[pallet::without_storage_info]280	pub struct Pallet<T>(_);281282	pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);283	#[pallet::hooks]284	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {285		fn on_runtime_upgrade() -> Weight {286			let mut weight = Weight::default();287			// 0-1 -- xcmv2 => xcmv3288			if StorageVersion::get::<Pallet<T>>() <= 0 {289				pub trait V0ToV1 {290					type Pallet: 'static + PalletInfoAccess;291				}292				#[frame_support::storage_alias]293				type LocationToCurrencyIds<T: Config> =294					StorageMap<Pallet<T>, Twox64Concat, xcm::v2::MultiLocation, ForeignAssetId>;295				<ForeignAssetLocations<T>>::translate_values::<xcm::v2::MultiLocation, _>(|loc| {296					weight += T::DbWeight::get().reads_writes(1, 1);297					Some(298						xcm::v3::MultiLocation::try_from(loc)299							.expect("failed to migrate multilocation from XCMv2 to XCMv3"),300					)301				});302303				let old_values: Vec<(xcm::v2::MultiLocation, _)> =304					<LocationToCurrencyIds<T>>::drain().collect();305				weight += T::DbWeight::get()306					.reads_writes(old_values.len() as u64, old_values.len() as u64);307				for (loc, asset_id) in old_values {308					let loc = xcm::v3::MultiLocation::try_from(loc)309						.expect("failed to migrate multilocation from XCMv2 to XCMv3");310					<crate::LocationToCurrencyIds<T>>::insert(loc, asset_id);311				}312			}313			STORAGE_VERSION.put::<Pallet<T>>();314			weight += T::DbWeight::get().writes(1);315			weight316		}317	}318319	#[pallet::genesis_config]320	pub struct GenesisConfig;321322	#[cfg(feature = "std")]323	impl Default for GenesisConfig {324		fn default() -> Self {325			Self326		}327	}328329	#[pallet::genesis_build]330	impl<T: Config> GenesisBuild<T> for GenesisConfig {331		fn build(&self) {332			STORAGE_VERSION.put::<Pallet<T>>();333		}334	}335336	#[pallet::call]337	impl<T: Config> Pallet<T> {338		#[pallet::call_index(0)]339		#[pallet::weight(<T as Config>::WeightInfo::register_foreign_asset())]340		pub fn register_foreign_asset(341			origin: OriginFor<T>,342			owner: T::AccountId,343			location: Box<VersionedMultiLocation>,344			metadata: Box<AssetMetadata<BalanceOf<T>>>,345		) -> DispatchResult {346			T::RegisterOrigin::ensure_origin(origin.clone())?;347348			let location: MultiLocation = (*location)349				.try_into()350				.map_err(|()| Error::<T>::BadLocation)?;351352			let md = metadata.clone();353			let name: Vec<u16> = md.name.into_iter().map(|x| x as u16).collect::<Vec<u16>>();354			let mut description: Vec<u16> = "Foreign assets collection for "355				.encode_utf16()356				.collect::<Vec<u16>>();357			description.append(&mut name.clone());358359			let data: CreateCollectionData<T::AccountId> = CreateCollectionData {360				name: name.try_into().unwrap(),361				description: description.try_into().unwrap(),362				mode: CollectionMode::Fungible(md.decimals),363				..Default::default()364			};365			let owner = T::CrossAccountId::from_sub(owner);366			let bounded_collection_id =367				<PalletFungible<T>>::init_foreign_collection(owner.clone(), owner, data)?;368			let foreign_asset_id =369				Self::do_register_foreign_asset(&location, &metadata, bounded_collection_id)?;370371			Self::deposit_event(Event::<T>::ForeignAssetRegistered {372				asset_id: foreign_asset_id,373				asset_address: location,374				metadata: *metadata,375			});376			Ok(())377		}378379		#[pallet::call_index(1)]380		#[pallet::weight(<T as Config>::WeightInfo::update_foreign_asset())]381		pub fn update_foreign_asset(382			origin: OriginFor<T>,383			foreign_asset_id: ForeignAssetId,384			location: Box<VersionedMultiLocation>,385			metadata: Box<AssetMetadata<BalanceOf<T>>>,386		) -> DispatchResult {387			T::RegisterOrigin::ensure_origin(origin)?;388389			let location: MultiLocation = (*location)390				.try_into()391				.map_err(|()| Error::<T>::BadLocation)?;392			Self::do_update_foreign_asset(foreign_asset_id, &location, &metadata)?;393394			Self::deposit_event(Event::<T>::ForeignAssetUpdated {395				asset_id: foreign_asset_id,396				asset_address: location,397				metadata: *metadata,398			});399			Ok(())400		}401	}402}403404impl<T: Config> Pallet<T> {405	fn get_next_foreign_asset_id() -> Result<ForeignAssetId, DispatchError> {406		NextForeignAssetId::<T>::try_mutate(|current| -> Result<ForeignAssetId, DispatchError> {407			let id = *current;408			*current = current409				.checked_add(One::one())410				.ok_or(ArithmeticError::Overflow)?;411			Ok(id)412		})413	}414415	fn do_register_foreign_asset(416		location: &MultiLocation,417		metadata: &AssetMetadata<BalanceOf<T>>,418		bounded_collection_id: CollectionId,419	) -> Result<ForeignAssetId, DispatchError> {420		let foreign_asset_id = Self::get_next_foreign_asset_id()?;421		LocationToCurrencyIds::<T>::try_mutate(location, |maybe_currency_ids| -> DispatchResult {422			ensure!(423				maybe_currency_ids.is_none(),424				Error::<T>::MultiLocationExisted425			);426			*maybe_currency_ids = Some(foreign_asset_id);427			// *maybe_currency_ids = Some(CurrencyId::ForeignAsset(foreign_asset_id));428429			ForeignAssetLocations::<T>::try_mutate(430				foreign_asset_id,431				|maybe_location| -> DispatchResult {432					ensure!(maybe_location.is_none(), Error::<T>::MultiLocationExisted);433					*maybe_location = Some(location.clone());434435					AssetMetadatas::<T>::try_mutate(436						AssetIds::ForeignAssetId(foreign_asset_id),437						|maybe_asset_metadatas| -> DispatchResult {438							ensure!(maybe_asset_metadatas.is_none(), Error::<T>::AssetIdExisted);439							*maybe_asset_metadatas = Some(metadata.clone());440							Ok(())441						},442					)443				},444			)?;445446			AssetBinding::<T>::try_mutate(foreign_asset_id, |collection_id| -> DispatchResult {447				*collection_id = Some(bounded_collection_id);448				Ok(())449			})450		})?;451452		Ok(foreign_asset_id)453	}454455	fn do_update_foreign_asset(456		foreign_asset_id: ForeignAssetId,457		location: &MultiLocation,458		metadata: &AssetMetadata<BalanceOf<T>>,459	) -> DispatchResult {460		ForeignAssetLocations::<T>::try_mutate(461			foreign_asset_id,462			|maybe_multi_locations| -> DispatchResult {463				let old_multi_locations = maybe_multi_locations464					.as_mut()465					.ok_or(Error::<T>::AssetIdNotExists)?;466467				AssetMetadatas::<T>::try_mutate(468					AssetIds::ForeignAssetId(foreign_asset_id),469					|maybe_asset_metadatas| -> DispatchResult {470						ensure!(471							maybe_asset_metadatas.is_some(),472							Error::<T>::AssetIdNotExists473						);474475						// modify location476						if location != old_multi_locations {477							LocationToCurrencyIds::<T>::remove(old_multi_locations.clone());478							LocationToCurrencyIds::<T>::try_mutate(479								location,480								|maybe_currency_ids| -> DispatchResult {481									ensure!(482										maybe_currency_ids.is_none(),483										Error::<T>::MultiLocationExisted484									);485									// *maybe_currency_ids = Some(CurrencyId::ForeignAsset(foreign_asset_id));486									*maybe_currency_ids = Some(foreign_asset_id);487									Ok(())488								},489							)?;490						}491						*maybe_asset_metadatas = Some(metadata.clone());492						*old_multi_locations = location.clone();493						Ok(())494					},495				)496			},497		)498	}499}500501pub use frame_support::{502	traits::{503		fungibles::{Balanced, CreditOf},504		tokens::currency::Currency as CurrencyT,505		OnUnbalanced as OnUnbalancedT,506	},507	weights::{WeightToFeePolynomial, WeightToFee},508};509510pub struct FreeForAll<511	WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,512	AssetId: Get<MultiLocation>,513	AccountId,514	Currency: CurrencyT<AccountId>,515	OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,516>(517	Weight,518	Currency::Balance,519	PhantomData<(WeightToFee, AssetId, AccountId, Currency, OnUnbalanced)>,520);521522impl<523		WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,524		AssetId: Get<MultiLocation>,525		AccountId,526		Currency: CurrencyT<AccountId>,527		OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,528	> WeightTrader for FreeForAll<WeightToFee, AssetId, AccountId, Currency, OnUnbalanced>529{530	fn new() -> Self {531		Self(Weight::default(), Zero::zero(), PhantomData)532	}533534	fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result<Assets, XcmError> {535		log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);536		Ok(payment)537	}538}539impl<WeightToFee, AssetId, AccountId, Currency, OnUnbalanced> Drop540	for FreeForAll<WeightToFee, AssetId, AccountId, Currency, OnUnbalanced>541where542	WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,543	AssetId: Get<MultiLocation>,544	Currency: CurrencyT<AccountId>,545	OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,546{547	fn drop(&mut self) {548		OnUnbalanced::on_unbalanced(Currency::issue(self.1));549	}550}