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

difftreelog

source

pallets/foreign-assets/src/lib.rs14.3 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};43use frame_system::pallet_prelude::*;44use pallet_common::erc::CrossAccountId;45use pallet_fungible::Pallet as PalletFungible;46use scale_info::TypeInfo;47use serde::{Deserialize, Serialize};48use sp_runtime::{49	traits::{One, Zero},50	ArithmeticError,51};52use sp_std::{boxed::Box, vec::Vec};53use staging_xcm::{latest::MultiLocation, VersionedMultiLocation};54// NOTE: MultiLocation is used in storages, we will need to do migration if upgrade the55// MultiLocation to the XCM v3.56use staging_xcm::{57	opaque::latest::{prelude::XcmError, Weight},58	v3::XcmContext,59};60use staging_xcm_executor::{traits::WeightTrader, Assets};61use up_data_structs::{CollectionId, CollectionMode, CreateCollectionData, TokenId};6263// TODO: Move to primitives64// Id of native currency.65// 0 - QTZ\UNQ66// 1 - KSM\DOT67#[derive(68	Clone,69	Copy,70	Eq,71	PartialEq,72	PartialOrd,73	Ord,74	MaxEncodedLen,75	RuntimeDebug,76	Encode,77	Decode,78	TypeInfo,79	Serialize,80	Deserialize,81)]82pub enum NativeCurrency {83	Here = 0,84	Parent = 1,85}8687#[derive(88	Clone,89	Copy,90	Eq,91	PartialEq,92	PartialOrd,93	Ord,94	MaxEncodedLen,95	RuntimeDebug,96	Encode,97	Decode,98	TypeInfo,99	Serialize,100	Deserialize,101)]102pub enum AssetId {103	ForeignAssetId(ForeignAssetId),104	NativeAssetId(NativeCurrency),105}106107pub trait TryAsForeign<T, F> {108	fn try_as_foreign(asset: T) -> Option<F>;109}110111impl TryAsForeign<AssetId, ForeignAssetId> for AssetId {112	fn try_as_foreign(asset: AssetId) -> Option<ForeignAssetId> {113		match asset {114			Self::ForeignAssetId(id) => Some(id),115			_ => None,116		}117	}118}119120pub type ForeignAssetId = u32;121pub type CurrencyId = AssetId;122123mod impl_fungibles;124pub mod weights;125126#[cfg(feature = "runtime-benchmarks")]127mod benchmarking;128129pub use module::*;130pub use weights::WeightInfo;131132/// Type alias for currency balance.133pub type BalanceOf<T> =134	<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;135136/// A mapping between ForeignAssetId and AssetMetadata.137pub trait AssetIdMapping<ForeignAssetId, MultiLocation, AssetMetadata> {138	/// Returns the AssetMetadata associated with a given ForeignAssetId.139	fn get_asset_metadata(foreign_asset_id: ForeignAssetId) -> Option<AssetMetadata>;140	/// Returns the MultiLocation associated with a given ForeignAssetId.141	fn get_multi_location(foreign_asset_id: ForeignAssetId) -> Option<MultiLocation>;142	/// Returns the CurrencyId associated with a given MultiLocation.143	fn get_currency_id(multi_location: MultiLocation) -> Option<CurrencyId>;144}145146pub struct XcmForeignAssetIdMapping<T>(sp_std::marker::PhantomData<T>);147148impl<T: Config> AssetIdMapping<ForeignAssetId, MultiLocation, AssetMetadata<BalanceOf<T>>>149	for XcmForeignAssetIdMapping<T>150{151	fn get_asset_metadata(foreign_asset_id: ForeignAssetId) -> Option<AssetMetadata<BalanceOf<T>>> {152		log::trace!(target: "fassets::asset_metadatas", "call");153		Pallet::<T>::asset_metadatas(AssetId::ForeignAssetId(foreign_asset_id))154	}155156	fn get_multi_location(foreign_asset_id: ForeignAssetId) -> Option<MultiLocation> {157		log::trace!(target: "fassets::get_multi_location", "call");158		Pallet::<T>::foreign_asset_locations(foreign_asset_id)159	}160161	fn get_currency_id(multi_location: MultiLocation) -> Option<CurrencyId> {162		log::trace!(target: "fassets::get_currency_id", "call");163		Pallet::<T>::location_to_currency_ids(multi_location).map(AssetId::ForeignAssetId)164	}165}166167#[frame_support::pallet]168pub mod module {169	use super::*;170171	#[pallet::config]172	pub trait Config:173		frame_system::Config174		+ pallet_common::Config175		+ pallet_fungible::Config176		+ orml_tokens::Config177		+ pallet_balances::Config178	{179		/// The overarching event type.180		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;181182		/// Currency type for withdraw and balance storage.183		type Currency: Currency<Self::AccountId>;184185		/// Required origin for registering asset.186		type RegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;187188		/// Weight information for the extrinsics in this module.189		type WeightInfo: WeightInfo;190	}191192	pub type AssetName = BoundedVec<u8, ConstU32<32>>;193	pub type AssetSymbol = BoundedVec<u8, ConstU32<7>>;194195	#[derive(Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)]196	pub struct AssetMetadata<Balance> {197		pub name: AssetName,198		pub symbol: AssetSymbol,199		pub decimals: u8,200		pub minimal_balance: Balance,201	}202203	#[pallet::error]204	pub enum Error<T> {205		/// The given location could not be used (e.g. because it cannot be expressed in the206		/// desired version of XCM).207		BadLocation,208		/// MultiLocation existed209		MultiLocationExisted,210		/// AssetId not exists211		AssetIdNotExists,212		/// AssetId exists213		AssetIdExisted,214	}215216	#[pallet::event]217	#[pallet::generate_deposit(fn deposit_event)]218	pub enum Event<T: Config> {219		/// The foreign asset registered.220		ForeignAssetRegistered {221			asset_id: ForeignAssetId,222			asset_address: MultiLocation,223			metadata: AssetMetadata<BalanceOf<T>>,224		},225		/// The foreign asset updated.226		ForeignAssetUpdated {227			asset_id: ForeignAssetId,228			asset_address: MultiLocation,229			metadata: AssetMetadata<BalanceOf<T>>,230		},231		/// The asset registered.232		AssetRegistered {233			asset_id: AssetId,234			metadata: AssetMetadata<BalanceOf<T>>,235		},236		/// The asset updated.237		AssetUpdated {238			asset_id: AssetId,239			metadata: AssetMetadata<BalanceOf<T>>,240		},241	}242243	/// Next available Foreign AssetId ID.244	///245	/// NextForeignAssetId: ForeignAssetId246	#[pallet::storage]247	#[pallet::getter(fn next_foreign_asset_id)]248	pub type NextForeignAssetId<T: Config> = StorageValue<_, ForeignAssetId, ValueQuery>;249	/// The storages for MultiLocations.250	///251	/// ForeignAssetLocations: map ForeignAssetId => Option<MultiLocation>252	#[pallet::storage]253	#[pallet::getter(fn foreign_asset_locations)]254	pub type ForeignAssetLocations<T: Config> =255		StorageMap<_, Twox64Concat, ForeignAssetId, staging_xcm::v3::MultiLocation, OptionQuery>;256257	/// The storages for CurrencyIds.258	///259	/// LocationToCurrencyIds: map MultiLocation => Option<ForeignAssetId>260	#[pallet::storage]261	#[pallet::getter(fn location_to_currency_ids)]262	pub type LocationToCurrencyIds<T: Config> =263		StorageMap<_, Twox64Concat, staging_xcm::v3::MultiLocation, ForeignAssetId, OptionQuery>;264265	/// The storages for AssetMetadatas.266	///267	/// AssetMetadatas: map AssetIds => Option<AssetMetadata>268	#[pallet::storage]269	#[pallet::getter(fn asset_metadatas)]270	pub type AssetMetadatas<T: Config> =271		StorageMap<_, Twox64Concat, AssetId, AssetMetadata<BalanceOf<T>>, OptionQuery>;272273	/// The storages for assets to fungible collection binding274	///275	#[pallet::storage]276	#[pallet::getter(fn asset_binding)]277	pub type AssetBinding<T: Config> =278		StorageMap<_, Twox64Concat, ForeignAssetId, CollectionId, OptionQuery>;279280	#[pallet::pallet]281	pub struct Pallet<T>(_);282283	#[pallet::call]284	impl<T: Config> Pallet<T> {285		#[pallet::call_index(0)]286		#[pallet::weight(<T as Config>::WeightInfo::register_foreign_asset())]287		pub fn register_foreign_asset(288			origin: OriginFor<T>,289			owner: T::AccountId,290			location: Box<VersionedMultiLocation>,291			metadata: Box<AssetMetadata<BalanceOf<T>>>,292		) -> DispatchResult {293			T::RegisterOrigin::ensure_origin(origin.clone())?;294295			let location: MultiLocation = (*location)296				.try_into()297				.map_err(|()| Error::<T>::BadLocation)?;298299			let md = metadata.clone();300			let name: Vec<u16> = md.name.into_iter().map(|x| x as u16).collect::<Vec<u16>>();301			let mut description: Vec<u16> = "Foreign assets collection for "302				.encode_utf16()303				.collect::<Vec<u16>>();304			description.append(&mut name.clone());305306			let data: CreateCollectionData<T::CrossAccountId> = CreateCollectionData {307				name: name.try_into().unwrap(),308				description: description.try_into().unwrap(),309				mode: CollectionMode::Fungible(md.decimals),310				..Default::default()311			};312			let owner = T::CrossAccountId::from_sub(owner);313			let bounded_collection_id =314				<PalletFungible<T>>::init_foreign_collection(owner.clone(), owner, data)?;315			let foreign_asset_id =316				Self::do_register_foreign_asset(&location, &metadata, bounded_collection_id)?;317318			Self::deposit_event(Event::<T>::ForeignAssetRegistered {319				asset_id: foreign_asset_id,320				asset_address: location,321				metadata: *metadata,322			});323			Ok(())324		}325326		#[pallet::call_index(1)]327		#[pallet::weight(<T as Config>::WeightInfo::update_foreign_asset())]328		pub fn update_foreign_asset(329			origin: OriginFor<T>,330			foreign_asset_id: ForeignAssetId,331			location: Box<VersionedMultiLocation>,332			metadata: Box<AssetMetadata<BalanceOf<T>>>,333		) -> DispatchResult {334			T::RegisterOrigin::ensure_origin(origin)?;335336			let location: MultiLocation = (*location)337				.try_into()338				.map_err(|()| Error::<T>::BadLocation)?;339			Self::do_update_foreign_asset(foreign_asset_id, &location, &metadata)?;340341			Self::deposit_event(Event::<T>::ForeignAssetUpdated {342				asset_id: foreign_asset_id,343				asset_address: location,344				metadata: *metadata,345			});346			Ok(())347		}348	}349}350351impl<T: Config> Pallet<T> {352	fn get_next_foreign_asset_id() -> Result<ForeignAssetId, DispatchError> {353		NextForeignAssetId::<T>::try_mutate(|current| -> Result<ForeignAssetId, DispatchError> {354			let id = *current;355			*current = current356				.checked_add(One::one())357				.ok_or(ArithmeticError::Overflow)?;358			Ok(id)359		})360	}361362	fn do_register_foreign_asset(363		location: &MultiLocation,364		metadata: &AssetMetadata<BalanceOf<T>>,365		bounded_collection_id: CollectionId,366	) -> Result<ForeignAssetId, DispatchError> {367		let foreign_asset_id = Self::get_next_foreign_asset_id()?;368		LocationToCurrencyIds::<T>::try_mutate(location, |maybe_currency_ids| -> DispatchResult {369			ensure!(370				maybe_currency_ids.is_none(),371				Error::<T>::MultiLocationExisted372			);373			*maybe_currency_ids = Some(foreign_asset_id);374			// *maybe_currency_ids = Some(CurrencyId::ForeignAsset(foreign_asset_id));375376			ForeignAssetLocations::<T>::try_mutate(377				foreign_asset_id,378				|maybe_location| -> DispatchResult {379					ensure!(maybe_location.is_none(), Error::<T>::MultiLocationExisted);380					*maybe_location = Some(*location);381382					AssetMetadatas::<T>::try_mutate(383						AssetId::ForeignAssetId(foreign_asset_id),384						|maybe_asset_metadatas| -> DispatchResult {385							ensure!(maybe_asset_metadatas.is_none(), Error::<T>::AssetIdExisted);386							*maybe_asset_metadatas = Some(metadata.clone());387							Ok(())388						},389					)390				},391			)?;392393			AssetBinding::<T>::try_mutate(foreign_asset_id, |collection_id| -> DispatchResult {394				*collection_id = Some(bounded_collection_id);395				Ok(())396			})397		})?;398399		Ok(foreign_asset_id)400	}401402	fn do_update_foreign_asset(403		foreign_asset_id: ForeignAssetId,404		location: &MultiLocation,405		metadata: &AssetMetadata<BalanceOf<T>>,406	) -> DispatchResult {407		ForeignAssetLocations::<T>::try_mutate(408			foreign_asset_id,409			|maybe_multi_locations| -> DispatchResult {410				let old_multi_locations = maybe_multi_locations411					.as_mut()412					.ok_or(Error::<T>::AssetIdNotExists)?;413414				AssetMetadatas::<T>::try_mutate(415					AssetId::ForeignAssetId(foreign_asset_id),416					|maybe_asset_metadatas| -> DispatchResult {417						ensure!(418							maybe_asset_metadatas.is_some(),419							Error::<T>::AssetIdNotExists420						);421422						// modify location423						if location != old_multi_locations {424							LocationToCurrencyIds::<T>::remove(*old_multi_locations);425							LocationToCurrencyIds::<T>::try_mutate(426								location,427								|maybe_currency_ids| -> DispatchResult {428									ensure!(429										maybe_currency_ids.is_none(),430										Error::<T>::MultiLocationExisted431									);432									// *maybe_currency_ids = Some(CurrencyId::ForeignAsset(foreign_asset_id));433									*maybe_currency_ids = Some(foreign_asset_id);434									Ok(())435								},436							)?;437						}438						*maybe_asset_metadatas = Some(metadata.clone());439						*old_multi_locations = *location;440						Ok(())441					},442				)443			},444		)445	}446}447448pub use frame_support::{449	traits::{450		fungibles::Balanced, tokens::currency::Currency as CurrencyT, OnUnbalanced as OnUnbalancedT,451	},452	weights::{WeightToFee, WeightToFeePolynomial},453};454455pub struct FreeForAll<456	WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,457	AssetId: Get<MultiLocation>,458	AccountId,459	Currency: CurrencyT<AccountId>,460	OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,461>(462	Weight,463	Currency::Balance,464	PhantomData<(WeightToFee, AssetId, AccountId, Currency, OnUnbalanced)>,465);466467impl<468		WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,469		AssetId: Get<MultiLocation>,470		AccountId,471		Currency: CurrencyT<AccountId>,472		OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,473	> WeightTrader for FreeForAll<WeightToFee, AssetId, AccountId, Currency, OnUnbalanced>474{475	fn new() -> Self {476		Self(Weight::default(), Zero::zero(), PhantomData)477	}478479	fn buy_weight(480		&mut self,481		weight: Weight,482		payment: Assets,483		_xcm: &XcmContext,484	) -> Result<Assets, XcmError> {485		log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);486		Ok(payment)487	}488}489impl<WeightToFee, AssetId, AccountId, Currency, OnUnbalanced> Drop490	for FreeForAll<WeightToFee, AssetId, AccountId, Currency, OnUnbalanced>491where492	WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,493	AssetId: Get<MultiLocation>,494	Currency: CurrencyT<AccountId>,495	OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,496{497	fn drop(&mut self) {498		OnUnbalanced::on_unbalanced(Currency::issue(self.1));499	}500}