--- /dev/null +++ b/pallets/foreing-assets/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "pallet-foreing-assets" +version = "0.1.0" +license = "GPLv3" +edition = "2021" + +[dependencies] +log = { version = "0.4.14", default-features = false } +serde = { version = "1.0.136", optional = true } +scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24", default-features = false } +frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24", default-features = false } +up-data-structs = { default-features = false, path = '../../primitives/data-structs' } +pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24", default-features = false } +pallet-common = { default-features = false, path = '../common' } +pallet-fungible = { default-features = false, path = '../fungible' } +xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.24", default-features = false } +xcm-builder = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.24", default-features = false } +xcm-executor = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.24", default-features = false } +orml-tokens = { git = 'https://github.com/UniqueNetwork/open-runtime-module-library', branch = 'unique-polkadot-v0.9.24', version = "0.4.1-dev", default-features = false } + +[dev-dependencies] +serde_json = "1.0.68" +hex = { version = "0.4" } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" } +pallet-timestamp = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" } +pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" } + +[features] +default = ["std"] +std = [ + "serde", + "log/std", + "codec/std", + "scale-info/std", + "sp-runtime/std", + "sp-std/std", + "frame-support/std", + "frame-system/std", + "up-data-structs/std", + "pallet-common/std", + "pallet-balances/std", + "pallet-fungible/std", + "orml-tokens/std" +] +try-runtime = ["frame-support/try-runtime"] --- /dev/null +++ b/pallets/foreing-assets/src/impl_fungibles.rs @@ -0,0 +1,621 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +//! Implementations for fungibles trait. + +use super::*; +use frame_system::Config as SystemConfig; + +use frame_support::traits::tokens::{DepositConsequence, WithdrawConsequence}; +use pallet_common::CollectionHandle; +use pallet_fungible::FungibleHandle; +use pallet_common::CommonCollectionOperations; +use up_data_structs::budget::Unlimited; +use sp_runtime::traits::{CheckedAdd, CheckedSub}; + +impl fungibles::Inspect<::AccountId> for Pallet +where + T: orml_tokens::Config, +{ + type AssetId = AssetIds; + type Balance = BalanceOf; + + fn total_issuance(asset: Self::AssetId) -> Self::Balance { + log::trace!(target: "fassets::impl_foreing_assets", "impl_fungible total_issuance"); + + match asset { + AssetIds::NativeAssetId(NativeCurrency::Here) => { + let parent_amount = as fungible::Inspect< + T::AccountId, + >>::total_issuance(); + + let value: u128 = match parent_amount.try_into() { + Ok(val) => val, + Err(_) => return Zero::zero(), + }; + + let ti: Self::Balance = match value.try_into() { + Ok(val) => val, + Err(_) => return Zero::zero(), + }; + + ti + } + AssetIds::NativeAssetId(NativeCurrency::Parent) => { + let amount = + as fungibles::Inspect>::total_issuance( + AssetIds::NativeAssetId(NativeCurrency::Parent), + ); + + let value: u128 = match amount.try_into() { + Ok(val) => val, + Err(_) => return Zero::zero(), + }; + + let ti: Self::Balance = match value.try_into() { + Ok(val) => val, + Err(_) => return Zero::zero(), + }; + + ti + } + AssetIds::ForeignAssetId(fid) => { + let target_collection_id = match >::get(fid) { + Some(v) => v, + None => return Zero::zero(), + }; + let collection_handle = match >::try_get(target_collection_id) { + Ok(v) => v, + Err(_) => return Zero::zero(), + }; + let collection = FungibleHandle::cast(collection_handle); + Self::Balance::try_from(collection.total_supply()).unwrap_or(Zero::zero()) + } + } + } + + fn minimum_balance(asset: Self::AssetId) -> Self::Balance { + log::trace!(target: "fassets::impl_foreing_assets", "impl_fungible minimum_balance"); + match asset { + AssetIds::NativeAssetId(NativeCurrency::Here) => { + let parent_amount = as fungible::Inspect< + T::AccountId, + >>::minimum_balance(); + + let value: u128 = match parent_amount.try_into() { + Ok(val) => val, + Err(_) => return Zero::zero(), + }; + + let ti: Self::Balance = match value.try_into() { + Ok(val) => val, + Err(_) => return Zero::zero(), + }; + + ti + } + AssetIds::NativeAssetId(NativeCurrency::Parent) => { + let amount = + as fungibles::Inspect>::minimum_balance( + AssetIds::NativeAssetId(NativeCurrency::Parent), + ); + + let value: u128 = match amount.try_into() { + Ok(val) => val, + Err(_) => return Zero::zero(), + }; + + let ti: Self::Balance = match value.try_into() { + Ok(val) => val, + Err(_) => return Zero::zero(), + }; + + ti + } + AssetIds::ForeignAssetId(fid) => { + AssetMetadatas::::get(AssetIds::ForeignAssetId(fid)) + .map(|x| x.minimal_balance) + .unwrap_or_else(Zero::zero) + } + } + } + + fn balance(asset: Self::AssetId, who: &::AccountId) -> Self::Balance { + log::trace!(target: "fassets::impl_foreing_assets", "impl_fungible balance"); + match asset { + AssetIds::NativeAssetId(NativeCurrency::Here) => { + let parent_amount = + as fungible::Inspect>::balance(who); + + let value: u128 = match parent_amount.try_into() { + Ok(val) => val, + Err(_) => return Zero::zero(), + }; + + let ti: Self::Balance = match value.try_into() { + Ok(val) => val, + Err(_) => return Zero::zero(), + }; + + ti + } + AssetIds::NativeAssetId(NativeCurrency::Parent) => { + let amount = as fungibles::Inspect>::balance( + AssetIds::NativeAssetId(NativeCurrency::Parent), + who, + ); + + let value: u128 = match amount.try_into() { + Ok(val) => val, + Err(_) => return Zero::zero(), + }; + + let ti: Self::Balance = match value.try_into() { + Ok(val) => val, + Err(_) => return Zero::zero(), + }; + + ti + } + AssetIds::ForeignAssetId(fid) => { + let target_collection_id = match >::get(fid) { + Some(v) => v, + None => return Zero::zero(), + }; + let collection_handle = match >::try_get(target_collection_id) { + Ok(v) => v, + Err(_) => return Zero::zero(), + }; + let collection = FungibleHandle::cast(collection_handle); + Self::Balance::try_from( + collection.balance(T::CrossAccountId::from_sub(who.clone()), TokenId(0)), + ) + .unwrap_or(Zero::zero()) + } + } + } + + fn reducible_balance( + asset: Self::AssetId, + who: &::AccountId, + keep_alive: bool, + ) -> Self::Balance { + log::trace!(target: "fassets::impl_foreing_assets", "impl_fungible reducible_balance"); + + match asset { + AssetIds::NativeAssetId(NativeCurrency::Here) => { + let parent_amount = as fungible::Inspect< + T::AccountId, + >>::reducible_balance(who, keep_alive); + + let value: u128 = match parent_amount.try_into() { + Ok(val) => val, + Err(_) => return Zero::zero(), + }; + + let ti: Self::Balance = match value.try_into() { + Ok(val) => val, + Err(_) => return Zero::zero(), + }; + + ti + } + AssetIds::NativeAssetId(NativeCurrency::Parent) => { + let amount = + as fungibles::Inspect>::reducible_balance( + AssetIds::NativeAssetId(NativeCurrency::Parent), + who, + keep_alive, + ); + + let value: u128 = match amount.try_into() { + Ok(val) => val, + Err(_) => return Zero::zero(), + }; + + let ti: Self::Balance = match value.try_into() { + Ok(val) => val, + Err(_) => return Zero::zero(), + }; + + ti + } + _ => Self::balance(asset, who), + } + } + + fn can_deposit( + asset: Self::AssetId, + who: &::AccountId, + amount: Self::Balance, + mint: bool, + ) -> DepositConsequence { + log::trace!(target: "fassets::impl_foreing_assets", "impl_fungible can_deposit"); + + let value: u128 = match amount.try_into() { + Ok(val) => val, + Err(_) => return DepositConsequence::CannotCreate, + }; + + match asset { + AssetIds::NativeAssetId(NativeCurrency::Here) => { + let this_amount: ::Balance = match value.try_into() { + Ok(val) => val, + Err(_) => { + return DepositConsequence::CannotCreate; + } + }; + as fungible::Inspect>::can_deposit( + who, + this_amount, + mint, + ) + } + AssetIds::NativeAssetId(NativeCurrency::Parent) => { + let parent_amount: ::Balance = match value.try_into() { + Ok(val) => val, + Err(_) => { + return DepositConsequence::CannotCreate; + } + }; + as fungibles::Inspect>::can_deposit( + AssetIds::NativeAssetId(NativeCurrency::Parent), + who, + parent_amount, + mint, + ) + } + _ => { + if amount.is_zero() { + return DepositConsequence::Success; + } + + let extential_deposit_value = T::ExistentialDeposit::get(); + let ed_value: u128 = match extential_deposit_value.try_into() { + Ok(val) => val, + Err(_) => return DepositConsequence::CannotCreate, + }; + let extential_deposit: Self::Balance = match ed_value.try_into() { + Ok(val) => val, + Err(_) => return DepositConsequence::CannotCreate, + }; + + let new_total_balance = match Self::balance(asset, who).checked_add(&amount) { + Some(x) => x, + None => return DepositConsequence::Overflow, + }; + + if new_total_balance < extential_deposit { + return DepositConsequence::BelowMinimum; + } + + DepositConsequence::Success + } + } + } + + fn can_withdraw( + asset: Self::AssetId, + who: &::AccountId, + amount: Self::Balance, + ) -> WithdrawConsequence { + log::trace!(target: "fassets::impl_foreing_assets", "impl_fungible can_withdraw"); + let value: u128 = match amount.try_into() { + Ok(val) => val, + Err(_) => return WithdrawConsequence::UnknownAsset, + }; + + match asset { + AssetIds::NativeAssetId(NativeCurrency::Here) => { + let this_amount: ::Balance = match value.try_into() { + Ok(val) => val, + Err(_) => { + return WithdrawConsequence::UnknownAsset; + } + }; + match as fungible::Inspect>::can_withdraw( + who, + this_amount, + ) { + WithdrawConsequence::NoFunds => WithdrawConsequence::NoFunds, + WithdrawConsequence::WouldDie => WithdrawConsequence::WouldDie, + WithdrawConsequence::UnknownAsset => WithdrawConsequence::UnknownAsset, + WithdrawConsequence::Underflow => WithdrawConsequence::Underflow, + WithdrawConsequence::Overflow => WithdrawConsequence::Overflow, + WithdrawConsequence::Frozen => WithdrawConsequence::Frozen, + WithdrawConsequence::Success => WithdrawConsequence::Success, + _ => WithdrawConsequence::NoFunds, + } + } + AssetIds::NativeAssetId(NativeCurrency::Parent) => { + let parent_amount: ::Balance = match value.try_into() { + Ok(val) => val, + Err(_) => { + return WithdrawConsequence::UnknownAsset; + } + }; + match as fungibles::Inspect>::can_withdraw( + AssetIds::NativeAssetId(NativeCurrency::Parent), + who, + parent_amount, + ) { + WithdrawConsequence::NoFunds => WithdrawConsequence::NoFunds, + WithdrawConsequence::WouldDie => WithdrawConsequence::WouldDie, + WithdrawConsequence::UnknownAsset => WithdrawConsequence::UnknownAsset, + WithdrawConsequence::Underflow => WithdrawConsequence::Underflow, + WithdrawConsequence::Overflow => WithdrawConsequence::Overflow, + WithdrawConsequence::Frozen => WithdrawConsequence::Frozen, + WithdrawConsequence::Success => WithdrawConsequence::Success, + _ => WithdrawConsequence::NoFunds, + } + } + _ => match Self::balance(asset, who).checked_sub(&amount) { + Some(_) => WithdrawConsequence::Success, + None => WithdrawConsequence::NoFunds, + }, + } + } +} + +impl fungibles::Mutate<::AccountId> for Pallet +where + T: orml_tokens::Config, +{ + fn mint_into( + asset: Self::AssetId, + who: &::AccountId, + amount: Self::Balance, + ) -> DispatchResult { + //Self::do_mint(asset, who, amount, None) + log::trace!(target: "fassets::impl_foreing_assets", "impl_fungible mint_into {:?}", asset); + + let value: u128 = match amount.try_into() { + Ok(val) => val, + Err(_) => return Err(DispatchError::Other("Bad amount to value conversion")), + }; + + match asset { + AssetIds::NativeAssetId(NativeCurrency::Here) => { + let this_amount: ::Balance = match value.try_into() { + Ok(val) => val, + Err(_) => { + return Err(DispatchError::Other( + "Bad amount to this parachain value conversion", + )) + } + }; + + as fungible::Mutate>::mint_into( + who, + this_amount, + ) + } + AssetIds::NativeAssetId(NativeCurrency::Parent) => { + let parent_amount: ::Balance = match value.try_into() { + Ok(val) => val, + Err(_) => { + return Err(DispatchError::Other( + "Bad amount to relay chain value conversion", + )) + } + }; + + as fungibles::Mutate>::mint_into( + AssetIds::NativeAssetId(NativeCurrency::Parent), + who, + parent_amount, + ) + } + AssetIds::ForeignAssetId(fid) => { + let target_collection_id = match >::get(fid) { + Some(v) => v, + None => { + return Err(DispatchError::Other( + "Associated collection not found for asset", + )) + } + }; + let collection = + FungibleHandle::cast(>::try_get(target_collection_id)?); + let account = T::CrossAccountId::from_sub(who.clone()); + + let amount_data: pallet_fungible::CreateItemData = (account.clone(), value); + + pallet_fungible::Pallet::::create_item_foreign( + &collection, + &account, + amount_data, + &Unlimited, + )?; + + Ok(()) + } + } + } + + fn burn_from( + asset: Self::AssetId, + who: &::AccountId, + amount: Self::Balance, + ) -> Result { + // let f = DebitFlags { keep_alive: false, best_effort: false }; + log::trace!(target: "fassets::impl_foreing_assets", "impl_fungible burn_from"); + + let value: u128 = match amount.try_into() { + Ok(val) => val, + Err(_) => return Err(DispatchError::Other("Bad amount to value conversion")), + }; + + match asset { + AssetIds::NativeAssetId(NativeCurrency::Here) => { + let this_amount: ::Balance = match value.try_into() { + Ok(val) => val, + Err(_) => { + return Err(DispatchError::Other( + "Bad amount to this parachain value conversion", + )) + } + }; + + match as fungible::Mutate>::burn_from( + who, + this_amount, + ) { + Ok(_) => Ok(amount), + Err(e) => Err(e), + } + } + AssetIds::NativeAssetId(NativeCurrency::Parent) => { + let parent_amount: ::Balance = match value.try_into() { + Ok(val) => val, + Err(_) => { + return Err(DispatchError::Other( + "Bad amount to relay chain value conversion", + )) + } + }; + + match as fungibles::Mutate>::burn_from( + AssetIds::NativeAssetId(NativeCurrency::Parent), + who, + parent_amount, + ) { + Ok(_) => Ok(amount), + Err(e) => Err(e), + } + } + AssetIds::ForeignAssetId(fid) => { + let target_collection_id = match >::get(fid) { + Some(v) => v, + None => { + return Err(DispatchError::Other( + "Associated collection not found for asset", + )) + } + }; + let collection = + FungibleHandle::cast(>::try_get(target_collection_id)?); + pallet_fungible::Pallet::::burn_foreign( + &collection, + &T::CrossAccountId::from_sub(who.clone()), + value, + )?; + + Ok(amount) + } + } + } + + fn slash( + asset: Self::AssetId, + who: &::AccountId, + amount: Self::Balance, + ) -> Result { + // let f = DebitFlags { keep_alive: false, best_effort: true }; + log::trace!(target: "fassets::impl_foreing_assets", "impl_fungible slash"); + Self::burn_from(asset, who, amount)?; + Ok(amount) + } +} + +impl fungibles::Transfer for Pallet +where + T: orml_tokens::Config, +{ + fn transfer( + asset: Self::AssetId, + source: &::AccountId, + dest: &::AccountId, + amount: Self::Balance, + keep_alive: bool, + ) -> Result { + // let f = TransferFlags { keep_alive, best_effort: false, burn_dust: false }; + log::trace!(target: "fassets::impl_foreing_assets", "impl_fungible transfer"); + + let value: u128 = match amount.try_into() { + Ok(val) => val, + Err(_) => return Err(DispatchError::Other("Bad amount to value conversion")), + }; + + match asset { + AssetIds::NativeAssetId(NativeCurrency::Here) => { + let this_amount: ::Balance = match value.try_into() { + Ok(val) => val, + Err(_) => { + return Err(DispatchError::Other( + "Bad amount to this parachain value conversion", + )) + } + }; + + match as fungible::Transfer>::transfer( + source, + dest, + this_amount, + keep_alive, + ) { + Ok(_) => Ok(amount), + Err(_) => Err(DispatchError::Other( + "Bad amount to relay chain value conversion", + )), + } + } + AssetIds::NativeAssetId(NativeCurrency::Parent) => { + let parent_amount: ::Balance = match value.try_into() { + Ok(val) => val, + Err(_) => { + return Err(DispatchError::Other( + "Bad amount to relay chain value conversion", + )) + } + }; + + match as fungibles::Transfer>::transfer( + AssetIds::NativeAssetId(NativeCurrency::Parent), + source, + dest, + parent_amount, + keep_alive, + ) { + Ok(_) => Ok(amount), + Err(e) => Err(e), + } + } + AssetIds::ForeignAssetId(fid) => { + let target_collection_id = match >::get(fid) { + Some(v) => v, + None => { + return Err(DispatchError::Other( + "Associated collection not found for asset", + )) + } + }; + let collection = + FungibleHandle::cast(>::try_get(target_collection_id)?); + + pallet_fungible::Pallet::::transfer( + &collection, + &T::CrossAccountId::from_sub(source.clone()), + &T::CrossAccountId::from_sub(dest.clone()), + value, + &Unlimited, + )?; + + Ok(amount) + } + } + } +} --- /dev/null +++ b/pallets/foreing-assets/src/lib.rs @@ -0,0 +1,541 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +//! # Foreing assets +//! +//! - [`Config`] +//! - [`Call`] +//! - [`Pallet`] +//! +//! ## Overview +//! +//! The foreing assests pallet provides functions for: +//! +//! - Local and foreign assets management. The foreign assets can be updated without runtime upgrade. +//! - Bounds between asset and target collection for cross chain transfer and inner transfers. +//! +//! ## Overview +//! +//! Under construction + +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(clippy::unused_unit)] + +use frame_support::{ + dispatch::DispatchResult, + ensure, + pallet_prelude::*, + traits::{fungible, fungibles, Currency, EnsureOrigin}, + transactional, RuntimeDebug, +}; +use frame_system::pallet_prelude::*; +use up_data_structs::{CollectionMode}; +use pallet_fungible::{Pallet as PalletFungible}; +use scale_info::{TypeInfo}; +use sp_runtime::{ + traits::{One, Zero}, + ArithmeticError, +}; +use sp_std::{boxed::Box, vec::Vec}; +use up_data_structs::{CollectionId, TokenId, CreateCollectionData}; + +// NOTE:v1::MultiLocation is used in storages, we would need to do migration if upgrade the +// MultiLocation in the future. +use xcm::opaque::latest::{prelude::XcmError, MultiAsset}; +use xcm::{v1::MultiLocation, VersionedMultiLocation}; +use xcm_executor::{traits::WeightTrader, Assets}; + +use pallet_common::erc::CrossAccountId; + +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; + +// TODO: Move to primitives +// Id of native currency. +// 0 - QTZ\UNQ +// 1 - KSM\DOT +#[derive( + Clone, + Copy, + Eq, + PartialEq, + PartialOrd, + Ord, + MaxEncodedLen, + RuntimeDebug, + Encode, + Decode, + TypeInfo, +)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub enum NativeCurrency { + Here = 0, + Parent = 1, +} + +#[derive( + Clone, + Copy, + Eq, + PartialEq, + PartialOrd, + Ord, + MaxEncodedLen, + RuntimeDebug, + Encode, + Decode, + TypeInfo, +)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub enum AssetIds { + ForeignAssetId(ForeignAssetId), + NativeAssetId(NativeCurrency), +} + +pub trait TryAsForeing { + fn try_as_foreing(asset: T) -> Option; +} + +impl TryAsForeing for AssetIds { + fn try_as_foreing(asset: AssetIds) -> Option { + match asset { + AssetIds::ForeignAssetId(id) => Some(id), + _ => None, + } + } +} + +pub type ForeignAssetId = u32; +pub type CurrencyId = AssetIds; + +mod impl_fungibles; +mod weights; + +pub use module::*; +pub use weights::WeightInfo; + +/// Type alias for currency balance. +pub type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; + +/// A mapping between ForeignAssetId and AssetMetadata. +pub trait AssetIdMapping { + /// Returns the AssetMetadata associated with a given ForeignAssetId. + fn get_asset_metadata(foreign_asset_id: ForeignAssetId) -> Option; + /// Returns the MultiLocation associated with a given ForeignAssetId. + fn get_multi_location(foreign_asset_id: ForeignAssetId) -> Option; + /// Returns the CurrencyId associated with a given MultiLocation. + fn get_currency_id(multi_location: MultiLocation) -> Option; +} + +pub struct XcmForeignAssetIdMapping(sp_std::marker::PhantomData); + +impl AssetIdMapping>> + for XcmForeignAssetIdMapping +{ + fn get_asset_metadata(foreign_asset_id: ForeignAssetId) -> Option>> { + log::trace!(target: "fassets::asset_metadatas", "call"); + Pallet::::asset_metadatas(AssetIds::ForeignAssetId(foreign_asset_id)) + } + + fn get_multi_location(foreign_asset_id: ForeignAssetId) -> Option { + log::trace!(target: "fassets::get_multi_location", "call"); + Pallet::::foreign_asset_locations(foreign_asset_id) + } + + fn get_currency_id(multi_location: MultiLocation) -> Option { + log::trace!(target: "fassets::get_currency_id", "call"); + Some(AssetIds::ForeignAssetId( + Pallet::::location_to_currency_ids(multi_location).unwrap_or(0), + )) + } +} + +#[frame_support::pallet] +pub mod module { + use super::*; + + #[pallet::config] + pub trait Config: + frame_system::Config + + pallet_common::Config + + pallet_fungible::Config + + orml_tokens::Config + + pallet_balances::Config + { + /// The overarching event type. + type Event: From> + IsType<::Event>; + + /// Currency type for withdraw and balance storage. + type Currency: Currency; + + /// Required origin for registering asset. + type RegisterOrigin: EnsureOrigin; + + /// Weight information for the extrinsics in this module. + type WeightInfo: WeightInfo; + } + + #[derive(Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, TypeInfo)] + pub struct AssetMetadata { + pub name: Vec, + pub symbol: Vec, + pub decimals: u8, + pub minimal_balance: Balance, + } + + #[pallet::error] + pub enum Error { + /// The given location could not be used (e.g. because it cannot be expressed in the + /// desired version of XCM). + BadLocation, + /// MultiLocation existed + MultiLocationExisted, + /// AssetId not exists + AssetIdNotExists, + /// AssetId exists + AssetIdExisted, + } + + #[pallet::event] + #[pallet::generate_deposit(fn deposit_event)] + pub enum Event { + /// The foreign asset registered. + ForeignAssetRegistered { + asset_id: ForeignAssetId, + asset_address: MultiLocation, + metadata: AssetMetadata>, + }, + /// The foreign asset updated. + ForeignAssetUpdated { + asset_id: ForeignAssetId, + asset_address: MultiLocation, + metadata: AssetMetadata>, + }, + /// The asset registered. + AssetRegistered { + asset_id: AssetIds, + metadata: AssetMetadata>, + }, + /// The asset updated. + AssetUpdated { + asset_id: AssetIds, + metadata: AssetMetadata>, + }, + } + + /// Next available Foreign AssetId ID. + /// + /// NextForeignAssetId: ForeignAssetId + #[pallet::storage] + #[pallet::getter(fn next_foreign_asset_id)] + pub type NextForeignAssetId = StorageValue<_, ForeignAssetId, ValueQuery>; + /// The storages for MultiLocations. + /// + /// ForeignAssetLocations: map ForeignAssetId => Option + #[pallet::storage] + #[pallet::getter(fn foreign_asset_locations)] + pub type ForeignAssetLocations = + StorageMap<_, Twox64Concat, ForeignAssetId, MultiLocation, OptionQuery>; + + /// The storages for CurrencyIds. + /// + /// LocationToCurrencyIds: map MultiLocation => Option + #[pallet::storage] + #[pallet::getter(fn location_to_currency_ids)] + pub type LocationToCurrencyIds = + StorageMap<_, Twox64Concat, MultiLocation, ForeignAssetId, OptionQuery>; + + /// The storages for AssetMetadatas. + /// + /// AssetMetadatas: map AssetIds => Option + #[pallet::storage] + #[pallet::getter(fn asset_metadatas)] + pub type AssetMetadatas = + StorageMap<_, Twox64Concat, AssetIds, AssetMetadata>, OptionQuery>; + + /// The storages for assets to fungible collection binding + /// + #[pallet::storage] + #[pallet::getter(fn asset_binding)] + pub type AssetBinding = + StorageMap<_, Twox64Concat, ForeignAssetId, CollectionId, OptionQuery>; + + #[pallet::pallet] + #[pallet::without_storage_info] + pub struct Pallet(_); + + #[pallet::call] + impl Pallet { + #[pallet::weight(::WeightInfo::register_foreign_asset())] + #[transactional] + pub fn register_foreign_asset( + origin: OriginFor, + owner: T::AccountId, + location: Box, + metadata: Box>>, + ) -> DispatchResult { + T::RegisterOrigin::ensure_origin(origin.clone())?; + + let location: MultiLocation = (*location) + .try_into() + .map_err(|()| Error::::BadLocation)?; + + let md = metadata.clone(); + let name: Vec = md.name.into_iter().map(|x| x as u16).collect::>(); + let mut description: Vec = "Foreing assets collection for " + .encode_utf16() + .collect::>(); + description.append(&mut name.clone()); + + let data: CreateCollectionData = CreateCollectionData { + name: name.try_into().unwrap(), + description: description.try_into().unwrap(), + mode: CollectionMode::Fungible(18), + ..Default::default() + }; + + let bounded_collection_id = >::init_foreign_collection( + CrossAccountId::from_sub(owner), + data, + )?; + let foreign_asset_id = + Self::do_register_foreign_asset(&location, &metadata, bounded_collection_id)?; + + Self::deposit_event(Event::::ForeignAssetRegistered { + asset_id: foreign_asset_id, + asset_address: location, + metadata: *metadata, + }); + Ok(()) + } + + #[pallet::weight(::WeightInfo::update_foreign_asset())] + #[transactional] + pub fn update_foreign_asset( + origin: OriginFor, + foreign_asset_id: ForeignAssetId, + location: Box, + metadata: Box>>, + ) -> DispatchResult { + T::RegisterOrigin::ensure_origin(origin)?; + + let location: MultiLocation = (*location) + .try_into() + .map_err(|()| Error::::BadLocation)?; + Self::do_update_foreign_asset(foreign_asset_id, &location, &metadata)?; + + Self::deposit_event(Event::::ForeignAssetUpdated { + asset_id: foreign_asset_id, + asset_address: location, + metadata: *metadata, + }); + Ok(()) + } + } +} + +impl Pallet { + fn get_next_foreign_asset_id() -> Result { + NextForeignAssetId::::try_mutate(|current| -> Result { + let id = *current; + *current = current + .checked_add(One::one()) + .ok_or(ArithmeticError::Overflow)?; + Ok(id) + }) + } + + fn do_register_foreign_asset( + location: &MultiLocation, + metadata: &AssetMetadata>, + bounded_collection_id: CollectionId, + ) -> Result { + let foreign_asset_id = Self::get_next_foreign_asset_id()?; + LocationToCurrencyIds::::try_mutate(location, |maybe_currency_ids| -> DispatchResult { + ensure!( + maybe_currency_ids.is_none(), + Error::::MultiLocationExisted + ); + *maybe_currency_ids = Some(foreign_asset_id); + // *maybe_currency_ids = Some(CurrencyId::ForeignAsset(foreign_asset_id)); + + ForeignAssetLocations::::try_mutate( + foreign_asset_id, + |maybe_location| -> DispatchResult { + ensure!(maybe_location.is_none(), Error::::MultiLocationExisted); + *maybe_location = Some(location.clone()); + + AssetMetadatas::::try_mutate( + AssetIds::ForeignAssetId(foreign_asset_id), + |maybe_asset_metadatas| -> DispatchResult { + ensure!(maybe_asset_metadatas.is_none(), Error::::AssetIdExisted); + *maybe_asset_metadatas = Some(metadata.clone()); + Ok(()) + }, + ) + }, + )?; + + AssetBinding::::try_mutate(foreign_asset_id, |collection_id| -> DispatchResult { + *collection_id = Some(bounded_collection_id); + Ok(()) + }) + })?; + + Ok(foreign_asset_id) + } + + fn do_update_foreign_asset( + foreign_asset_id: ForeignAssetId, + location: &MultiLocation, + metadata: &AssetMetadata>, + ) -> DispatchResult { + ForeignAssetLocations::::try_mutate( + foreign_asset_id, + |maybe_multi_locations| -> DispatchResult { + let old_multi_locations = maybe_multi_locations + .as_mut() + .ok_or(Error::::AssetIdNotExists)?; + + AssetMetadatas::::try_mutate( + AssetIds::ForeignAssetId(foreign_asset_id), + |maybe_asset_metadatas| -> DispatchResult { + ensure!( + maybe_asset_metadatas.is_some(), + Error::::AssetIdNotExists + ); + + // modify location + if location != old_multi_locations { + LocationToCurrencyIds::::remove(old_multi_locations.clone()); + LocationToCurrencyIds::::try_mutate( + location, + |maybe_currency_ids| -> DispatchResult { + ensure!( + maybe_currency_ids.is_none(), + Error::::MultiLocationExisted + ); + // *maybe_currency_ids = Some(CurrencyId::ForeignAsset(foreign_asset_id)); + *maybe_currency_ids = Some(foreign_asset_id); + Ok(()) + }, + )?; + } + *maybe_asset_metadatas = Some(metadata.clone()); + *old_multi_locations = location.clone(); + Ok(()) + }, + ) + }, + ) + } +} + +use sp_runtime::SaturatedConversion; +use sp_runtime::traits::Saturating; +pub use frame_support::{ + traits::{ + fungibles::{Balanced, CreditOf}, + tokens::currency::Currency as CurrencyT, + OnUnbalanced as OnUnbalancedT, + }, + weights::{WeightToFeePolynomial, WeightToFee}, +}; + +use xcm::latest::{Fungibility::Fungible as XcmFungible}; + +pub struct UsingAnyCurrencyComponents< + WeightToFee: WeightToFeePolynomial, + AssetId: Get, + AccountId, + Currency: CurrencyT, + OnUnbalanced: OnUnbalancedT, +>( + Weight, + Currency::Balance, + PhantomData<(WeightToFee, AssetId, AccountId, Currency, OnUnbalanced)>, +); + +impl< + WeightToFee: WeightToFeePolynomial, + AssetId: Get, + AccountId, + Currency: CurrencyT, + OnUnbalanced: OnUnbalancedT, + > WeightTrader + for UsingAnyCurrencyComponents +{ + fn new() -> Self { + Self(0, Zero::zero(), PhantomData) + } + + fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result { + log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment); + + let amount = WeightToFee::weight_to_fee(&weight); + let u128_amount: u128 = amount.try_into().map_err(|_| XcmError::Overflow)?; + + let asset_id = payment + .fungible + .iter() + .next() + .map_or(Err(XcmError::TooExpensive), |v| Ok(v.0))?; + + // First fungible pays fee + let required = MultiAsset { + id: asset_id.clone(), + fun: XcmFungible(u128_amount), + }; + + log::trace!( + target: "fassets::weight", "buy_weight payment: {:?}, required: {:?}", + payment, required, + ); + + let unused = payment + .checked_sub(required) + .map_err(|_| XcmError::TooExpensive)?; + self.0 = self.0.saturating_add(weight); + self.1 = self.1.saturating_add(amount); + Ok(unused) + } + + fn refund_weight(&mut self, weight: Weight) -> Option { + let weight = weight.min(self.0); + let amount = WeightToFee::weight_to_fee(&weight); + self.0 -= weight; + self.1 = self.1.saturating_sub(amount); + let amount: u128 = amount.saturated_into(); + if amount > 0 { + Some((AssetId::get(), amount).into()) + } else { + None + } + } +} +impl< + WeightToFee: WeightToFeePolynomial, + AssetId: Get, + AccountId, + Currency: CurrencyT, + OnUnbalanced: OnUnbalancedT, + > Drop for UsingAnyCurrencyComponents +{ + fn drop(&mut self) { + OnUnbalanced::on_unbalanced(Currency::issue(self.1)); + } +} --- /dev/null +++ b/pallets/foreing-assets/src/weights.rs @@ -0,0 +1,43 @@ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for module_asset_registry. +pub trait WeightInfo { + fn register_foreign_asset() -> Weight; + fn update_foreign_asset() -> Weight; +} + +/// Weights for module_asset_registry using the Acala node and recommended hardware. +pub struct AcalaWeight(PhantomData); +impl WeightInfo for AcalaWeight { + fn register_foreign_asset() -> Weight { + (29_819_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) + } + fn update_foreign_asset() -> Weight { + (25_119_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn register_foreign_asset() -> Weight { + (29_819_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + } + fn update_foreign_asset() -> Weight { + (25_119_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } +} \ No newline at end of file --- a/runtime/common/construct_runtime/mod.rs +++ b/runtime/common/construct_runtime/mod.rs @@ -77,6 +77,9 @@ #[runtimes(opal)] RmrkEquip: pallet_proxy_rmrk_equip::{Pallet, Call, Storage, Event} = 72, + #[runtimes(opal)] + ForeingAssets: pallet_foreing_assets::{Pallet, Call, Storage, Event} = 80, + // Frontier EVM: pallet_evm::{Pallet, Config, Call, Storage, Event} = 100, Ethereum: pallet_ethereum::{Pallet, Config, Call, Storage, Event, Origin} = 101,