xtokens on generic (#343)

* generic xtokens support

---------

Co-authored-by: Nikita Khateev <nikita.khateev@openzeppelin.com>
This commit is contained in:
Özgün Özerk
2024-10-31 10:38:32 +03:00
committed by GitHub
parent c43aa9591b
commit a7d968b934
11 changed files with 579 additions and 26 deletions
@@ -181,12 +181,12 @@ pub trait AccountIdAssetIdConversion<Account, AssetId> {
fn asset_id_to_account(prefix: &[u8], asset_id: AssetId) -> Account;
}
// needs to be 28 bytes due to `data` is 4 bytes
const FOREIGN_ASSET_PRECOMPILE_ADDRESS_PREFIX: &[u8] = &[255u8; 28];
// Instruct how to go from an H256 to an AssetID
impl AccountIdAssetIdConversion<AccountId, AssetId> for Runtime {
/// The way to convert an account to assetId is by ensuring that the prefix is 0XFFFFFFFF
/// and by taking the lowest 128 bits as the assetId
fn account_to_asset_id(account: AccountId) -> Option<(Vec<u8>, AssetId)> {
let bytes: [u8; 32] = account.into();
let h256_account: H256 = bytes.into();
@@ -194,6 +194,7 @@ impl AccountIdAssetIdConversion<AccountId, AssetId> for Runtime {
let (prefix_part, id_part) = h256_account.as_fixed_bytes().split_at(28);
if prefix_part == FOREIGN_ASSET_PRECOMPILE_ADDRESS_PREFIX {
data.copy_from_slice(id_part);
// we use `data` to create a u32 -> data needs to be 4 bytes
let asset_id: AssetId = u32::from_be_bytes(data);
Some((prefix_part.to_vec(), asset_id))
} else {
@@ -1,4 +1,5 @@
pub mod asset_config;
pub use asset_config::AssetType;
pub mod governance;
pub mod xcm_config;
@@ -1,43 +1,59 @@
use core::marker::PhantomData;
use frame_support::{
pallet_prelude::Get,
parameter_types,
traits::{ConstU32, Contains, Everything, Nothing, PalletInfoAccess},
traits::{ConstU32, Contains, ContainsPair, Everything, Nothing, PalletInfoAccess},
weights::Weight,
};
use frame_system::EnsureRoot;
use orml_traits::{location::Reserve, parameter_type_with_key};
use orml_xcm_support::MultiNativeAsset;
use pallet_xcm::XcmPassthrough;
use parity_scale_codec::{Decode, Encode};
use polkadot_parachain_primitives::primitives::{self, Sibling};
use polkadot_runtime_common::impls::ToAuthor;
use scale_info::TypeInfo;
use sp_runtime::traits::Convert;
use xcm::latest::prelude::*;
use xcm_builder::{
AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowTopLevelPaidExecutionFrom,
AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowTopLevelPaidExecutionFrom, Case,
DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, FixedWeightBounds,
FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, IsChildSystemParachain,
IsConcrete, NativeAsset, NoChecking, ParentIsPreset, RelayChainAsNative,
SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative,
SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId,
UsingComponents, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents,
XcmFeeToAccount,
IsConcrete, NoChecking, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative,
SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32,
SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, WithComputedOrigin,
WithUniqueTopic, XcmFeeManagerFromComponents, XcmFeeToAccount,
};
use xcm_executor::XcmExecutor;
use xcm_primitives::{AbsoluteAndRelativeReserve, AsAssetType};
use super::TreasuryAccount;
use crate::{
configs::{
weights, Balances, ParachainSystem, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin,
WeightToFee, XcmpQueue,
weights, AssetType, Balances, ParachainSystem,
Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, XcmpQueue,
},
types::{AccountId, Balance},
AllPalletsWithSystem, Assets, ParachainInfo, PolkadotXcm,
types::{AccountId, AssetId, Balance},
AllPalletsWithSystem, AssetManager, Assets, ParachainInfo, PolkadotXcm,
};
parameter_types! {
pub const RelayLocation: Location = Location::parent();
pub const RelayNetwork: Option<NetworkId> = None;
pub PlaceholderAccount: AccountId = PolkadotXcm::check_account();
pub AssetsPalletLocation: Location =
PalletInstance(<Assets as PalletInfoAccess>::index() as u8).into();
pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into();
pub UniversalLocation: InteriorLocation = Parachain(ParachainInfo::parachain_id().into()).into();
// Self Reserve location, defines the multilocation identifiying the self-reserve currency
// This is used to match it also against our Balances pallet when we receive such
// a Location: (Self Balances pallet index)
// We use the RELATIVE multilocation
pub SelfReserve: Location = Location {
parents:0,
interior: [
PalletInstance(<Balances as PalletInfoAccess>::index() as u8)
].into()
};
}
/// `AssetId/Balancer` converter for `TrustBackedAssets`
@@ -145,6 +161,48 @@ pub type Barrier = TrailingSetTopicAsId<
>,
>;
/// Matches foreign assets from a given origin.
/// Foreign assets are assets bridged from other consensus systems. i.e parents > 1.
pub struct IsBridgedConcreteAssetFrom<Origin>(PhantomData<Origin>);
impl<Origin> ContainsPair<Asset, Location> for IsBridgedConcreteAssetFrom<Origin>
where
Origin: Get<Location>,
{
fn contains(asset: &Asset, origin: &Location) -> bool {
let loc = Origin::get();
&loc == origin
&& matches!(
asset,
Asset { id: AssetId(Location { parents: 2, .. }), fun: Fungibility::Fungible(_) },
)
}
}
parameter_types! {
/// Location of Asset Hub
pub AssetHubLocation: Location = Location::new(1, [Parachain(1000)]);
pub const RelayLocation: Location = Location::parent();
pub RelayLocationFilter: AssetFilter = Wild(AllOf {
fun: WildFungible,
id: xcm::prelude::AssetId(RelayLocation::get()),
});
pub RelayChainNativeAssetFromAssetHub: (AssetFilter, Location) = (
RelayLocationFilter::get(),
AssetHubLocation::get()
);
}
type Reserves = (
// Assets bridged from different consensus systems held in reserve on Asset Hub.
IsBridgedConcreteAssetFrom<AssetHubLocation>,
// Relaychain (DOT) from Asset Hub
Case<RelayChainNativeAssetFromAssetHub>,
// Assets which the reserve is the same as the origin.
MultiNativeAsset<AbsoluteAndRelativeReserve<SelfLocationAbsolute>>,
);
pub type XcmWeigher = FixedWeightBounds<UnitWeightCost, RuntimeCall, MaxInstructions>;
pub struct XcmConfig;
impl xcm_executor::Config for XcmConfig {
type Aliasers = Nothing;
@@ -165,7 +223,7 @@ impl xcm_executor::Config for XcmConfig {
type HrmpChannelClosingHandler = ();
type HrmpNewChannelOpenRequestHandler = ();
/// Please, keep these two configs (`IsReserve` and `IsTeleporter`) mutually exclusive
type IsReserve = NativeAsset;
type IsReserve = Reserves;
type IsTeleporter = ();
type MaxAssetsIntoHolding = MaxAssetsIntoHolding;
type MessageExporter = ();
@@ -175,13 +233,12 @@ impl xcm_executor::Config for XcmConfig {
type RuntimeCall = RuntimeCall;
type SafeCallFilter = Everything;
type SubscriptionService = PolkadotXcm;
type Trader =
UsingComponents<WeightToFee, RelayLocation, AccountId, Balances, ToAuthor<Runtime>>;
type Trader = pallet_xcm_weight_trader::Trader<Runtime>;
type TransactionalProcessor = FrameTransactionalProcessor;
type UniversalAliases = Nothing;
// Teleporting is disabled.
type UniversalLocation = UniversalLocation;
type Weigher = FixedWeightBounds<UnitWeightCost, RuntimeCall, MaxInstructions>;
type Weigher = XcmWeigher;
type XcmRecorder = PolkadotXcm;
type XcmSender = XcmRouter;
}
@@ -220,7 +277,7 @@ impl pallet_xcm::Config for Runtime {
type SovereignAccountOf = LocationToAccountId;
type TrustedLockers = ();
type UniversalLocation = UniversalLocation;
type Weigher = FixedWeightBounds<UnitWeightCost, RuntimeCall, MaxInstructions>;
type Weigher = XcmWeigher;
/// Rerun benchmarks if you are making changes to runtime configuration.
type WeightInfo = weights::pallet_xcm::WeightInfo<Runtime>;
#[cfg(feature = "runtime-benchmarks")]
@@ -240,3 +297,195 @@ impl cumulus_pallet_xcm::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type XcmExecutor = XcmExecutor<XcmConfig>;
}
parameter_types! {
pub const BaseXcmWeight: Weight = Weight::from_parts(200_000_000u64, 0);
pub const MaxAssetsForTransfer: usize = 2;
// This is how we are going to detect whether the asset is a Reserve asset
// This however is the chain part only
pub SelfLocation: Location = Location::here();
// We need this to be able to catch when someone is trying to execute a non-
// cross-chain transfer in xtokens through the absolute path way
pub SelfLocationAbsolute: Location = Location {
parents:1,
interior: [
Parachain(ParachainInfo::parachain_id().into())
].into()
};
}
parameter_type_with_key! {
pub ParachainMinFee: |location: Location| -> Option<u128> {
match (location.parents, location.first_interior()) {
// Polkadot AssetHub fee
(1, Some(Parachain(1000u32))) => Some(50_000_000u128),
_ => None,
}
};
}
// Our currencyId. We distinguish for now between SelfReserve, and Others, defined by their Id.
#[derive(Clone, Eq, Debug, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
pub enum CurrencyId {
// Our native token
SelfReserve,
// Assets representing other chains native tokens
ForeignAsset(AssetId),
}
parameter_types! {
pub PolkadotXcmLocation: Location = Location {
parents:0,
interior: [
PalletInstance(<PolkadotXcm as PalletInfoAccess>::index() as u8)
].into()
};
}
// How to convert from CurrencyId to Location
pub struct CurrencyIdToLocation<AssetXConverter>(sp_std::marker::PhantomData<AssetXConverter>);
impl<AssetXConverter> sp_runtime::traits::Convert<CurrencyId, Option<Location>>
for CurrencyIdToLocation<AssetXConverter>
where
AssetXConverter: sp_runtime::traits::MaybeEquivalence<Location, AssetId>,
{
fn convert(currency: CurrencyId) -> Option<Location> {
match currency {
CurrencyId::SelfReserve => {
let multi: Location = SelfReserve::get();
Some(multi)
}
CurrencyId::ForeignAsset(asset) => AssetXConverter::convert_back(&asset),
}
}
}
pub struct AccountIdToLocation;
impl Convert<AccountId, Location> for AccountIdToLocation {
fn convert(account: AccountId) -> Location {
AccountId32 { network: None, id: account.into() }.into()
}
}
/// The `DOTReserveProvider` overrides the default reserve location for DOT (Polkadot's native token).
///
/// DOT can exist in multiple locations, and this provider ensures that the reserve is correctly set
/// to the AssetHub parachain.
///
/// - **Default Location:** `{ parents: 1, location: Here }`
/// - **Reserve Location on AssetHub:** `{ parents: 1, location: X1(Parachain(AssetHubParaId)) }`
///
/// This provider ensures that if the asset's ID points to the default "Here" location,
/// it will instead be mapped to the AssetHub parachain.
pub struct DOTReserveProvider;
impl Reserve for DOTReserveProvider {
fn reserve(asset: &Asset) -> Option<Location> {
let AssetId(location) = &asset.id;
let dot_here = Location::new(1, Here);
let dot_asset_hub = AssetHubLocation::get();
if location == &dot_here {
Some(dot_asset_hub) // Reserve is on AssetHub.
} else {
None
}
}
}
/// The `BridgedAssetReserveProvider` handles assets that are bridged from external consensus systems
/// (e.g., Ethereum) and may have multiple valid reserve locations.
///
/// Specifically, these bridged assets can have two known reserves:
/// 1. **Ethereum-based Reserve:**
/// `{ parents: 1, location: X1(GlobalConsensus(Ethereum{ chain_id: 1 })) }`
/// 2. **AssetHub Parachain Reserve:**
/// `{ parents: 1, location: X1(Parachain(AssetHubParaId)) }`
///
/// This provider maps the reserve for bridged assets to AssetHub when the asset originates
/// from a global consensus system, such as Ethereum.
pub struct BridgedAssetReserveProvider;
impl Reserve for BridgedAssetReserveProvider {
fn reserve(asset: &Asset) -> Option<Location> {
let AssetId(location) = &asset.id;
let asset_hub_reserve = AssetHubLocation::get();
// any asset that has parents > 1 and interior that starts with GlobalConsensus(_) pattern
// can be considered a bridged asset.
//
// `split_global` will return an `Err` if the first item is not a `GlobalConsensus`
if location.parents > 1 && location.interior.clone().split_global().is_ok() {
Some(asset_hub_reserve)
} else {
None
}
}
}
pub struct ReserveProviders;
impl Reserve for ReserveProviders {
fn reserve(asset: &Asset) -> Option<Location> {
// Try each provider's reserve method in sequence.
DOTReserveProvider::reserve(asset)
.or_else(|| BridgedAssetReserveProvider::reserve(asset))
.or_else(|| AbsoluteAndRelativeReserve::<SelfLocationAbsolute>::reserve(asset))
}
}
impl orml_xtokens::Config for Runtime {
type AccountIdToLocation = AccountIdToLocation;
type Balance = Balance;
type BaseXcmWeight = BaseXcmWeight;
type CurrencyId = CurrencyId;
type CurrencyIdConvert = CurrencyIdToLocation<AsAssetType<AssetId, AssetType, AssetManager>>;
type LocationsFilter = Everything;
type MaxAssetsForTransfer = MaxAssetsForTransfer;
type MinXcmFee = ParachainMinFee;
type RateLimiter = ();
type RateLimiterId = ();
type ReserveProvider = ReserveProviders;
type RuntimeEvent = RuntimeEvent;
type SelfLocation = SelfLocation;
type UniversalLocation = UniversalLocation;
type Weigher = XcmWeigher;
type XcmExecutor = XcmExecutor<XcmConfig>;
}
pub struct AssetFeesFilter;
impl frame_support::traits::Contains<Location> for AssetFeesFilter {
fn contains(location: &Location) -> bool {
location.parent_count() > 0
&& location.first_interior() != PolkadotXcmLocation::get().first_interior()
}
}
// implement your own business logic for who can add/edit/remove/resume supported assets
pub type AddSupportedAssetOrigin = EnsureRoot<AccountId>;
pub type EditSupportedAssetOrigin = EnsureRoot<AccountId>;
pub type RemoveSupportedAssetOrigin = EnsureRoot<AccountId>;
pub type ResumeSupportedAssetOrigin = EnsureRoot<AccountId>;
impl pallet_xcm_weight_trader::Config for Runtime {
type AccountIdToLocation = AccountIdToLocation;
type AddSupportedAssetOrigin = AddSupportedAssetOrigin;
type AssetLocationFilter = AssetFeesFilter;
type AssetTransactor = AssetTransactors;
type Balance = Balance;
type EditSupportedAssetOrigin = EditSupportedAssetOrigin;
type NativeLocation = SelfReserve;
#[cfg(feature = "runtime-benchmarks")]
type NotFilteredLocation = RelayLocation;
type PauseSupportedAssetOrigin = EditSupportedAssetOrigin;
type RemoveSupportedAssetOrigin = RemoveSupportedAssetOrigin;
type ResumeSupportedAssetOrigin = ResumeSupportedAssetOrigin;
type RuntimeEvent = RuntimeEvent;
// TODO: update this when we update benchmarks
type WeightInfo = weights::pallet_xcm_weight_trader::WeightInfo<Runtime>;
type WeightToFee = <Runtime as pallet_transaction_payment::Config>::WeightToFee;
type XcmFeesAccount = TreasuryAccount;
}
+4
View File
@@ -185,6 +185,10 @@ mod runtime {
pub type CumulusXcm = cumulus_pallet_xcm;
#[runtime::pallet_index(33)]
pub type MessageQueue = pallet_message_queue;
#[runtime::pallet_index(34)]
pub type XTokens = orml_xtokens;
#[runtime::pallet_index(35)]
pub type XcmWeightTrader = pallet_xcm_weight_trader;
}
cumulus_pallet_parachain_system::register_validate_block! {
@@ -39,6 +39,7 @@ pub mod pallet_treasury;
pub mod pallet_utility;
pub mod pallet_whitelist;
pub mod pallet_xcm;
pub mod pallet_xcm_weight_trader;
pub mod paritydb_weights;
pub mod rocksdb_weights;
@@ -0,0 +1,104 @@
// Copyright 2024 Moonbeam foundation
// This file is part of Moonbeam.
// Moonbeam 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.
// Moonbeam 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 Moonbeam. If not, see <http://www.gnu.org/licenses/>.
//! Autogenerated weights for `pallet_xcm_weight_trader`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0
//! DATE: 2024-09-13, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `ip-10-0-0-176`, CPU: `Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz`
//! WASM-EXECUTION: Compiled, CHAIN: Some("moonbase-dev"), DB CACHE: 1024
// Executed Command:
// ./target/production/moonbeam
// benchmark
// pallet
// --chain=moonbase-dev
// --steps=50
// --repeat=20
// --pallet=pallet_xcm_weight_trader
// --extrinsic=*
// --wasm-execution=compiled
// --header=./file_header.txt
// --template=./benchmarking/frame-weight-template.hbs
// --output=./runtime/common/src/weights/
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
use sp_std::marker::PhantomData;
/// Weights for `pallet_xcm_weight_trader`.
pub struct WeightInfo<T>(PhantomData<T>);
impl<T: frame_system::Config> pallet_xcm_weight_trader::WeightInfo for WeightInfo<T> {
/// Storage: `XcmWeightTrader::SupportedAssets` (r:1 w:1)
/// Proof: `XcmWeightTrader::SupportedAssets` (`max_values`: None, `max_size`: Some(635), added: 3110, mode: `MaxEncodedLen`)
fn add_asset() -> Weight {
// Proof Size summary in bytes:
// Measured: `42`
// Estimated: `4100`
// Minimum execution time: 9_432_000 picoseconds.
Weight::from_parts(9_644_000, 4100)
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
/// Storage: `XcmWeightTrader::SupportedAssets` (r:1 w:1)
/// Proof: `XcmWeightTrader::SupportedAssets` (`max_values`: None, `max_size`: Some(635), added: 3110, mode: `MaxEncodedLen`)
fn edit_asset() -> Weight {
// Proof Size summary in bytes:
// Measured: `102`
// Estimated: `4100`
// Minimum execution time: 10_261_000 picoseconds.
Weight::from_parts(10_540_000, 4100)
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
/// Storage: `XcmWeightTrader::SupportedAssets` (r:1 w:1)
/// Proof: `XcmWeightTrader::SupportedAssets` (`max_values`: None, `max_size`: Some(635), added: 3110, mode: `MaxEncodedLen`)
fn resume_asset_support() -> Weight {
// Proof Size summary in bytes:
// Measured: `102`
// Estimated: `4100`
// Minimum execution time: 10_153_000 picoseconds.
Weight::from_parts(10_537_000, 4100)
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
/// Storage: `XcmWeightTrader::SupportedAssets` (r:1 w:1)
/// Proof: `XcmWeightTrader::SupportedAssets` (`max_values`: None, `max_size`: Some(635), added: 3110, mode: `MaxEncodedLen`)
fn pause_asset_support() -> Weight {
// Proof Size summary in bytes:
// Measured: `102`
// Estimated: `4100`
// Minimum execution time: 10_029_000 picoseconds.
Weight::from_parts(10_456_000, 4100)
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
/// Storage: `XcmWeightTrader::SupportedAssets` (r:1 w:1)
/// Proof: `XcmWeightTrader::SupportedAssets` (`max_values`: None, `max_size`: Some(635), added: 3110, mode: `MaxEncodedLen`)
fn remove_asset() -> Weight {
// Proof Size summary in bytes:
// Measured: `102`
// Estimated: `4100`
// Minimum execution time: 9_844_000 picoseconds.
Weight::from_parts(10_218_000, 4100)
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
}