From bb0e969dcbe7b1a3a1dc8a866822535745b4fc12 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Wed, 24 Jul 2024 19:25:26 +0200 Subject: [PATCH 01/19] feat(xcm-builder): add an adapter for configuring AssetExchanger --- Cargo.lock | 3 + cumulus/primitives/utility/src/lib.rs | 21 +- polkadot/xcm/xcm-builder/Cargo.toml | 8 +- .../xcm/xcm-builder/src/asset_exchange/mod.rs | 22 + .../asset_exchange/single_asset_adapter.rs | 198 +++++++++ .../single_asset_adapter/mock.rs | 375 ++++++++++++++++++ .../single_asset_adapter/tests.rs | 181 +++++++++ polkadot/xcm/xcm-builder/src/lib.rs | 3 + polkadot/xcm/xcm-builder/src/test_utils.rs | 4 + polkadot/xcm/xcm-builder/src/tests/mock.rs | 4 + .../xcm-executor/src/traits/asset_exchange.rs | 17 + substrate/frame/asset-conversion/src/lib.rs | 2 +- 12 files changed, 834 insertions(+), 4 deletions(-) create mode 100644 polkadot/xcm/xcm-builder/src/asset_exchange/mod.rs create mode 100644 polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter.rs create mode 100644 polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter/mock.rs create mode 100644 polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter/tests.rs diff --git a/Cargo.lock b/Cargo.lock index d085f2d90ff7..33385a741d30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21011,6 +21011,7 @@ dependencies = [ "frame-system", "impl-trait-for-tuples", "log", + "pallet-asset-conversion", "pallet-assets", "pallet-balances", "pallet-salary", @@ -21024,8 +21025,10 @@ dependencies = [ "primitive-types", "scale-info", "sp-arithmetic", + "sp-core", "sp-io", "sp-runtime", + "sp-std 14.0.0", "sp-weights", "staging-xcm", "staging-xcm-executor", diff --git a/cumulus/primitives/utility/src/lib.rs b/cumulus/primitives/utility/src/lib.rs index 9d5bf4e231eb..3ebcb44fa439 100644 --- a/cumulus/primitives/utility/src/lib.rs +++ b/cumulus/primitives/utility/src/lib.rs @@ -407,10 +407,22 @@ impl< let first_asset: Asset = payment.fungible.pop_first().ok_or(XcmError::AssetNotFound)?.into(); let (fungibles_asset, balance) = FungiblesAssetMatcher::matches_fungibles(&first_asset) - .map_err(|_| XcmError::AssetNotFound)?; + .map_err(|error| { + log::trace!( + target: "xcm::weight", + "SwapFirstAssetTrader::buy_weight asset {:?} didn't match. Error: {:?}", + first_asset, + error, + ); + XcmError::AssetNotFound + })?; let swap_asset = fungibles_asset.clone().into(); if Target::get().eq(&swap_asset) { + log::trace!( + target: "xcm::weight", + "SwapFirstAssetTrader::buy_weight Asset was same as Target, swap not needed.", + ); // current trader is not applicable. return Err(XcmError::FeesNotMet) } @@ -424,7 +436,12 @@ impl< credit_in, fee, ) - .map_err(|(credit_in, _)| { + .map_err(|(credit_in, error)| { + log::trace!( + target: "xcm::weight", + "SwapFirstAssetTrader::buy_weight swap couldn't be done. Error was: {:?}", + error, + ); drop(credit_in); XcmError::FeesNotMet })?; diff --git a/polkadot/xcm/xcm-builder/Cargo.toml b/polkadot/xcm/xcm-builder/Cargo.toml index 7702e2f9be07..c722f9bca45c 100644 --- a/polkadot/xcm/xcm-builder/Cargo.toml +++ b/polkadot/xcm/xcm-builder/Cargo.toml @@ -17,18 +17,21 @@ xcm = { workspace = true } xcm-executor = { workspace = true } sp-arithmetic = { workspace = true } sp-io = { workspace = true } +sp-std = { workspace = true } sp-runtime = { workspace = true } sp-weights = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } pallet-transaction-payment = { workspace = true } +pallet-asset-conversion = { workspace = true } log = { workspace = true } # Polkadot dependencies polkadot-parachain-primitives = { workspace = true } [dev-dependencies] -primitive-types = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +primitive-types = { features = ["codec", "num-traits", "scale-info"], workspace = true } pallet-balances = { workspace = true, default-features = true } pallet-xcm = { workspace = true, default-features = true } pallet-salary = { workspace = true, default-features = true } @@ -43,6 +46,7 @@ default = ["std"] runtime-benchmarks = [ "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", + "pallet-asset-conversion/runtime-benchmarks", "pallet-assets/runtime-benchmarks", "pallet-balances/runtime-benchmarks", "pallet-salary/runtime-benchmarks", @@ -59,9 +63,11 @@ std = [ "frame-support/std", "frame-system/std", "log/std", + "pallet-asset-conversion/std", "pallet-transaction-payment/std", "polkadot-parachain-primitives/std", "scale-info/std", + "sp-std/std", "sp-arithmetic/std", "sp-io/std", "sp-runtime/std", diff --git a/polkadot/xcm/xcm-builder/src/asset_exchange/mod.rs b/polkadot/xcm/xcm-builder/src/asset_exchange/mod.rs new file mode 100644 index 000000000000..d42a443c9be1 --- /dev/null +++ b/polkadot/xcm/xcm-builder/src/asset_exchange/mod.rs @@ -0,0 +1,22 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +//! Adapters for the AssetExchanger config item. +//! +//! E.g. types that implement the [`xcm_executor::traits::AssetExchange`] trait. + +mod single_asset_adapter; +pub use single_asset_adapter::SingleAssetExchangeAdapter; diff --git a/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter.rs b/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter.rs new file mode 100644 index 000000000000..bdb7bffb871a --- /dev/null +++ b/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter.rs @@ -0,0 +1,198 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +//! Single asset exchange adapter. + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +use core::marker::PhantomData; +use frame_support::{ensure, traits::tokens::fungibles}; +use pallet_asset_conversion::{QuotePrice, SwapCredit}; +use sp_std::vec; +use xcm::prelude::*; +use xcm_executor::{ + traits::{AssetExchange, MatchesFungibles}, + AssetsInHolding, +}; + +/// An adapter from [`pallet_asset_conversion::SwapCredit`] and +/// [`pallet_asset_conversion::QuotePrice`] to [`xcm_executor::traits::AssetExchange`]. +/// +/// This adapter takes just one fungible asset in `give` and allows only one fungible asset in +/// `want`. If you need to handle more assets in either `give` or `want`, then you should use +/// another type that implements [`xcm_executor::traits::AssetExchange`] or build your own. +/// +/// `exchange_asset` will return an error if there's more than one asset in `want`. +pub struct SingleAssetExchangeAdapter( + PhantomData<(AssetConversion, Fungibles, Matcher, AccountId)>, +); +impl AssetExchange + for SingleAssetExchangeAdapter +where + AssetConversion: SwapCredit< + AccountId, + Balance = u128, + AssetKind = Fungibles::AssetId, + Credit = fungibles::Credit, + > + QuotePrice, + Fungibles: fungibles::Balanced, + Matcher: MatchesFungibles, +{ + fn exchange_asset( + _: Option<&Location>, + give: AssetsInHolding, + want: &Assets, + maximal: bool, + ) -> Result { + let mut give_iter = give.fungible_assets_iter(); + let give_asset = give_iter.next().ok_or_else(|| { + log::trace!( + target: "xcm::SingleAssetExchangeAdapter::exchange_asset", + "No fungible asset was in `give`.", + ); + give.clone() + })?; + ensure!(give_iter.next().is_none(), give.clone()); // We only support 1 asset in `give`. + ensure!(want.len() == 1, give.clone()); // We only support 1 asset in `want`. + let want_asset = if let Some(asset) = want.get(0) { + asset + } else { + log::trace!( + target: "xcm::SingleAssetExchangeAdapter::exchange_asset", + "No asset was in `want`.", + ); + return Ok(give.clone()); + }; + let (give_asset_id, give_amount) = + Matcher::matches_fungibles(&give_asset).map_err(|error| { + log::trace!( + target: "xcm::SingleAssetExchangeAdapter::exchange_asset", + "Could not map XCM asset give {:?} to FRAME asset. Error: {:?}", + give_asset, + error, + ); + give.clone() + })?; + let (want_asset_id, want_amount) = + Matcher::matches_fungibles(&want_asset).map_err(|error| { + log::trace!( + target: "xcm::SingleAssetExchangeAdapter::exchange_asset", + "Could not map XCM asset want {:?} to FRAME asset. Error: {:?}", + want_asset, + error, + ); + give.clone() + })?; + + // We have to do this to convert the XCM assets into credit the pool can use. + let swap_asset = give_asset_id.clone().into(); + let credit_in = Fungibles::issue(give_asset_id, give_amount); + + // Do the swap. + let credit_out = if maximal { + // If `maximal`, then we swap exactly `credit_in` to get as much of `want_asset_id` as + // we can, with a minimum of `want_amount`. + >::swap_exact_tokens_for_tokens( + vec![swap_asset, want_asset_id], + credit_in, + Some(want_amount), + ) + .map_err(|(credit_in, error)| { + log::error!( + target: "xcm::SingleAssetExchangeAdapter::exchange_asset", + "Could not perform the swap, error: {:?}.", + error + ); + drop(credit_in); + give.clone() + })? + } else { + // If `minimal`, then we swap as little of `credit_in` as we can to get exactly + // `want_amount` of `want_asset_id`. + let (credit_out, _credit_change) = + >::swap_tokens_for_exact_tokens( + vec![swap_asset, want_asset_id], + credit_in, + want_amount, + ) + .map_err(|(credit_in, error)| { + log::error!( + target: "xcm::SingleAssetExchangeAdapter::exchange_asset", + "Could not perform the swap, error: {:?}.", + error + ); + drop(credit_in); + give.clone() + })?; + + // TODO: If we want to make this a generic adapter, this need not be 0. Handle it. + // Probably depositing it back to the holding. + // debug_assert!(credit_change.peek() == Zero::zero()); + + credit_out + }; + + let resulting_asset: Asset = (want_asset.id.clone(), credit_out.peek()).into(); + Ok(resulting_asset.into()) + } + + fn quote_exchange_price(asset1: &Asset, asset2: &Asset, maximal: bool) -> Option { + // We first match both XCM assets to the asset ID types `AssetConversion` can handle. + let (asset1_id, _) = Matcher::matches_fungibles(asset1) + .map_err(|error| { + log::trace!( + target: "xcm::SingleAssetExchangeAdapter::quote_exchange_price", + "Could not map XCM asset {:?} to FRAME asset. Error: {:?}.", + asset1, + error, + ); + () + }) + .ok()?; + // For `asset2`, we also want the desired amount. + let (asset2_id, desired_asset2_amount) = Matcher::matches_fungibles(asset2) + .map_err(|error| { + log::trace!( + target: "xcm::SingleAssetExchangeAdapter::quote_exchange_price", + "Could not map XCM asset {:?} to FRAME asset. Error: {:?}.", + asset2, + error, + ); + () + }) + .ok()?; + // We quote the price. + let necessary_asset1_amount = if maximal { + ::quote_price_exact_tokens_for_tokens( + asset1_id, + asset2_id, + desired_asset2_amount, + true, + )? + } else { + ::quote_price_tokens_for_exact_tokens( + asset1_id, + asset2_id, + desired_asset2_amount, + true, + )? + }; + Some(necessary_asset1_amount) + } +} diff --git a/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter/mock.rs b/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter/mock.rs new file mode 100644 index 000000000000..71b6f247405a --- /dev/null +++ b/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter/mock.rs @@ -0,0 +1,375 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +//! Mock to test [`SingleAssetExchangeAdapter`]. + +use core::marker::PhantomData; +use frame_support::{ + assert_ok, construct_runtime, derive_impl, ord_parameter_types, parameter_types, + traits::{ + fungible::{self, NativeFromLeft, NativeOrWithId}, + fungibles::Mutate, + tokens::imbalance::ResolveAssetTo, + AsEnsureOriginWithArg, Equals, Everything, Nothing, OriginTrait, PalletInfoAccess, + }, + PalletId, +}; +use sp_core::{ConstU128, ConstU32, Get}; +use sp_runtime::{ + traits::{AccountIdConversion, IdentityLookup, MaybeEquivalence, TryConvert, TryConvertInto}, + BuildStorage, Permill, +}; +use xcm::prelude::*; +use xcm_executor::{traits::ConvertLocation, XcmExecutor}; + +use crate::{FungibleAdapter, IsConcrete, MatchedConvertedConcreteId, StartsWith}; + +pub type Block = frame_system::mocking::MockBlock; +pub type AccountId = u64; +pub type Balance = u128; + +construct_runtime! { + pub struct Runtime { + System: frame_system, + Balances: pallet_balances, + AssetsPallet: pallet_assets::, + PoolAssets: pallet_assets::, + XcmPallet: pallet_xcm, + AssetConversion: pallet_asset_conversion, + } +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Runtime { + type Block = Block; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type AccountData = pallet_balances::AccountData; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] +impl pallet_balances::Config for Runtime { + type Balance = Balance; + type AccountStore = System; + type ExistentialDeposit = ConstU128<1>; +} + +pub type TrustBackedAssetsInstance = pallet_assets::Instance1; +pub type PoolAssetsInstance = pallet_assets::Instance2; + +#[derive_impl(pallet_assets::config_preludes::TestDefaultConfig)] +impl pallet_assets::Config for Runtime { + type Currency = Balances; + type Balance = Balance; + type AssetDeposit = ConstU128<1>; + type AssetAccountDeposit = ConstU128<10>; + type MetadataDepositBase = ConstU128<1>; + type MetadataDepositPerByte = ConstU128<1>; + type ApprovalDeposit = ConstU128<1>; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = frame_system::EnsureRoot; + type Freezer = (); + type CallbackHandle = (); +} + +#[derive_impl(pallet_assets::config_preludes::TestDefaultConfig)] +impl pallet_assets::Config for Runtime { + type Currency = Balances; + type Balance = Balance; + type AssetDeposit = ConstU128<1>; + type AssetAccountDeposit = ConstU128<10>; + type MetadataDepositBase = ConstU128<1>; + type MetadataDepositPerByte = ConstU128<1>; + type ApprovalDeposit = ConstU128<1>; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = frame_system::EnsureRoot; + type Freezer = (); + type CallbackHandle = (); +} + +/// Union fungibles implementation for `Assets` and `Balances`. +pub type NativeAndAssets = + fungible::UnionOf, AccountId>; + +parameter_types! { + pub const AssetConversionPalletId: PalletId = PalletId(*b"py/ascon"); + pub const Native: NativeOrWithId = NativeOrWithId::Native; + pub const LiquidityWithdrawalFee: Permill = Permill::from_percent(0); +} + +ord_parameter_types! { + pub const AssetConversionOrigin: AccountId = + AccountIdConversion::::into_account_truncating(&AssetConversionPalletId::get()); +} + +pub type PoolIdToAccountId = pallet_asset_conversion::AccountIdConverter< + AssetConversionPalletId, + (NativeOrWithId, NativeOrWithId), +>; + +impl pallet_asset_conversion::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type HigherPrecisionBalance = sp_core::U256; + type AssetKind = NativeOrWithId; + type Assets = NativeAndAssets; + type PoolId = (Self::AssetKind, Self::AssetKind); + type PoolLocator = pallet_asset_conversion::WithFirstAsset< + Native, + AccountId, + Self::AssetKind, + PoolIdToAccountId, + >; + type PoolAssetId = u32; + type PoolAssets = PoolAssets; + type PoolSetupFee = ConstU128<100>; // Asset class deposit fees are sufficient to prevent spam + type PoolSetupFeeAsset = Native; + type PoolSetupFeeTarget = ResolveAssetTo; + type LiquidityWithdrawalFee = LiquidityWithdrawalFee; + type LPFee = ConstU32<3>; + type PalletId = AssetConversionPalletId; + type MaxSwapPathLength = ConstU32<3>; + type MintMinLiquidity = ConstU128<100>; + type WeightInfo = (); + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = assets_common::benchmarks::AssetPairFactory< + HereLocation, + parachain_info::Pallet, + xcm_config::TrustBackedAssetsPalletIndex, + Location, + >; +} + +/// We only alias local accounts. +pub type LocationToAccountId = AccountIndex64Aliases; + +parameter_types! { + pub HereLocation: Location = Here.into_location(); + pub WeightPerInstruction: Weight = Weight::from_parts(1, 1); + pub MaxInstructions: u32 = 100; + pub UniversalLocation: InteriorLocation = [GlobalConsensus(Polkadot), Parachain(1000)].into(); + pub TrustBackedAssetsPalletIndex: u8 = ::index() as u8; + pub TrustBackedAssetsPalletLocation: Location = PalletInstance(TrustBackedAssetsPalletIndex::get()).into(); +} + +/// Adapter for the native token. +pub type FungibleTransactor = FungibleAdapter< + // Use this implementation of the `fungible::*` traits. + // `Balances` is the name given to the balances pallet + Balances, + // This transactor deals with the native token. + IsConcrete, + // How to convert an XCM Location into a local account id. + // This is also something that's configured in the XCM executor. + LocationToAccountId, + // The type for account ids, only needed because `fungible` is generic over it. + AccountId, + // Not tracking teleports. + (), +>; + +pub type Weigher = crate::FixedWeightBounds; + +pub struct LocationToAssetId; +impl MaybeEquivalence> for LocationToAssetId { + fn convert(location: &Location) -> Option> { + let pallet_instance = TrustBackedAssetsPalletIndex::get(); + match location.unpack() { + (0, [PalletInstance(instance), GeneralIndex(index)]) + if *instance == pallet_instance => + Some(NativeOrWithId::WithId(*index as u32)), + (0, []) => Some(NativeOrWithId::Native), + _ => None, + } + } + + fn convert_back(asset_id: &NativeOrWithId) -> Option { + let pallet_instance = TrustBackedAssetsPalletIndex::get(); + Some(match asset_id { + NativeOrWithId::WithId(id) => + Location::new(0, [PalletInstance(pallet_instance), GeneralIndex((*id).into())]), + NativeOrWithId::Native => Location::new(0, []), + }) + } +} + +pub type PoolAssetsExchanger = crate::SingleAssetExchangeAdapter< + AssetConversion, + NativeAndAssets, + MatchedConvertedConcreteId< + NativeOrWithId, + Balance, + (StartsWith, Equals), + LocationToAssetId, + TryConvertInto, + >, + AccountId, +>; + +pub struct XcmConfig; +impl xcm_executor::Config for XcmConfig { + type RuntimeCall = RuntimeCall; + type XcmSender = (); + type AssetTransactor = FungibleTransactor; + type OriginConverter = (); + type IsReserve = (); + type IsTeleporter = (); + type UniversalLocation = UniversalLocation; + // This is not safe, you should use `crate::AllowTopLevelPaidExecutionFrom` in a + // production chain + type Barrier = crate::AllowUnpaidExecutionFrom; + type Weigher = Weigher; + type Trader = (); + type ResponseHandler = (); + type AssetTrap = (); + type AssetLocker = (); + type AssetExchanger = PoolAssetsExchanger; + type AssetClaims = (); + type SubscriptionService = (); + type PalletInstancesInfo = (); + type FeeManager = (); + type MaxAssetsIntoHolding = ConstU32<1>; + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = RuntimeCall; + type SafeCallFilter = Everything; + type Aliasers = Nothing; + type TransactionalProcessor = crate::FrameTransactionalProcessor; + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); + type XcmRecorder = (); +} + +/// Simple converter from a [`Location`] with an [`AccountIndex64`] junction and no parent to a +/// `u64`. +pub struct AccountIndex64Aliases; +impl ConvertLocation for AccountIndex64Aliases { + fn convert_location(location: &Location) -> Option { + let index = match location.unpack() { + (0, [AccountIndex64 { index, network: None }]) => index, + _ => return None, + }; + Some((*index).into()) + } +} + +/// `Convert` implementation to convert from some a `Signed` (system) `Origin` into an +/// `AccountIndex64`. +/// +/// Typically used when configuring `pallet-xcm` in tests to allow `u64` accounts to dispatch an XCM +/// from an `AccountIndex64` origin. +pub struct SignedToAccountIndex64( + PhantomData<(RuntimeOrigin, AccountId, Network)>, +); +impl, Network: Get>> + TryConvert for SignedToAccountIndex64 +where + RuntimeOrigin::PalletsOrigin: From> + + TryInto, Error = RuntimeOrigin::PalletsOrigin>, +{ + fn try_convert(o: RuntimeOrigin) -> Result { + o.try_with_caller(|caller| match caller.try_into() { + Ok(frame_system::RawOrigin::Signed(who)) => + Ok(Junction::AccountIndex64 { network: Network::get(), index: who.into() }.into()), + Ok(other) => Err(other.into()), + Err(other) => Err(other), + }) + } +} + +parameter_types! { + pub const NoNetwork: Option = None; +} + +pub type LocalOriginToLocation = SignedToAccountIndex64; + +impl pallet_xcm::Config for Runtime { + // We turn off sending for these tests + type SendXcmOrigin = crate::EnsureXcmOrigin; + type XcmRouter = (); + // Anyone can execute XCM programs + type ExecuteXcmOrigin = crate::EnsureXcmOrigin; + // We execute any type of program + type XcmExecuteFilter = Everything; + // How we execute programs + type XcmExecutor = XcmExecutor; + // We don't allow teleports + type XcmTeleportFilter = Nothing; + // We don't allow reserve transfers + type XcmReserveTransferFilter = Nothing; + // Same weigher executor uses to weigh XCM programs + type Weigher = Weigher; + // Same universal location + type UniversalLocation = UniversalLocation; + // No version discovery needed + const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 0; + type AdvertisedXcmVersion = frame_support::traits::ConstU32<3>; + type AdminOrigin = frame_system::EnsureRoot; + // No locking + type TrustedLockers = (); + type MaxLockers = frame_support::traits::ConstU32<0>; + type MaxRemoteLockConsumers = frame_support::traits::ConstU32<0>; + type RemoteLockConsumerIdentifier = (); + // How to turn locations into accounts + type SovereignAccountOf = LocationToAccountId; + // A currency to pay for things and its matcher, we are using the relay token + type Currency = Balances; + type CurrencyMatcher = crate::IsConcrete; + // Pallet benchmarks, no need for this recipe + type WeightInfo = pallet_xcm::TestWeightInfo; + // Runtime types + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; +} + +pub const INITIAL_BALANCE: Balance = 1_000_000_000; + +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + + pallet_balances::GenesisConfig:: { + balances: vec![(0, INITIAL_BALANCE), (1, INITIAL_BALANCE), (2, INITIAL_BALANCE)], + } + .assimilate_storage(&mut t) + .unwrap(); + + let owner = 0; + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + System::set_block_number(1); + assert_ok!(AssetsPallet::force_create(RuntimeOrigin::root(), 1, owner, false, 1,)); + assert_ok!(AssetsPallet::mint_into(1, &owner, INITIAL_BALANCE,)); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(owner), + Box::new(NativeOrWithId::Native), + Box::new(NativeOrWithId::WithId(1)), + )); + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(owner), + Box::new(NativeOrWithId::Native), + Box::new(NativeOrWithId::WithId(1)), + 50_000_000, + 1, + 100_000_000, + 1, + owner, + )); + }); + ext +} diff --git a/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter/tests.rs b/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter/tests.rs new file mode 100644 index 000000000000..9c1214d9ef63 --- /dev/null +++ b/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter/tests.rs @@ -0,0 +1,181 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +//! Tests for the [`SingleAssetExchangeAdapter`] type. + +use super::mock::*; +use xcm::prelude::*; +use xcm_executor::{traits::AssetExchange, AssetsInHolding}; + +// ========== Happy path ========== + +/// Scenario: +/// Account #3 wants to use the local liquidity pool between two custom assets, +/// 1 and 2. +#[test] +fn maximal_exchange() { + new_test_ext().execute_with(|| { + let assets = PoolAssetsExchanger::exchange_asset( + None, + vec![([PalletInstance(2), GeneralIndex(1)], 10_000_000).into()].into(), + &vec![(Here, 2_000_000).into()].into(), + true, // Maximal + ) + .unwrap(); + let amount = get_amount_from_first_fungible(assets); + let pool_fee = 6; + assert_eq!(amount, 50_000_000 - pool_fee); + }); +} + +#[test] +fn minimal_exchange() { + new_test_ext().execute_with(|| { + let assets = PoolAssetsExchanger::exchange_asset( + None, + vec![([PalletInstance(2), GeneralIndex(1)], 10_000_000).into()].into(), + &vec![(Here, 2_000_000).into()].into(), + false, // Minimal + ) + .unwrap(); + let amount = get_amount_from_first_fungible(assets); + assert_eq!(amount, 2_000_000); + }); +} + +#[test] +fn maximal_quote() { + new_test_ext().execute_with(|| { + let _amount = quote( + &([PalletInstance(2), GeneralIndex(1)], 1).into(), + &(Here, 2_000_000).into(), + true, + ); + }); +} + +#[test] +fn minimal_quote() { + new_test_ext().execute_with(|| { + let _amount = quote( + &([PalletInstance(2), GeneralIndex(1)], 10).into(), + &(Here, 2_000_000).into(), + false, + ); + }); +} + +// ========== Unhappy path ========== + +#[test] +fn no_asset_in_give() { + new_test_ext().execute_with(|| { + assert!(PoolAssetsExchanger::exchange_asset( + None, + vec![].into(), + &vec![(Here, 2_000_000).into()].into(), + true + ) + .is_err()); + }); +} + +#[test] +fn more_than_one_asset_in_give() { + new_test_ext().execute_with(|| { + assert!(PoolAssetsExchanger::exchange_asset( + None, + vec![([PalletInstance(2), GeneralIndex(1)], 1).into(), (Here, 2).into()].into(), + &vec![(Here, 2_000_000).into()].into(), + true + ) + .is_err()); + }); +} + +#[test] +fn no_asset_in_want() { + new_test_ext().execute_with(|| { + assert!(PoolAssetsExchanger::exchange_asset( + None, + vec![([PalletInstance(2), GeneralIndex(1)], 10_000_000).into()].into(), + &vec![].into(), + true + ) + .is_err()); + }); +} + +#[test] +fn more_than_one_asset_in_want() { + new_test_ext().execute_with(|| { + assert!(PoolAssetsExchanger::exchange_asset( + None, + vec![([PalletInstance(2), GeneralIndex(1)], 10_000_000).into()].into(), + &vec![(Here, 2_000_000).into(), ([PalletInstance(2), GeneralIndex(1)], 1).into()] + .into(), + true + ) + .is_err()); + }); +} + +#[test] +fn give_asset_does_not_match() { + new_test_ext().execute_with(|| { + let nonexistent_asset_id = 1000; + assert!(PoolAssetsExchanger::exchange_asset( + None, + vec![([PalletInstance(2), GeneralIndex(nonexistent_asset_id)], 10_000_000).into()] + .into(), + &vec![(Here, 2_000_000).into()].into(), + true + ) + .is_err()); + }); +} + +#[test] +fn want_asset_does_not_match() { + new_test_ext().execute_with(|| { + let nonexistent_asset_id = 1000; + assert!(PoolAssetsExchanger::exchange_asset( + None, + vec![(Here, 2_000_000).into()].into(), + &vec![([PalletInstance(2), GeneralIndex(nonexistent_asset_id)], 10_000_000).into()] + .into(), + true + ) + .is_err()); + }); +} + +#[test] +fn exchange_fails() {} + +// ========== Helper functions ========== + +fn get_amount_from_first_fungible(assets: AssetsInHolding) -> u128 { + let first_fungible = assets.fungible_assets_iter().next().unwrap(); + let Fungible(amount) = first_fungible.fun else { + unreachable!("Asset should be fungible"); + }; + amount +} + +fn quote(asset_1: &Asset, asset_2: &Asset, maximal: bool) -> Option { + PoolAssetsExchanger::quote_exchange_price(asset_1, asset_2, maximal) +} diff --git a/polkadot/xcm/xcm-builder/src/lib.rs b/polkadot/xcm/xcm-builder/src/lib.rs index 4cf83c9fc451..bec3bdcb05a0 100644 --- a/polkadot/xcm/xcm-builder/src/lib.rs +++ b/polkadot/xcm/xcm-builder/src/lib.rs @@ -35,6 +35,9 @@ pub use asset_conversion::{ AsPrefixedGeneralIndex, ConvertedConcreteId, MatchedConvertedConcreteId, }; +mod asset_exchange; +pub use asset_exchange::SingleAssetExchangeAdapter; + mod barriers; pub use barriers::{ AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, diff --git a/polkadot/xcm/xcm-builder/src/test_utils.rs b/polkadot/xcm/xcm-builder/src/test_utils.rs index 37a49a1b3dc7..fa69d7bcda96 100644 --- a/polkadot/xcm/xcm-builder/src/test_utils.rs +++ b/polkadot/xcm/xcm-builder/src/test_utils.rs @@ -109,6 +109,10 @@ impl AssetExchange for TestAssetExchanger { ) -> Result { Ok(want.clone().into()) } + + fn quote_exchange_price(_: &Asset, _: &Asset, _: bool) -> Option { + None + } } pub struct TestPalletsInfo; diff --git a/polkadot/xcm/xcm-builder/src/tests/mock.rs b/polkadot/xcm/xcm-builder/src/tests/mock.rs index ac43d217ff3f..09f6fa348a13 100644 --- a/polkadot/xcm/xcm-builder/src/tests/mock.rs +++ b/polkadot/xcm/xcm-builder/src/tests/mock.rs @@ -695,6 +695,10 @@ impl AssetExchange for TestAssetExchange { EXCHANGE_ASSETS.with(|l| l.replace(have)); Ok(get) } + + fn quote_exchange_price(_: &Asset, _: &Asset, _: bool) -> Option { + None + } } pub struct SiblingPrefix; diff --git a/polkadot/xcm/xcm-executor/src/traits/asset_exchange.rs b/polkadot/xcm/xcm-executor/src/traits/asset_exchange.rs index 432a7498ed4c..49bac348bed4 100644 --- a/polkadot/xcm/xcm-executor/src/traits/asset_exchange.rs +++ b/polkadot/xcm/xcm-executor/src/traits/asset_exchange.rs @@ -37,6 +37,13 @@ pub trait AssetExchange { want: &Assets, maximal: bool, ) -> Result; + + /// Handler for quoting the exchange price of two assets. + /// + /// - `asset1` The first asset. + /// - `asset2` The second asset. + /// - `maximal`: If `true`, then all of `asset1` should be used. + fn quote_exchange_price(asset1: &Asset, asset2: &Asset, maximal: bool) -> Option; } #[impl_trait_for_tuples::impl_for_tuples(30)] @@ -55,4 +62,14 @@ impl AssetExchange for Tuple { )* ); Err(give) } + + fn quote_exchange_price(asset1: &Asset, asset2: &Asset, maximal: bool) -> Option { + for_tuples!( #( + match Tuple::quote_exchange_price(asset1, asset2, maximal) { + Some(amount) => return Some(amount), + None => {} + } + )* ); + None + } } diff --git a/substrate/frame/asset-conversion/src/lib.rs b/substrate/frame/asset-conversion/src/lib.rs index a9dc30375e5a..d6671a45be55 100644 --- a/substrate/frame/asset-conversion/src/lib.rs +++ b/substrate/frame/asset-conversion/src/lib.rs @@ -435,7 +435,7 @@ pub mod pallet { /// calls to render the liquidity withdrawable and rectify the exchange rate. /// /// Once liquidity is added, someone may successfully call - /// [`Pallet::swap_exact_tokens_for_tokens`] successfully. + /// [`Pallet::swap_exact_tokens_for_tokens`]. #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::add_liquidity())] pub fn add_liquidity( From 5939ba21aeb99fa95e6abf7078eb1b353bdbe299 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Wed, 24 Jul 2024 20:09:11 +0200 Subject: [PATCH 02/19] feat: allow different assets for delivery fees --- Cargo.lock | 3 + .../assets/asset-hub-westend/src/genesis.rs | 8 +- .../parachains/testing/penpal/src/genesis.rs | 5 +- .../parachains/testing/penpal/src/lib.rs | 2 + .../tests/assets/asset-hub-westend/src/lib.rs | 1 + .../src/tests/fellowship_treasury.rs | 12 +- .../src/tests/hybrid_transfers.rs | 6 +- .../src/tests/reserve_transfer.rs | 366 +++++++++++++++++- .../asset-hub-westend/src/tests/treasury.rs | 12 +- .../assets/asset-hub-rococo/src/xcm_config.rs | 32 +- .../asset-hub-westend/src/xcm_config.rs | 30 +- .../runtimes/assets/common/Cargo.toml | 3 + .../runtimes/assets/common/src/lib.rs | 3 +- .../runtimes/testing/penpal/Cargo.toml | 7 + .../runtimes/testing/penpal/src/lib.rs | 123 +++++- .../runtimes/testing/penpal/src/xcm_config.rs | 79 +++- polkadot/xcm/xcm-executor/src/lib.rs | 144 ++++++- 17 files changed, 760 insertions(+), 76 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 33385a741d30..9a36ac7a4cd6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1076,6 +1076,7 @@ dependencies = [ "impl-trait-for-tuples", "log", "pallet-asset-conversion", + "pallet-assets", "pallet-xcm", "parachains-common", "parity-scale-codec", @@ -12451,6 +12452,7 @@ dependencies = [ "frame-try-runtime", "hex-literal", "log", + "pallet-asset-conversion", "pallet-asset-tx-payment", "pallet-assets", "pallet-aura", @@ -12469,6 +12471,7 @@ dependencies = [ "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-runtime-common", + "primitive-types", "scale-info", "smallvec", "sp-api", diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/genesis.rs index 219d1306906c..07b66c07747c 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/genesis.rs @@ -21,12 +21,13 @@ use sp_core::{sr25519, storage::Storage}; use emulated_integration_tests_common::{ accounts, build_genesis_storage, collators, get_account_id_from_seed, PenpalSiblingSovereignAccount, PenpalTeleportableAssetLocation, RESERVABLE_ASSET_ID, - SAFE_XCM_VERSION, + SAFE_XCM_VERSION, USDT_ID, }; use parachains_common::{AccountId, Balance}; pub const PARA_ID: u32 = 1000; pub const ED: Balance = testnet_parachains_constants::westend::currency::EXISTENTIAL_DEPOSIT; +pub const USDT_ED: Balance = 70_000; parameter_types! { pub AssetHubWestendAssetOwner: AccountId = get_account_id_from_seed::("Alice"); @@ -64,7 +65,10 @@ pub fn genesis() -> Storage { ..Default::default() }, assets: asset_hub_westend_runtime::AssetsConfig { - assets: vec![(RESERVABLE_ASSET_ID, AssetHubWestendAssetOwner::get(), true, ED)], + assets: vec![ + (RESERVABLE_ASSET_ID, AssetHubWestendAssetOwner::get(), true, ED), + (USDT_ID, AssetHubWestendAssetOwner::get(), true, USDT_ED), + ], ..Default::default() }, foreign_assets: asset_hub_westend_runtime::ForeignAssetsConfig { diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/genesis.rs index 450439f5ea30..ac5bba834be9 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/genesis.rs @@ -22,11 +22,12 @@ use emulated_integration_tests_common::{ accounts, build_genesis_storage, collators, get_account_id_from_seed, SAFE_XCM_VERSION, }; use parachains_common::{AccountId, Balance}; -use penpal_runtime::xcm_config::{LocalReservableFromAssetHub, RelayLocation}; +use penpal_runtime::xcm_config::{LocalReservableFromAssetHub, RelayLocation, UsdtFromAssetHub}; // Penpal pub const PARA_ID_A: u32 = 2000; pub const PARA_ID_B: u32 = 2001; pub const ED: Balance = penpal_runtime::EXISTENTIAL_DEPOSIT; +pub const USDT_ED: Balance = 70_000; parameter_types! { pub PenpalSudoAccount: AccountId = get_account_id_from_seed::("Alice"); @@ -80,6 +81,8 @@ pub fn genesis(para_id: u32) -> Storage { (RelayLocation::get(), PenpalAssetOwner::get(), true, ED), // Sufficient AssetHub asset representation (LocalReservableFromAssetHub::get(), PenpalAssetOwner::get(), true, ED), + // USDT from AssetHub + (UsdtFromAssetHub::get(), PenpalAssetOwner::get(), true, USDT_ED), ], ..Default::default() }, diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/lib.rs index 91793d33f304..92dfa30f2e83 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/lib.rs @@ -53,6 +53,7 @@ decl_test_parachains! { PolkadotXcm: penpal_runtime::PolkadotXcm, Assets: penpal_runtime::Assets, ForeignAssets: penpal_runtime::ForeignAssets, + AssetConversion: penpal_runtime::AssetConversion, Balances: penpal_runtime::Balances, } }, @@ -76,6 +77,7 @@ decl_test_parachains! { PolkadotXcm: penpal_runtime::PolkadotXcm, Assets: penpal_runtime::Assets, ForeignAssets: penpal_runtime::ForeignAssets, + AssetConversion: penpal_runtime::AssetConversion, Balances: penpal_runtime::Balances, } }, diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs index 9401621c5ba3..4e84e5af75a4 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs @@ -63,6 +63,7 @@ mod imports { CustomizableAssetFromSystemAssetHub as PenpalCustomizableAssetFromSystemAssetHub, LocalReservableFromAssetHub as PenpalLocalReservableFromAssetHub, LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub, + UsdtFromAssetHub as PenpalUsdtFromAssetHub, }, PenpalAParaPallet as PenpalAPallet, PenpalAssetOwner, PenpalBParaPallet as PenpalBPallet, diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/fellowship_treasury.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/fellowship_treasury.rs index 15f4fe33bddc..e86a1b64ef83 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/fellowship_treasury.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/fellowship_treasury.rs @@ -15,13 +15,13 @@ use crate::imports::*; use emulated_integration_tests_common::accounts::{ALICE, BOB}; -use frame_support::traits::fungibles::{Create, Inspect, Mutate}; +use frame_support::traits::fungibles::{Inspect, Mutate}; use polkadot_runtime_common::impls::VersionedLocatableAsset; use xcm_executor::traits::ConvertLocation; #[test] fn create_and_claim_treasury_spend() { - const ASSET_ID: u32 = 1984; + const ASSET_ID: u32 = 1984; // USDT already created at genesis. const SPEND_AMOUNT: u128 = 1_000_000; // treasury location from a sibling parachain. let treasury_location: Location = @@ -44,13 +44,7 @@ fn create_and_claim_treasury_spend() { AssetHubWestend::execute_with(|| { type Assets = ::Assets; - // create an asset class and mint some assets to the treasury account. - assert_ok!(>::create( - ASSET_ID, - treasury_account.clone(), - true, - SPEND_AMOUNT / 2 - )); + // USDT already created at genesis. assert_ok!(>::mint_into(ASSET_ID, &treasury_account, SPEND_AMOUNT * 4)); // beneficiary has zero balance. assert_eq!(>::balance(ASSET_ID, &alice,), 0u128,); diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/hybrid_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/hybrid_transfers.rs index 49dfe8d58394..975bacea7b4f 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/hybrid_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/hybrid_transfers.rs @@ -613,10 +613,10 @@ fn transfer_foreign_assets_from_para_to_para_through_asset_hub() { >::balance(roc_at_westend_parachains, &receiver) }); - // Sender's balance is reduced by amount sent plus delivery fees + // Sender's balance is reduced by amount sent. assert!(sender_wnds_after < sender_wnds_before - wnd_to_send); assert_eq!(sender_rocs_after, sender_rocs_before - roc_to_send); - // Sovereign accounts on reserve are changed accordingly + // Sovereign accounts on reserve are changed accordingly. assert_eq!( wnds_in_sender_reserve_on_ah_after, wnds_in_sender_reserve_on_ah_before - wnd_to_send @@ -630,7 +630,7 @@ fn transfer_foreign_assets_from_para_to_para_through_asset_hub() { rocs_in_receiver_reserve_on_ah_after, rocs_in_receiver_reserve_on_ah_before + roc_to_send ); - // Receiver's balance is increased + // Receiver's balance is increased by amount sent minus delivery fees. assert!(receiver_wnds_after > receiver_wnds_before); assert_eq!(receiver_rocs_after, receiver_rocs_before + roc_to_send); } diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs index 65d013a0eec4..1ac627167da5 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs @@ -493,6 +493,19 @@ fn para_to_para_through_relay_limited_reserve_transfer_assets( ) } +fn para_to_para_through_asset_hub_limited_reserve_transfer_assets( + t: ParaToParaThroughAHTest, +) -> DispatchResult { + ::PolkadotXcm::limited_reserve_transfer_assets( + t.signed_origin, + bx!(t.args.dest.into()), + bx!(t.args.beneficiary.into()), + bx!(t.args.assets.into()), + t.args.fee_asset_item, + t.args.weight_limit, + ) +} + /// Reserve Transfers of native asset from Relay Chain to the System Parachain shouldn't work #[test] fn reserve_transfer_native_asset_from_relay_to_system_para_fails() { @@ -1132,8 +1145,359 @@ fn reserve_transfer_native_asset_from_para_to_para_through_relay() { >::balance(relay_native_asset_location, &receiver) }); - // Sender's balance is reduced by amount sent plus delivery fees + // Sender's balance is reduced by amount sent plus delivery fees. assert!(sender_assets_after < sender_assets_before - amount_to_send); + // Receiver's balance is increased by `amount_to_send` minus delivery fees. + assert!(receiver_assets_after > receiver_assets_before); + assert!(receiver_assets_after < receiver_assets_before + amount_to_send); +} + +// ========================================================================================= +// ==== Reserve Transfers - TrustBacked Asset pay fees using pool - AssetHub->Parachain ==== +// ========================================================================================= +#[test] +fn reserve_transfer_pool_assets_from_system_para_to_para() { + let asset_id = 9999u32; + let penpal_location = AssetHubWestend::sibling_location_of(PenpalA::para_id()); + let penpal_sov_account = AssetHubWestend::sovereign_account_id_of(penpal_location.clone()); + + // Create SA-of-Penpal-on-AHW with ED. + // This ED isn't reflected in any derivative in a PenpalA account. + AssetHubWestend::fund_accounts(vec![(penpal_sov_account.clone().into(), ASSET_HUB_WESTEND_ED)]); + + AssetHubWestend::force_create_asset( + asset_id.into(), + AssetHubWestendSender::get().into(), + false, + ASSET_MIN_BALANCE, + vec![(AssetHubWestendSender::get(), 10_000_000_000_000)], + ); + + let relay_asset_penpal_pov = RelayLocation::get(); + let custom_asset_penpal_pov = Location::new( + 1, + [Parachain(1000), PalletInstance(ASSETS_PALLET_ID), GeneralIndex(asset_id.into())], + ); + PenpalA::force_create_foreign_asset( + custom_asset_penpal_pov.clone(), + PenpalAssetOwner::get(), + true, + 1_000_000, + // We give it some funds to be able to add liquidity later. + vec![(PenpalASender::get(), 10_000_000_000_000)], + ); + + // Setup the pool between `relay_asset_penpal_pov` and `custom_asset_penpal_pov` on PenpalA. + // So we can swap the custom asset that comes from AssetHubWestend for native asset to pay for + // fees. + PenpalA::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_ok!(::AssetConversion::create_pool( + ::RuntimeOrigin::signed(PenpalASender::get()), + Box::new(relay_asset_penpal_pov.clone()), + Box::new(custom_asset_penpal_pov.clone()), + )); + + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, + ] + ); + + assert_ok!(::AssetConversion::add_liquidity( + ::RuntimeOrigin::signed(PenpalASender::get()), + Box::new(relay_asset_penpal_pov), + Box::new(custom_asset_penpal_pov.clone()), + // `custom_asset_penpal_pov` is worth a third of `relay_asset_penpal_pov` + 1_000_000_000_000, + 3_000_000_000_000, + 0, + 0, + PenpalASender::get().into() + )); + + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded { .. }) => {}, + ] + ); + }); + + let sender = AssetHubWestendSender::get(); + let receiver = PenpalAReceiver::get(); + let asset_amount_to_send = 1_000_000_000_000; + let assets: Assets = vec![( + [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(asset_id.into())], + asset_amount_to_send, + ) + .into()] + .into(); + + let test_args = TestContext { + sender: sender.clone(), + receiver: receiver.clone(), + args: TestArgs::new_para( + penpal_location, + receiver.clone(), + asset_amount_to_send, + assets, + None, + 0, + ), + }; + let mut test = SystemParaToParaTest::new(test_args); + + let sender_initial_balance = AssetHubWestend::execute_with(|| { + type Assets = ::Assets; + >::balance(asset_id, &sender) + }); + let sender_initial_native_balance = AssetHubWestend::execute_with(|| { + type Balances = ::Balances; + Balances::free_balance(&sender) + }); + let receiver_initial_balance = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(custom_asset_penpal_pov.clone(), &receiver) + }); + + test.set_assertion::(system_para_to_para_receiver_assertions); + test.set_dispatchable::(system_para_to_para_reserve_transfer_assets); + test.assert(); + + let sender_after_balance = AssetHubWestend::execute_with(|| { + type Assets = ::Assets; + >::balance(asset_id, &sender) + }); + let sender_after_native_balance = AssetHubWestend::execute_with(|| { + type Balances = ::Balances; + Balances::free_balance(&sender) + }); + let receiver_after_balance = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(custom_asset_penpal_pov, &receiver) + }); + + // TODO: When we allow payment with different assets locally, this should be the same, since + // they aren't used for fees. + assert!(sender_after_native_balance < sender_initial_native_balance); + // Sender account's balance decreases. + assert_eq!(sender_after_balance, sender_initial_balance - asset_amount_to_send); + // Receiver account's balance increases. + assert!(receiver_after_balance > receiver_initial_balance); + assert!(receiver_after_balance < receiver_initial_balance + asset_amount_to_send); +} + +// =============================================================================================== +// == Reserve Transfers USDT - Parachain->AssetHub->Parachain - pay fees with USDT (using pool) == +// =============================================================================================== +// +// Transfer USDT From Penpal A to Penpal B with AssetHub as the reserve, while paying fees using +// USDT by making use of existing USDT pools on AssetHub and destination. +#[test] +fn reserve_transfer_usdt_from_para_to_para_through_asset_hub() { + let destination = PenpalA::sibling_location_of(PenpalB::para_id()); + let sender = PenpalASender::get(); + let asset_amount_to_send: Balance = WESTEND_ED * 10000; + let fee_amount_to_send: Balance = WESTEND_ED * 10000; + let sender_chain_as_seen_by_asset_hub = + AssetHubWestend::sibling_location_of(PenpalA::para_id()); + let sov_of_sender_on_asset_hub = + AssetHubWestend::sovereign_account_id_of(sender_chain_as_seen_by_asset_hub); + let receiver_as_seen_by_asset_hub = AssetHubWestend::sibling_location_of(PenpalB::para_id()); + let sov_of_receiver_on_asset_hub = + AssetHubWestend::sovereign_account_id_of(receiver_as_seen_by_asset_hub); + + // Create SA-of-Penpal-on-AHW with ED. + // This ED isn't reflected in any derivative in a PenpalA account. + AssetHubWestend::fund_accounts(vec![ + (sov_of_sender_on_asset_hub.clone().into(), ASSET_HUB_WESTEND_ED), + (sov_of_receiver_on_asset_hub.clone().into(), ASSET_HUB_WESTEND_ED), + ]); + + // Give USDT to sov account of sender. + let usdt_id = 1984; + AssetHubWestend::execute_with(|| { + use frame_support::traits::tokens::fungibles::Mutate; + type Assets = ::Assets; + assert_ok!(>::mint_into( + usdt_id.into(), + &sov_of_sender_on_asset_hub.clone().into(), + asset_amount_to_send + fee_amount_to_send, + )); + }); + + // We create a pool between WND and USDT in AssetHub. + let native_asset = v3::Parent.into(); + let usdt = v3::Location::new( + 0, + [ + v3::Junction::PalletInstance(ASSETS_PALLET_ID), + v3::Junction::GeneralIndex(usdt_id.into()), + ], + ); + + // set up pool with USDT <> native pair + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_ok!(::Assets::mint( + ::RuntimeOrigin::signed(AssetHubWestendSender::get()), + usdt_id.into(), + AssetHubWestendSender::get().into(), + 10_000_000_000_000, // For it to have more than enough. + )); + + assert_ok!(::AssetConversion::create_pool( + ::RuntimeOrigin::signed(AssetHubWestendSender::get()), + Box::new(native_asset), + Box::new(usdt), + )); + + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, + ] + ); + + assert_ok!(::AssetConversion::add_liquidity( + ::RuntimeOrigin::signed(AssetHubWestendSender::get()), + Box::new(native_asset), + Box::new(usdt), + 1_000_000_000_000, + 2_000_000_000_000, // usdt is worth half of `native_asset` + 0, + 0, + AssetHubWestendSender::get().into() + )); + + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded { .. }) => {}, + ] + ); + }); + + let usdt_from_asset_hub = PenpalUsdtFromAssetHub::get(); + + // We also need a pool between WND and USDT on PenpalB. + PenpalB::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + let relay_asset = RelayLocation::get(); + + assert_ok!(::ForeignAssets::mint( + ::RuntimeOrigin::signed(PenpalAssetOwner::get()), + usdt_from_asset_hub.clone().into(), + PenpalBReceiver::get().into(), + 10_000_000_000_000, // For it to have more than enough. + )); + + assert_ok!(::AssetConversion::create_pool( + ::RuntimeOrigin::signed(PenpalBReceiver::get()), + Box::new(relay_asset.clone()), + Box::new(usdt_from_asset_hub.clone()), + )); + + assert_expected_events!( + PenpalB, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, + ] + ); + + assert_ok!(::AssetConversion::add_liquidity( + ::RuntimeOrigin::signed(PenpalBReceiver::get()), + Box::new(relay_asset), + Box::new(usdt_from_asset_hub.clone()), + 1_000_000_000_000, + 2_000_000_000_000, // `usdt_from_asset_hub` is worth half of `relay_asset` + 0, + 0, + PenpalBReceiver::get().into() + )); + + assert_expected_events!( + PenpalB, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded { .. }) => {}, + ] + ); + }); + + PenpalA::execute_with(|| { + use frame_support::traits::tokens::fungibles::Mutate; + type ForeignAssets = ::ForeignAssets; + assert_ok!(>::mint_into( + usdt_from_asset_hub.clone(), + &sender, + asset_amount_to_send + fee_amount_to_send, + )); + }); + + // Prepare assets to transfer. + let assets: Assets = + (usdt_from_asset_hub.clone(), asset_amount_to_send + fee_amount_to_send).into(); + // Just to be very specific we're not including anything other than USDT. + assert_eq!(assets.len(), 1); + + // Give the sender enough Relay tokens to pay for local delivery fees. + // TODO: When we support local delivery fee payment in other assets, we don't need this. + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(PenpalAssetOwner::get()), + RelayLocation::get(), + sender.clone(), + 10_000_000_000_000, // Large estimate to make sure it works. + ); + + // Init values for Parachain Destination + let receiver = PenpalBReceiver::get(); + + // Init Test + let fee_asset_index = 0; + let test_args = TestContext { + sender: sender.clone(), + receiver: receiver.clone(), + args: TestArgs::new_para( + destination, + receiver.clone(), + asset_amount_to_send, + assets, + None, + fee_asset_index, + ), + }; + let mut test = ParaToParaThroughAHTest::new(test_args); + + // Query initial balances + let sender_assets_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(usdt_from_asset_hub.clone(), &sender) + }); + let receiver_assets_before = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(usdt_from_asset_hub.clone(), &receiver) + }); + test.set_dispatchable::( + para_to_para_through_asset_hub_limited_reserve_transfer_assets, + ); + test.assert(); + + // Query final balances + let sender_assets_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(usdt_from_asset_hub.clone(), &sender) + }); + let receiver_assets_after = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(usdt_from_asset_hub, &receiver) + }); + + // Sender's balance is reduced by amount + assert!(sender_assets_after < sender_assets_before - asset_amount_to_send); // Receiver's balance is increased assert!(receiver_assets_after > receiver_assets_before); } diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/treasury.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/treasury.rs index 8cbce3e0d223..3660b828c27c 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/treasury.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/treasury.rs @@ -15,13 +15,13 @@ use crate::imports::*; use emulated_integration_tests_common::accounts::{ALICE, BOB}; -use frame_support::traits::fungibles::{Create, Inspect, Mutate}; +use frame_support::traits::fungibles::{Inspect, Mutate}; use polkadot_runtime_common::impls::VersionedLocatableAsset; use xcm_executor::traits::ConvertLocation; #[test] fn create_and_claim_treasury_spend() { - const ASSET_ID: u32 = 1984; + const ASSET_ID: u32 = 1984; // USDT already created at genesis. const SPEND_AMOUNT: u128 = 1_000_000; // treasury location from a sibling parachain. let treasury_location: Location = Location::new(1, PalletInstance(37)); @@ -43,13 +43,7 @@ fn create_and_claim_treasury_spend() { AssetHubWestend::execute_with(|| { type Assets = ::Assets; - // create an asset class and mint some assets to the treasury account. - assert_ok!(>::create( - ASSET_ID, - treasury_account.clone(), - true, - SPEND_AMOUNT / 2 - )); + // USDT already created at genesis. assert_ok!(>::mint_into(ASSET_ID, &treasury_account, SPEND_AMOUNT * 4)); // beneficiary has zero balance. assert_eq!(>::balance(ASSET_ID, &alice,), 0u128,); diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs index a11dca4f6d7c..8c8e991eb850 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs @@ -21,7 +21,7 @@ use super::{ XcmpQueue, }; use assets_common::{ - matching::{FromNetwork, FromSiblingParachain, IsForeignConcreteAsset}, + matching::{FromNetwork, FromSiblingParachain, IsForeignConcreteAsset, ParentLocation}, TrustBackedAssetsAsLocation, }; use frame_support::{ @@ -43,7 +43,7 @@ use parachains_common::{ use polkadot_parachain_primitives::primitives::Sibling; use polkadot_runtime_common::xcm_sender::ExponentialPrice; use snowbridge_router_primitives::inbound::GlobalConsensusEthereumConvertsFor; -use sp_runtime::traits::{AccountIdConversion, ConvertInto}; +use sp_runtime::traits::{AccountIdConversion, ConvertInto, TryConvertInto}; use testnet_parachains_constants::rococo::snowbridge::{ EthereumNetwork, INBOUND_QUEUE_PALLET_INDEX, }; @@ -55,11 +55,12 @@ use xcm_builder::{ EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, GlobalConsensusParachainConvertsFor, HashedDescription, IsConcrete, LocalMint, NetworkExportTableItem, NoChecking, NonFungiblesAdapter, ParentAsSuperuser, ParentIsPreset, - RelayChainAsNative, SendXcmFeeToAccount, SiblingParachainAsNative, SiblingParachainConvertsVia, + RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignPaidRemoteExporter, SovereignSignedViaLocation, StartsWith, StartsWithExplicitGlobalConsensus, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, - XcmFeeManagerFromComponents, + WithLatestLocationConverter, XcmFeeManagerFromComponents, SendXcmFeeToAccount, SingleAssetExchangeAdapter, + MatchedConvertedConcreteId, }; use xcm_executor::XcmExecutor; @@ -329,6 +330,27 @@ pub type TrustedTeleporters = ( IsForeignConcreteAsset>>, ); +/// Asset converter for pool assets. +/// Used to convert assets in pools to the asset required for fee payment. +/// The pool must be between the first asset and the one required for fee payment. +/// This type allows paying fees with any asset in a pool with the asset required for fee payment. +pub type PoolAssetsExchanger = SingleAssetExchangeAdapter< + crate::AssetConversion, + crate::NativeAndAssets, + ( + TrustBackedAssetsAsLocation, + ForeignAssetsConvertedConcreteId, + MatchedConvertedConcreteId< + xcm::v3::Location, + Balance, + Equals, + WithLatestLocationConverter, + TryConvertInto, + >, // Adding this back in to match on relay token. + ), + AccountId, +>; + pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; @@ -410,7 +432,7 @@ impl xcm_executor::Config for XcmConfig { type PalletInstancesInfo = AllPalletsWithSystem; type MaxAssetsIntoHolding = MaxAssetsIntoHolding; type AssetLocker = (); - type AssetExchanger = (); + type AssetExchanger = PoolAssetsExchanger; type FeeManager = XcmFeeManagerFromComponents< WaivedLocations, SendXcmFeeToAccount, diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs index 5ecfce18b6da..34ff951746a7 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs @@ -21,7 +21,7 @@ use super::{ XcmpQueue, }; use assets_common::{ - matching::{FromSiblingParachain, IsForeignConcreteAsset}, + matching::{FromSiblingParachain, IsForeignConcreteAsset, ParentLocation}, TrustBackedAssetsAsLocation, }; use frame_support::{ @@ -42,7 +42,7 @@ use parachains_common::{ }; use polkadot_parachain_primitives::primitives::Sibling; use polkadot_runtime_common::xcm_sender::ExponentialPrice; -use sp_runtime::traits::{AccountIdConversion, ConvertInto}; +use sp_runtime::traits::{AccountIdConversion, ConvertInto, TryConvertInto}; use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, @@ -51,10 +51,11 @@ use xcm_builder::{ EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, GlobalConsensusParachainConvertsFor, HashedDescription, IsConcrete, LocalMint, NetworkExportTableItem, NoChecking, NonFungiblesAdapter, ParentAsSuperuser, ParentIsPreset, - RelayChainAsNative, SendXcmFeeToAccount, SiblingParachainAsNative, SiblingParachainConvertsVia, + RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, StartsWith, StartsWithExplicitGlobalConsensus, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, + WithLatestLocationConverter, SendXcmFeeToAccount, SingleAssetExchangeAdapter, MatchedConvertedConcreteId, }; use xcm_executor::XcmExecutor; @@ -348,6 +349,27 @@ pub type TrustedTeleporters = ( IsForeignConcreteAsset>>, ); +/// Asset converter for pool assets. +/// Used to convert assets in pools to the asset required for fee payment. +/// The pool must be between the first asset and the one required for fee payment. +/// This type allows paying fees with any asset in a pool with the asset required for fee payment. +pub type PoolAssetsExchanger = SingleAssetExchangeAdapter< + crate::AssetConversion, + crate::NativeAndAssets, + ( + TrustBackedAssetsAsLocation, + ForeignAssetsConvertedConcreteId, + MatchedConvertedConcreteId< + xcm::v3::Location, + Balance, + Equals, + WithLatestLocationConverter, + TryConvertInto, + >, // Adding this back in to match on relay token. + ), + AccountId, +>; + pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; @@ -425,7 +447,7 @@ impl xcm_executor::Config for XcmConfig { type PalletInstancesInfo = AllPalletsWithSystem; type MaxAssetsIntoHolding = MaxAssetsIntoHolding; type AssetLocker = (); - type AssetExchanger = (); + type AssetExchanger = PoolAssetsExchanger; type FeeManager = XcmFeeManagerFromComponents< WaivedLocations, SendXcmFeeToAccount, diff --git a/cumulus/parachains/runtimes/assets/common/Cargo.toml b/cumulus/parachains/runtimes/assets/common/Cargo.toml index c6740269339d..fb66f0de2322 100644 --- a/cumulus/parachains/runtimes/assets/common/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/common/Cargo.toml @@ -19,6 +19,7 @@ impl-trait-for-tuples = { workspace = true } frame-support = { workspace = true } sp-api = { workspace = true } sp-runtime = { workspace = true } +pallet-assets = { workspace = true } pallet-asset-conversion = { workspace = true } # Polkadot @@ -42,6 +43,7 @@ std = [ "frame-support/std", "log/std", "pallet-asset-conversion/std", + "pallet-assets/std", "pallet-xcm/std", "parachains-common/std", "scale-info/std", @@ -56,6 +58,7 @@ runtime-benchmarks = [ "cumulus-primitives-core/runtime-benchmarks", "frame-support/runtime-benchmarks", "pallet-asset-conversion/runtime-benchmarks", + "pallet-assets/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", "parachains-common/runtime-benchmarks", "sp-runtime/runtime-benchmarks", diff --git a/cumulus/parachains/runtimes/assets/common/src/lib.rs b/cumulus/parachains/runtimes/assets/common/src/lib.rs index 4bb593f98929..deda5fa4ab9c 100644 --- a/cumulus/parachains/runtimes/assets/common/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/common/src/lib.rs @@ -29,7 +29,7 @@ use crate::matching::{LocalLocationPattern, ParentLocation}; use frame_support::traits::{Equals, EverythingBut}; use parachains_common::{AssetIdForTrustBackedAssets, CollectionId, ItemId}; use sp_runtime::traits::TryConvertInto; -use xcm::latest::Location; +use xcm::prelude::*; use xcm_builder::{ AsPrefixedGeneralIndex, MatchedConvertedConcreteId, StartsWith, WithLatestLocationConverter, }; @@ -138,7 +138,6 @@ pub type PoolAssetsConvertedConcreteId = mod tests { use super::*; use sp_runtime::traits::MaybeEquivalence; - use xcm::prelude::*; use xcm_builder::{StartsWithExplicitGlobalConsensus, WithLatestLocationConverter}; use xcm_executor::traits::{Error as MatchError, MatchesFungibles}; diff --git a/cumulus/parachains/runtimes/testing/penpal/Cargo.toml b/cumulus/parachains/runtimes/testing/penpal/Cargo.toml index 1a2737f3aa22..094e7301c57d 100644 --- a/cumulus/parachains/runtimes/testing/penpal/Cargo.toml +++ b/cumulus/parachains/runtimes/testing/penpal/Cargo.toml @@ -42,6 +42,7 @@ pallet-transaction-payment = { workspace = true } pallet-transaction-payment-rpc-runtime-api = { workspace = true } pallet-asset-tx-payment = { workspace = true } pallet-assets = { workspace = true } +pallet-asset-conversion = { workspace = true } sp-api = { workspace = true } sp-block-builder = { workspace = true } sp-consensus-aura = { workspace = true } @@ -79,6 +80,8 @@ parachain-info = { workspace = true } parachains-common = { workspace = true } assets-common = { workspace = true } +primitive-types = { version = "0.12.1", default-features = false, features = ["codec", "num-traits", "scale-info"] } + [features] default = ["std"] std = [ @@ -99,6 +102,7 @@ std = [ "frame-system/std", "frame-try-runtime?/std", "log/std", + "pallet-asset-conversion/std", "pallet-asset-tx-payment/std", "pallet-assets/std", "pallet-aura/std", @@ -117,6 +121,7 @@ std = [ "polkadot-parachain-primitives/std", "polkadot-primitives/std", "polkadot-runtime-common/std", + "primitive-types/std", "scale-info/std", "sp-api/std", "sp-block-builder/std", @@ -149,6 +154,7 @@ runtime-benchmarks = [ "frame-system-benchmarking/runtime-benchmarks", "frame-system/runtime-benchmarks", "hex-literal", + "pallet-asset-conversion/runtime-benchmarks", "pallet-asset-tx-payment/runtime-benchmarks", "pallet-assets/runtime-benchmarks", "pallet-balances/runtime-benchmarks", @@ -176,6 +182,7 @@ try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", "frame-try-runtime/try-runtime", + "pallet-asset-conversion/try-runtime", "pallet-asset-tx-payment/try-runtime", "pallet-assets/try-runtime", "pallet-aura/try-runtime", diff --git a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs index bf39c02a3f59..7d19c0ed8d85 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs @@ -35,6 +35,10 @@ pub mod xcm_config; extern crate alloc; use alloc::{vec, vec::Vec}; +use assets_common::{ + local_and_foreign_assets::{LocalFromLeft, TargetFromLeft}, + AssetIdForTrustBackedAssetsConvert, +}; use codec::Encode; use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; @@ -42,10 +46,13 @@ use frame_support::{ construct_runtime, derive_impl, dispatch::DispatchClass, genesis_builder_helper::{build_state, get_preset}, + ord_parameter_types, pallet_prelude::Weight, parameter_types, traits::{ - AsEnsureOriginWithArg, ConstBool, ConstU32, ConstU64, ConstU8, Everything, TransformOrigin, + tokens::{fungible, fungibles, imbalance::ResolveAssetTo}, + AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, Everything, + TransformOrigin, }, weights::{ constants::WEIGHT_REF_TIME_PER_SECOND, ConstantMultiplier, FeePolynomial, WeightToFee as _, @@ -55,7 +62,7 @@ use frame_support::{ }; use frame_system::{ limits::{BlockLength, BlockWeights}, - EnsureRoot, EnsureSigned, + EnsureRoot, EnsureSigned, EnsureSignedBy, }; use parachains_common::{ impls::{AssetsToBlockAuthor, NonZeroIssuance}, @@ -67,7 +74,7 @@ pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, - traits::{AccountIdLookup, BlakeTwo256, Block as BlockT, Dispatchable}, + traits::{AccountIdConversion, AccountIdLookup, BlakeTwo256, Block as BlockT, Dispatchable}, transaction_validity::{TransactionSource, TransactionValidity}, ApplyExtrinsicResult, }; @@ -442,7 +449,9 @@ parameter_types! { // pub type AssetsForceOrigin = // EnsureOneOf, EnsureXcm>>; -impl pallet_assets::Config for Runtime { +pub type TrustBackedAssetsInstance = pallet_assets::Instance1; + +impl pallet_assets::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Balance = Balance; type AssetId = AssetId; @@ -500,6 +509,106 @@ impl pallet_assets::Config for Runtime { type BenchmarkHelper = xcm_config::XcmBenchmarkHelper; } +parameter_types! { + pub const AssetConversionPalletId: PalletId = PalletId(*b"py/ascon"); + pub const LiquidityWithdrawalFee: Permill = Permill::from_percent(0); +} + +ord_parameter_types! { + pub const AssetConversionOrigin: sp_runtime::AccountId32 = + AccountIdConversion::::into_account_truncating(&AssetConversionPalletId::get()); +} + +pub type AssetsForceOrigin = EnsureRoot; + +pub type PoolAssetsInstance = pallet_assets::Instance3; +impl pallet_assets::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type RemoveItemsLimit = ConstU32<1000>; + type AssetId = u32; + type AssetIdParameter = u32; + type Currency = Balances; + type CreateOrigin = + AsEnsureOriginWithArg>; + type ForceOrigin = AssetsForceOrigin; + type AssetDeposit = ConstU128<0>; + type AssetAccountDeposit = ConstU128<0>; + type MetadataDepositBase = ConstU128<0>; + type MetadataDepositPerByte = ConstU128<0>; + type ApprovalDeposit = ConstU128<0>; + type StringLimit = ConstU32<50>; + type Freezer = (); + type Extra = (); + type WeightInfo = pallet_assets::weights::SubstrateWeight; + type CallbackHandle = (); + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); +} + +/// Union fungibles implementation for `Assets` and `ForeignAssets`. +pub type LocalAndForeignAssets = fungibles::UnionOf< + Assets, + ForeignAssets, + LocalFromLeft< + AssetIdForTrustBackedAssetsConvert< + xcm_config::TrustBackedAssetsPalletLocation, + xcm::latest::Location, + >, + parachains_common::AssetIdForTrustBackedAssets, + xcm::latest::Location, + >, + xcm::latest::Location, + AccountId, +>; + +/// Union fungibles implementation for [`LocalAndForeignAssets`] and `Balances`. +pub type NativeAndAssets = fungible::UnionOf< + Balances, + LocalAndForeignAssets, + TargetFromLeft, + xcm::latest::Location, + AccountId, +>; + +pub type PoolIdToAccountId = pallet_asset_conversion::AccountIdConverter< + AssetConversionPalletId, + (xcm::latest::Location, xcm::latest::Location), +>; + +impl pallet_asset_conversion::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type HigherPrecisionBalance = sp_core::U256; + type AssetKind = xcm::latest::Location; + type Assets = NativeAndAssets; + type PoolId = (Self::AssetKind, Self::AssetKind); + type PoolLocator = pallet_asset_conversion::WithFirstAsset< + xcm_config::RelayLocation, + AccountId, + Self::AssetKind, + PoolIdToAccountId, + >; + type PoolAssetId = u32; + type PoolAssets = PoolAssets; + type PoolSetupFee = ConstU128<0>; // Asset class deposit fees are sufficient to prevent spam + type PoolSetupFeeAsset = xcm_config::RelayLocation; + type PoolSetupFeeTarget = ResolveAssetTo; + type LiquidityWithdrawalFee = LiquidityWithdrawalFee; + type LPFee = ConstU32<3>; + type PalletId = AssetConversionPalletId; + type MaxSwapPathLength = ConstU32<3>; + type MintMinLiquidity = ConstU128<100>; + type WeightInfo = (); + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = assets_common::benchmarks::AssetPairFactory< + xcm_config::RelayLocation, + parachain_info::Pallet, + xcm_config::TrustBackedAssetsPalletIndex, + xcm::latest::Location, + >; +} + parameter_types! { pub const ReservedXcmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4); pub const ReservedDmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4); @@ -642,9 +751,9 @@ impl pallet_asset_tx_payment::Config for Runtime { Balances, Runtime, ConvertInto, - pallet_assets::Instance1, + TrustBackedAssetsInstance, >, - AssetsToBlockAuthor, + AssetsToBlockAuthor, >; } @@ -685,6 +794,8 @@ construct_runtime!( // The main stage. Assets: pallet_assets:: = 50, ForeignAssets: pallet_assets:: = 51, + PoolAssets: pallet_assets:: = 52, + AssetConversion: pallet_asset_conversion = 53, Sudo: pallet_sudo = 255, } diff --git a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs index eca7c7bbc3c2..d202789017bf 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs @@ -24,15 +24,19 @@ //! soon. use super::{ AccountId, AllPalletsWithSystem, AssetId as AssetIdPalletAssets, Assets, Authorship, Balance, - Balances, ForeignAssets, ForeignAssetsInstance, NonZeroIssuance, ParachainInfo, - ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, WeightToFee, - XcmpQueue, + Balances, CollatorSelection, ForeignAssets, ForeignAssetsInstance, NonZeroIssuance, + ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, + WeightToFee, XcmpQueue, }; use crate::{BaseDeliveryFee, FeeAssetId, TransactionByteFee}; +use assets_common::TrustBackedAssetsAsLocation; use core::marker::PhantomData; use frame_support::{ parameter_types, - traits::{ConstU32, Contains, ContainsPair, Everything, EverythingBut, Get, Nothing}, + traits::{ + tokens::imbalance::ResolveAssetTo, ConstU32, Contains, ContainsPair, Everything, + EverythingBut, Get, Nothing, PalletInfoAccess, + }, weights::Weight, }; use frame_system::EnsureRoot; @@ -46,18 +50,18 @@ use xcm_builder::{ AccountId32Aliases, AllowHrmpNotificationsFromRelayChain, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AsPrefixedGeneralIndex, ConvertedConcreteId, EnsureXcmOrigin, FixedWeightBounds, FrameTransactionalProcessor, - FungibleAdapter, FungiblesAdapter, IsConcrete, LocalMint, NativeAsset, NoChecking, - ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SendXcmFeeToAccount, + FungibleAdapter, FungiblesAdapter, SingleAssetExchangeAdapter, IsConcrete, LocalMint, NativeAsset, + NoChecking, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, StartsWith, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, WithComputedOrigin, WithUniqueTopic, - XcmFeeManagerFromComponents, + XcmFeeManagerFromComponents, SendXcmFeeToAccount, }; use xcm_executor::{traits::JustTry, XcmExecutor}; parameter_types! { pub const RelayLocation: Location = Location::parent(); - // Local native currency which is stored in `pallet_balances`` + // Local native currency which is stored in `pallet_balances` pub const PenpalNativeCurrency: Location = Location::here(); // The Penpal runtime is utilized for testing with various environment setups. // This storage item allows us to customize the `NetworkId` where Penpal is deployed. @@ -70,6 +74,10 @@ parameter_types! { Parachain(ParachainInfo::parachain_id().into()) ].into(); pub TreasuryAccount: AccountId = TREASURY_PALLET_ID.into_account_truncating(); + pub StakingPot: AccountId = CollatorSelection::account_id(); + pub TrustBackedAssetsPalletIndex: u8 = ::index() as u8; + pub TrustBackedAssetsPalletLocation: Location = + PalletInstance(TrustBackedAssetsPalletIndex::get()).into(); } /// Type for specifying how a `Location` can be converted into an `AccountId`. This is used @@ -265,6 +273,8 @@ pub const TELEPORTABLE_ASSET_ID: u32 = 2; pub const ASSETS_PALLET_ID: u8 = 50; pub const ASSET_HUB_ID: u32 = 1000; +pub const USDT_ASSET_ID: u128 = 1984; + parameter_types! { /// The location that this chain recognizes as the Relay network's Asset Hub. pub SystemAssetHubLocation: Location = Location::new(1, [Parachain(ASSET_HUB_ID)]); @@ -282,6 +292,10 @@ parameter_types! { 1, [Parachain(ASSET_HUB_ID), PalletInstance(ASSETS_PALLET_ID), GeneralIndex(RESERVABLE_ASSET_ID.into())] ); + pub UsdtFromAssetHub: Location = Location::new( + 1, + [Parachain(ASSET_HUB_ID), PalletInstance(ASSETS_PALLET_ID), GeneralIndex(USDT_ASSET_ID)], + ); /// The Penpal runtime is utilized for testing with various environment setups. /// This storage item provides the opportunity to customize testing scenarios @@ -312,6 +326,28 @@ pub type TrustedReserves = ( pub type TrustedTeleporters = (AssetFromChain,); +/// `AssetId`/`Balance` converter for `TrustBackedAssets`. +pub type TrustBackedAssetsConvertedConcreteId = + assets_common::TrustBackedAssetsConvertedConcreteId; + +/// Asset converter for pool assets. +/// Used to convert assets in pools to the asset required for fee payment. +/// The pool must be between the first asset and the one required for fee payment. +/// This type allows paying fees with any asset in a pool with the asset required for fee payment. +pub type PoolAssetsExchanger = SingleAssetExchangeAdapter< + crate::AssetConversion, + crate::NativeAndAssets, + ( + TrustBackedAssetsAsLocation< + TrustBackedAssetsPalletLocation, + Balance, + xcm::latest::Location, + >, + ForeignAssetsConvertedConcreteId, + ), + AccountId, +>; + pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; @@ -327,18 +363,21 @@ impl xcm_executor::Config for XcmConfig { type Weigher = FixedWeightBounds; type Trader = ( UsingComponents>, - // This trader allows to pay with `is_sufficient=true` "Foreign" assets from dedicated - // `pallet_assets` instance - `ForeignAssets`. - cumulus_primitives_utility::TakeFirstAssetTrader< + cumulus_primitives_utility::SwapFirstAssetTrader< + RelayLocation, + crate::AssetConversion, + WeightToFee, + crate::NativeAndAssets, + ( + TrustBackedAssetsAsLocation< + TrustBackedAssetsPalletLocation, + Balance, + xcm::latest::Location, + >, + ForeignAssetsConvertedConcreteId, + ), + ResolveAssetTo, AccountId, - ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger, - ForeignAssetsConvertedConcreteId, - ForeignAssets, - cumulus_primitives_utility::XcmFeesTo32ByteAccount< - ForeignFungiblesTransactor, - AccountId, - XcmAssetFeesReceiver, - >, >, ); type ResponseHandler = PolkadotXcm; @@ -348,7 +387,7 @@ impl xcm_executor::Config for XcmConfig { type PalletInstancesInfo = AllPalletsWithSystem; type MaxAssetsIntoHolding = MaxAssetsIntoHolding; type AssetLocker = (); - type AssetExchanger = (); + type AssetExchanger = PoolAssetsExchanger; type FeeManager = XcmFeeManagerFromComponents< (), SendXcmFeeToAccount, diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index 1daf5ae750cf..5576544cf421 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -83,6 +83,9 @@ pub struct XcmExecutor { appendix_weight: Weight, transact_status: MaybeErrorCode, fees_mode: FeesMode, + /// Asset provided in last `BuyExecution` instruction (if any) in current XCM program. Same + /// asset type will be used for paying any potential delivery fees incurred by the program. + asset_used_for_fees: Option, _config: PhantomData, } @@ -269,7 +272,7 @@ impl ExecuteXcm for XcmExecutor XcmExecutor { appendix_weight: Weight::zero(), transact_status: Default::default(), fees_mode: FeesMode { jit_withdraw: false }, + asset_used_for_fees: None, _config: PhantomData, } } @@ -469,26 +473,95 @@ impl XcmExecutor { Ok(()) } - fn take_fee(&mut self, fee: Assets, reason: FeeReason) -> XcmResult { + fn take_fee(&mut self, fees: Assets, reason: FeeReason) -> XcmResult { if Config::FeeManager::is_waived(self.origin_ref(), reason.clone()) { return Ok(()) } tracing::trace!( target: "xcm::fees", - ?fee, + ?fees, origin_ref = ?self.origin_ref(), fees_mode = ?self.fees_mode, ?reason, "Taking fees", ); - let paid = if self.fees_mode.jit_withdraw { - let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; - for asset in fee.inner() { - Config::AssetTransactor::withdraw_asset(&asset, origin, Some(&self.context))?; + // We only ever use the first asset from `fees`. + let asset_needed_for_fees = match fees.get(0) { + Some(fee) => fee, + None => return Ok(()), // No delivery fees need to be paid. + }; + // If `BuyExecution` was called, we know we can try to use that asset for fees. + // We get the asset the user wants to use to pay for fees. + let asset_to_pay_for_fees = if let Some(asset_used_for_fees) = &self.asset_used_for_fees { + if asset_used_for_fees != &asset_needed_for_fees.id { + match Config::AssetExchanger::quote_exchange_price( + &(asset_used_for_fees.clone(), 1u128).into(), + &asset_needed_for_fees, + false, + ) { + Some(necessary_amount) => + (asset_used_for_fees.clone(), necessary_amount).into(), + // If we can't convert, then we return the original asset. + // It will error later in any case. + None => { + tracing::trace!( + target: "xcm::take_fee", + ?asset_used_for_fees, + "Could not convert fees", + ); + asset_needed_for_fees.clone() + }, + } + } else { + asset_needed_for_fees.clone() } - fee } else { - self.holding.try_take(fee.into()).map_err(|_| XcmError::NotHoldingFees)?.into() + asset_needed_for_fees.clone() + }; + tracing::trace!(target: "xcm::fees", ?asset_to_pay_for_fees); + // We withdraw or take from holding the asset the user wants to use for fee payment. + let withdrawn_fee_asset = if self.fees_mode.jit_withdraw { + let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; + Config::AssetTransactor::withdraw_asset( + &asset_to_pay_for_fees, + origin, + Some(&self.context), + )?; + tracing::trace!(target: "xcm::fees", ?asset_needed_for_fees); + asset_to_pay_for_fees.clone().into() + } else { + let assets_taken_from_holding_to_pay_delivery_fees = self + .holding + .try_take(asset_to_pay_for_fees.clone().into()) + .map_err(|_| XcmError::NotHoldingFees)?; + tracing::trace!(target: "xcm::fees", ?assets_taken_from_holding_to_pay_delivery_fees); + let mut iter = assets_taken_from_holding_to_pay_delivery_fees.fungible_assets_iter(); + let asset = iter.next().ok_or(XcmError::NotHoldingFees)?; + asset.into() + }; + // We perform the swap if we need to to pay fees in the correct asset. + let paid = if asset_to_pay_for_fees.id != asset_needed_for_fees.id { + let swapped_asset: Assets = Config::AssetExchanger::exchange_asset( + self.origin_ref(), + withdrawn_fee_asset, + &asset_needed_for_fees.clone().into(), + false, + ) + .map_err(|given_assets| { + tracing::error!( + target: "xcm::fees", + ?given_assets, + "Swap was deemed necessary but couldn't be done", + ); + XcmError::FeesNotMet + })? + .into(); + swapped_asset + } else { + // If the asset wanted to pay for fees is the one that was needed, + // we don't need to do any swap. + // We just use the assets withdrawn or taken from holding. + withdrawn_fee_asset.into() }; Config::FeeManager::handle_fee(paid, Some(&self.context), reason); Ok(()) @@ -885,11 +958,48 @@ impl XcmExecutor { message_to_weigh.extend(xcm.0.clone().into_iter()); let (_, fee) = validate_send::(dest.clone(), Xcm(message_to_weigh))?; - // set aside fee to be charged by XcmSender - let transport_fee = self.holding.saturating_take(fee.into()); - + let maybe_delivery_fee = if let Some(asset_needed_for_fees) = fee.get(0) { + tracing::trace!( + target: "xcm::DepositReserveAsset", + "Asset provided to pay for fees {:?}, asset required for transport fees: {:?}", + self.asset_used_for_fees, asset_needed_for_fees, + ); + let asset_to_pay_for_fees = + self.asset_used_for_fees.as_ref().unwrap_or(&asset_needed_for_fees.id); + let actual_asset_to_use_for_fees = + if asset_to_pay_for_fees != &asset_needed_for_fees.id { + // Get the correct amount of asset_to_pay_for_fees. + match Config::AssetExchanger::quote_exchange_price( + &(asset_to_pay_for_fees.clone(), 1u128).into(), + &asset_needed_for_fees, + false, + ) { + Some(necessary_amount) => + (asset_to_pay_for_fees.clone(), necessary_amount).into(), + None => { + tracing::trace!( + target: "xcm::take_fee", + ?asset_to_pay_for_fees, + "Could not convert fees", + ); + asset_needed_for_fees.clone() + }, + } + } else { + asset_needed_for_fees.clone() + }; + // set aside fee to be charged by XcmSender + let delivery_fee = + self.holding.saturating_take(actual_asset_to_use_for_fees.into()); + tracing::trace!(target: "xcm::DepositReserveAsset", ?delivery_fee); + Some(delivery_fee) + } else { + None + }; // now take assets to deposit (excluding transport_fee) + // TODO: We should be taking `assets - transport_fee` let deposited = self.holding.saturating_take(assets); + tracing::trace!(target: "xcm::DepositReserveAsset", ?deposited, "Assets except delivery fee"); for asset in deposited.assets_iter() { Config::AssetTransactor::deposit_asset(&asset, &dest, Some(&self.context))?; } @@ -899,8 +1009,10 @@ impl XcmExecutor { let assets = Self::reanchored(deposited, &dest, None); let mut message = vec![ReserveAssetDeposited(assets), ClearOrigin]; message.extend(xcm.0.into_iter()); - // put back transport_fee in holding register to be charged by XcmSender - self.holding.subsume_assets(transport_fee); + // put back delivery_fee in holding register to be charged by XcmSender + if let Some(delivery_fee) = maybe_delivery_fee { + self.holding.subsume_assets(delivery_fee); + } self.send(dest, Xcm(message), FeeReason::DepositReserveAsset)?; Ok(()) }); @@ -978,6 +1090,10 @@ impl XcmExecutor { // should be executed. let Some(weight) = Option::::from(weight_limit) else { return Ok(()) }; let old_holding = self.holding.clone(); + // Save the asset being used for execution fees, so we later know what should be + // used for delivery fees. + self.asset_used_for_fees = Some(fees.id.clone()); + tracing::trace!(target: "xcm::executor::BuyExecution", asset_used_for_fees = ?self.asset_used_for_fees); // pay for `weight` using up to `fees` of the holding register. let max_fee = self.holding.try_take(fees.into()).map_err(|_| XcmError::NotHoldingFees)?; From 81cc5bf1808a104a89ad67cda57fb858b55976e7 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Fri, 26 Jul 2024 11:58:23 +0200 Subject: [PATCH 03/19] doc: add prdoc --- prdoc/pr_5131.prdoc | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 prdoc/pr_5131.prdoc diff --git a/prdoc/pr_5131.prdoc b/prdoc/pr_5131.prdoc new file mode 100644 index 000000000000..b3096fa07de7 --- /dev/null +++ b/prdoc/pr_5131.prdoc @@ -0,0 +1,25 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Swap for paying delivery fees in different assets + +doc: + - audience: Runtime User + description: | + If the `AssetExchanger` is configured on a runtime, the XCM executor is now able to swap assets + to pay for delivery fees. + This was already possible for execution fees. + A runtime where this will be possible is Asset Hub. + That means reserve asset transfers from Parachain A to Parachain B passing through Asset Hub no + longer need to have any DOT. + They can have any asset in a pool with DOT on Asset Hub, for example USDT or USDC. + - audience: Runtime Dev + description: | + Using the `AssetExchanger` XCM config item, the executor now swaps fees to use for delivery fees, + if possible. + If you want your runtime to support this, you need to configure this new item. + Thankfully, xcm-builder now has a new adapter for this, letting you use pallet-asset-conversion for it. + It's called `SingleAssetExchangeAdapter`, read more about it in its rust docs. + This item is already configured in Asset Hub. + +crates: [ ] From f7a11c3b44492cb8988f3005ee0dfe24afe8f709 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Fri, 26 Jul 2024 22:17:18 +0200 Subject: [PATCH 04/19] chore(xcm-executor): refactor finding asset to pay delivery fees --- .../assets/asset-hub-rococo/src/xcm_config.rs | 15 +-- .../asset-hub-westend/src/xcm_config.rs | 14 +-- .../runtimes/testing/penpal/src/xcm_config.rs | 10 +- polkadot/xcm/xcm-executor/src/lib.rs | 92 ++++++++----------- 4 files changed, 61 insertions(+), 70 deletions(-) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs index 8c8e991eb850..a2882f54bd06 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs @@ -54,13 +54,13 @@ use xcm_builder::{ DenyReserveTransferToRelayChain, DenyThenTry, DescribeAllTerminal, DescribeFamily, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, GlobalConsensusParachainConvertsFor, HashedDescription, IsConcrete, LocalMint, - NetworkExportTableItem, NoChecking, NonFungiblesAdapter, ParentAsSuperuser, ParentIsPreset, - RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignPaidRemoteExporter, + MatchedConvertedConcreteId, NetworkExportTableItem, NoChecking, NonFungiblesAdapter, + ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SendXcmFeeToAccount, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SingleAssetExchangeAdapter, SovereignPaidRemoteExporter, SovereignSignedViaLocation, StartsWith, StartsWithExplicitGlobalConsensus, TakeWeightCredit, - TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, - WithLatestLocationConverter, XcmFeeManagerFromComponents, SendXcmFeeToAccount, SingleAssetExchangeAdapter, - MatchedConvertedConcreteId, + TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, + WithLatestLocationConverter, WithUniqueTopic, XcmFeeManagerFromComponents, }; use xcm_executor::XcmExecutor; @@ -340,13 +340,14 @@ pub type PoolAssetsExchanger = SingleAssetExchangeAdapter< ( TrustBackedAssetsAsLocation, ForeignAssetsConvertedConcreteId, + // `ForeignAssetsConvertedConcreteId` excludes the relay token, so we add it back here. MatchedConvertedConcreteId< xcm::v3::Location, Balance, Equals, WithLatestLocationConverter, TryConvertInto, - >, // Adding this back in to match on relay token. + >, ), AccountId, >; diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs index 34ff951746a7..6f2acfdaf6bb 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs @@ -50,12 +50,13 @@ use xcm_builder::{ DenyReserveTransferToRelayChain, DenyThenTry, DescribeFamily, DescribePalletTerminal, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, GlobalConsensusParachainConvertsFor, HashedDescription, IsConcrete, LocalMint, - NetworkExportTableItem, NoChecking, NonFungiblesAdapter, ParentAsSuperuser, ParentIsPreset, - RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, StartsWith, + MatchedConvertedConcreteId, NetworkExportTableItem, NoChecking, NonFungiblesAdapter, + ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SendXcmFeeToAccount, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SingleAssetExchangeAdapter, SovereignSignedViaLocation, StartsWith, StartsWithExplicitGlobalConsensus, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, - WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, - WithLatestLocationConverter, SendXcmFeeToAccount, SingleAssetExchangeAdapter, MatchedConvertedConcreteId, + WeightInfoBounds, WithComputedOrigin, WithLatestLocationConverter, WithUniqueTopic, + XcmFeeManagerFromComponents, }; use xcm_executor::XcmExecutor; @@ -359,13 +360,14 @@ pub type PoolAssetsExchanger = SingleAssetExchangeAdapter< ( TrustBackedAssetsAsLocation, ForeignAssetsConvertedConcreteId, + // `ForeignAssetsConvertedConcreteId` excludes the relay token, so we add it back here. MatchedConvertedConcreteId< xcm::v3::Location, Balance, Equals, WithLatestLocationConverter, TryConvertInto, - >, // Adding this back in to match on relay token. + >, ), AccountId, >; diff --git a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs index d202789017bf..99aadb33b840 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs @@ -50,12 +50,12 @@ use xcm_builder::{ AccountId32Aliases, AllowHrmpNotificationsFromRelayChain, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AsPrefixedGeneralIndex, ConvertedConcreteId, EnsureXcmOrigin, FixedWeightBounds, FrameTransactionalProcessor, - FungibleAdapter, FungiblesAdapter, SingleAssetExchangeAdapter, IsConcrete, LocalMint, NativeAsset, - NoChecking, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, + FungibleAdapter, FungiblesAdapter, IsConcrete, LocalMint, NativeAsset, NoChecking, + ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SendXcmFeeToAccount, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, StartsWith, TakeWeightCredit, - TrailingSetTopicAsId, UsingComponents, WithComputedOrigin, WithUniqueTopic, - XcmFeeManagerFromComponents, SendXcmFeeToAccount, + SignedToAccountId32, SingleAssetExchangeAdapter, SovereignSignedViaLocation, StartsWith, + TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, WithComputedOrigin, WithUniqueTopic, + XcmFeeManagerFromComponents, }; use xcm_executor::{traits::JustTry, XcmExecutor}; diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index 5576544cf421..fbc436aa2472 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -492,32 +492,8 @@ impl XcmExecutor { }; // If `BuyExecution` was called, we know we can try to use that asset for fees. // We get the asset the user wants to use to pay for fees. - let asset_to_pay_for_fees = if let Some(asset_used_for_fees) = &self.asset_used_for_fees { - if asset_used_for_fees != &asset_needed_for_fees.id { - match Config::AssetExchanger::quote_exchange_price( - &(asset_used_for_fees.clone(), 1u128).into(), - &asset_needed_for_fees, - false, - ) { - Some(necessary_amount) => - (asset_used_for_fees.clone(), necessary_amount).into(), - // If we can't convert, then we return the original asset. - // It will error later in any case. - None => { - tracing::trace!( - target: "xcm::take_fee", - ?asset_used_for_fees, - "Could not convert fees", - ); - asset_needed_for_fees.clone() - }, - } - } else { - asset_needed_for_fees.clone() - } - } else { - asset_needed_for_fees.clone() - }; + let asset_to_pay_for_fees = + self.calculate_asset_for_delivery_fees(asset_needed_for_fees.clone()); tracing::trace!(target: "xcm::fees", ?asset_to_pay_for_fees); // We withdraw or take from holding the asset the user wants to use for fee payment. let withdrawn_fee_asset = if self.fees_mode.jit_withdraw { @@ -567,6 +543,40 @@ impl XcmExecutor { Ok(()) } + /// Calculates the amount of `self.asset_used_for_fees` required to swap for + /// `asset_needed_for_fees`. + /// + /// The calculation is done by `Config::AssetExchanger`. + /// If `self.asset_used_for_fees` is not set, it will just return `asset_needed_for_fees`. + fn calculate_asset_for_delivery_fees(&self, asset_needed_for_fees: Asset) -> Asset { + if let Some(asset_wanted_for_fees) = &self.asset_used_for_fees { + if *asset_wanted_for_fees != asset_needed_for_fees.id { + match Config::AssetExchanger::quote_exchange_price( + &(asset_wanted_for_fees.clone(), 1u128).into(), + &asset_needed_for_fees, + false, // Minimal. + ) { + Some(necessary_amount) => + (asset_wanted_for_fees.clone(), necessary_amount).into(), + // If we can't convert, then we return the original asset. + // It will error later in any case. + None => { + tracing::trace!( + target: "xcm::take_fee", + ?asset_wanted_for_fees, + "Could not convert fees", + ); + asset_needed_for_fees.clone() + }, + } + } else { + asset_needed_for_fees + } + } else { + asset_needed_for_fees + } + } + /// Calculates what `local_querier` would be from the perspective of `destination`. fn to_querier( local_querier: Option, @@ -965,39 +975,17 @@ impl XcmExecutor { self.asset_used_for_fees, asset_needed_for_fees, ); let asset_to_pay_for_fees = - self.asset_used_for_fees.as_ref().unwrap_or(&asset_needed_for_fees.id); - let actual_asset_to_use_for_fees = - if asset_to_pay_for_fees != &asset_needed_for_fees.id { - // Get the correct amount of asset_to_pay_for_fees. - match Config::AssetExchanger::quote_exchange_price( - &(asset_to_pay_for_fees.clone(), 1u128).into(), - &asset_needed_for_fees, - false, - ) { - Some(necessary_amount) => - (asset_to_pay_for_fees.clone(), necessary_amount).into(), - None => { - tracing::trace!( - target: "xcm::take_fee", - ?asset_to_pay_for_fees, - "Could not convert fees", - ); - asset_needed_for_fees.clone() - }, - } - } else { - asset_needed_for_fees.clone() - }; + self.calculate_asset_for_delivery_fees(asset_needed_for_fees.clone()); // set aside fee to be charged by XcmSender let delivery_fee = - self.holding.saturating_take(actual_asset_to_use_for_fees.into()); + self.holding.saturating_take(asset_to_pay_for_fees.into()); tracing::trace!(target: "xcm::DepositReserveAsset", ?delivery_fee); Some(delivery_fee) } else { None }; - // now take assets to deposit (excluding transport_fee) - // TODO: We should be taking `assets - transport_fee` + // now take assets to deposit (excluding delivery_fee) + // TODO: We should be taking `assets - delivery_fee` let deposited = self.holding.saturating_take(assets); tracing::trace!(target: "xcm::DepositReserveAsset", ?deposited, "Assets except delivery fee"); for asset in deposited.assets_iter() { From 6ba0a325a40410286209f98bacb1c5010001ec38 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Fri, 26 Jul 2024 22:29:50 +0200 Subject: [PATCH 05/19] chore(xcm-executor): remove TODO --- polkadot/xcm/xcm-executor/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index fbc436aa2472..bf6ff3671880 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -984,8 +984,7 @@ impl XcmExecutor { } else { None }; - // now take assets to deposit (excluding delivery_fee) - // TODO: We should be taking `assets - delivery_fee` + // now take assets to deposit (after having taken delivery fees) let deposited = self.holding.saturating_take(assets); tracing::trace!(target: "xcm::DepositReserveAsset", ?deposited, "Assets except delivery fee"); for asset in deposited.assets_iter() { From 8c1119eae03fc7e2ba27de7e376a9cee896e7b91 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Fri, 26 Jul 2024 23:01:12 +0200 Subject: [PATCH 06/19] chore(asset-hub-westend-integration-tests): make asset USDT --- .../src/tests/reserve_transfer.rs | 80 +++++++++---------- 1 file changed, 39 insertions(+), 41 deletions(-) diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs index 1ac627167da5..3c254447c52d 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs @@ -1152,12 +1152,12 @@ fn reserve_transfer_native_asset_from_para_to_para_through_relay() { assert!(receiver_assets_after < receiver_assets_before + amount_to_send); } -// ========================================================================================= -// ==== Reserve Transfers - TrustBacked Asset pay fees using pool - AssetHub->Parachain ==== -// ========================================================================================= +// ===================================================================================================== +// ==== Reserve Transfers - TrustBacked Asset pay fees with USDT (using pool) - AssetHub->Parachain ==== +// ===================================================================================================== #[test] fn reserve_transfer_pool_assets_from_system_para_to_para() { - let asset_id = 9999u32; + let usdt_id = 1984u32; let penpal_location = AssetHubWestend::sibling_location_of(PenpalA::para_id()); let penpal_sov_account = AssetHubWestend::sovereign_account_id_of(penpal_location.clone()); @@ -1165,38 +1165,41 @@ fn reserve_transfer_pool_assets_from_system_para_to_para() { // This ED isn't reflected in any derivative in a PenpalA account. AssetHubWestend::fund_accounts(vec![(penpal_sov_account.clone().into(), ASSET_HUB_WESTEND_ED)]); - AssetHubWestend::force_create_asset( - asset_id.into(), - AssetHubWestendSender::get().into(), - false, - ASSET_MIN_BALANCE, - vec![(AssetHubWestendSender::get(), 10_000_000_000_000)], - ); + let sender = AssetHubWestendSender::get(); + let receiver = PenpalAReceiver::get(); + let asset_amount_to_send = 1_000_000_000_000; + + AssetHubWestend::execute_with(|| { + use frame_support::traits::tokens::fungibles::Mutate; + type Assets = ::Assets; + assert_ok!(>::mint_into( + usdt_id.into(), + &AssetHubWestendSender::get(), + asset_amount_to_send + 10_000_000_000_000, // Make sure it has enough. + )); + }); let relay_asset_penpal_pov = RelayLocation::get(); - let custom_asset_penpal_pov = Location::new( - 1, - [Parachain(1000), PalletInstance(ASSETS_PALLET_ID), GeneralIndex(asset_id.into())], - ); - PenpalA::force_create_foreign_asset( - custom_asset_penpal_pov.clone(), - PenpalAssetOwner::get(), - true, - 1_000_000, - // We give it some funds to be able to add liquidity later. - vec![(PenpalASender::get(), 10_000_000_000_000)], - ); - // Setup the pool between `relay_asset_penpal_pov` and `custom_asset_penpal_pov` on PenpalA. + let usdt_from_asset_hub = PenpalUsdtFromAssetHub::get(); + + // Setup the pool between `relay_asset_penpal_pov` and `usdt_from_asset_hub` on PenpalA. // So we can swap the custom asset that comes from AssetHubWestend for native asset to pay for // fees. PenpalA::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; + assert_ok!(::ForeignAssets::mint( + ::RuntimeOrigin::signed(PenpalAssetOwner::get()), + usdt_from_asset_hub.clone().into(), + PenpalASender::get().into(), + 10_000_000_000_000, // For it to have more than enough. + )); + assert_ok!(::AssetConversion::create_pool( ::RuntimeOrigin::signed(PenpalASender::get()), Box::new(relay_asset_penpal_pov.clone()), - Box::new(custom_asset_penpal_pov.clone()), + Box::new(usdt_from_asset_hub.clone()), )); assert_expected_events!( @@ -1209,8 +1212,8 @@ fn reserve_transfer_pool_assets_from_system_para_to_para() { assert_ok!(::AssetConversion::add_liquidity( ::RuntimeOrigin::signed(PenpalASender::get()), Box::new(relay_asset_penpal_pov), - Box::new(custom_asset_penpal_pov.clone()), - // `custom_asset_penpal_pov` is worth a third of `relay_asset_penpal_pov` + Box::new(usdt_from_asset_hub.clone()), + // `usdt_from_asset_hub` is worth a third of `relay_asset_penpal_pov` 1_000_000_000_000, 3_000_000_000_000, 0, @@ -1226,15 +1229,10 @@ fn reserve_transfer_pool_assets_from_system_para_to_para() { ); }); - let sender = AssetHubWestendSender::get(); - let receiver = PenpalAReceiver::get(); - let asset_amount_to_send = 1_000_000_000_000; let assets: Assets = vec![( - [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(asset_id.into())], - asset_amount_to_send, - ) - .into()] - .into(); + [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(usdt_id.into())], + asset_amount_to_send + ).into()].into(); let test_args = TestContext { sender: sender.clone(), @@ -1252,7 +1250,7 @@ fn reserve_transfer_pool_assets_from_system_para_to_para() { let sender_initial_balance = AssetHubWestend::execute_with(|| { type Assets = ::Assets; - >::balance(asset_id, &sender) + >::balance(usdt_id, &sender) }); let sender_initial_native_balance = AssetHubWestend::execute_with(|| { type Balances = ::Balances; @@ -1260,7 +1258,7 @@ fn reserve_transfer_pool_assets_from_system_para_to_para() { }); let receiver_initial_balance = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(custom_asset_penpal_pov.clone(), &receiver) + >::balance(usdt_from_asset_hub.clone(), &receiver) }); test.set_assertion::(system_para_to_para_receiver_assertions); @@ -1269,7 +1267,7 @@ fn reserve_transfer_pool_assets_from_system_para_to_para() { let sender_after_balance = AssetHubWestend::execute_with(|| { type Assets = ::Assets; - >::balance(asset_id, &sender) + >::balance(usdt_id, &sender) }); let sender_after_native_balance = AssetHubWestend::execute_with(|| { type Balances = ::Balances; @@ -1277,10 +1275,10 @@ fn reserve_transfer_pool_assets_from_system_para_to_para() { }); let receiver_after_balance = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(custom_asset_penpal_pov, &receiver) + >::balance(usdt_from_asset_hub, &receiver) }); - // TODO: When we allow payment with different assets locally, this should be the same, since + // TODO(https://github.com/paritytech/polkadot-sdk/issues/5160): When we allow payment with different assets locally, this should be the same, since // they aren't used for fees. assert!(sender_after_native_balance < sender_initial_native_balance); // Sender account's balance decreases. @@ -1445,7 +1443,7 @@ fn reserve_transfer_usdt_from_para_to_para_through_asset_hub() { assert_eq!(assets.len(), 1); // Give the sender enough Relay tokens to pay for local delivery fees. - // TODO: When we support local delivery fee payment in other assets, we don't need this. + // TODO(https://github.com/paritytech/polkadot-sdk/issues/5160): When we support local delivery fee payment in other assets, we don't need this. PenpalA::mint_foreign_asset( ::RuntimeOrigin::signed(PenpalAssetOwner::get()), RelayLocation::get(), From 00950b69f404c16527458c93f71973a1dca24e48 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Mon, 29 Jul 2024 10:40:58 +0200 Subject: [PATCH 07/19] chore(asset-hub-integration-tests): assertions --- .../src/tests/reserve_transfer.rs | 66 +++++++++++++++++-- 1 file changed, 61 insertions(+), 5 deletions(-) diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs index 3c254447c52d..ebde459fe3d4 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs @@ -60,10 +60,10 @@ pub fn system_para_to_para_sender_assertions(t: SystemParaToParaTest) { AssetHubWestend::assert_xcm_pallet_attempted_complete(None); let sov_acc_of_dest = AssetHubWestend::sovereign_account_id_of(t.args.dest.clone()); - for (idx, asset) in t.args.assets.into_inner().into_iter().enumerate() { + for asset in t.args.assets.into_inner().into_iter() { let expected_id = asset.id.0.clone().try_into().unwrap(); let asset_amount = if let Fungible(a) = asset.fun { Some(a) } else { None }.unwrap(); - if idx == t.args.fee_asset_item as usize { + if asset.id == AssetId(Location::new(1, [])) { assert_expected_events!( AssetHubWestend, vec![ @@ -77,6 +77,23 @@ pub fn system_para_to_para_sender_assertions(t: SystemParaToParaTest) { }, ] ); + } else if matches!( + asset.id.0.unpack(), + (0, [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(_)]) + ) { + assert_expected_events!( + AssetHubWestend, + vec![ + // Amount of foreign asset is transferred to Parachain's Sovereign account + RuntimeEvent::Assets( + pallet_assets::Event::Transferred { from, to, amount, .. }, + ) => { + from: *from == t.sender.account_id, + to: *to == sov_acc_of_dest, + amount: *amount == asset_amount, + }, + ] + ); } else { assert_expected_events!( AssetHubWestend, @@ -418,6 +435,38 @@ fn para_to_para_relay_hop_assertions(t: ParaToParaThroughRelayTest) { ); } +fn para_to_para_asset_hub_hop_assertions(t: ParaToParaThroughAHTest) { + type RuntimeEvent = ::RuntimeEvent; + let sov_penpal_a_on_ah = AssetHubWestend::sovereign_account_id_of( + AssetHubWestend::sibling_location_of(PenpalA::para_id()), + ); + let sov_penpal_b_on_ah = AssetHubWestend::sovereign_account_id_of( + AssetHubWestend::sibling_location_of(PenpalB::para_id()), + ); + + assert_expected_events!( + AssetHubWestend, + vec![ + // Withdrawn from sender parachain SA + RuntimeEvent::Assets( + pallet_assets::Event::Burned { owner, balance, .. } + ) => { + owner: *owner == sov_penpal_a_on_ah, + balance: *balance == t.args.amount, + }, + // Deposited to receiver parachain SA + RuntimeEvent::Assets( + pallet_assets::Event::Deposited { who, .. } + ) => { + who: *who == sov_penpal_b_on_ah, + }, + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); +} + pub fn para_to_para_through_hop_receiver_assertions(t: Test) { type RuntimeEvent = ::RuntimeEvent; @@ -1153,7 +1202,8 @@ fn reserve_transfer_native_asset_from_para_to_para_through_relay() { } // ===================================================================================================== -// ==== Reserve Transfers - TrustBacked Asset pay fees with USDT (using pool) - AssetHub->Parachain ==== +// ==== Reserve Transfers - TrustBacked Asset pay fees with USDT (using pool) - AssetHub->Parachain +// ==== // ===================================================================================================== #[test] fn reserve_transfer_pool_assets_from_system_para_to_para() { @@ -1231,8 +1281,10 @@ fn reserve_transfer_pool_assets_from_system_para_to_para() { let assets: Assets = vec![( [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(usdt_id.into())], - asset_amount_to_send - ).into()].into(); + asset_amount_to_send, + ) + .into()] + .into(); let test_args = TestContext { sender: sender.clone(), @@ -1261,6 +1313,7 @@ fn reserve_transfer_pool_assets_from_system_para_to_para() { >::balance(usdt_from_asset_hub.clone(), &receiver) }); + test.set_assertion::(system_para_to_para_sender_assertions); test.set_assertion::(system_para_to_para_receiver_assertions); test.set_dispatchable::(system_para_to_para_reserve_transfer_assets); test.assert(); @@ -1479,6 +1532,9 @@ fn reserve_transfer_usdt_from_para_to_para_through_asset_hub() { type ForeignAssets = ::ForeignAssets; >::balance(usdt_from_asset_hub.clone(), &receiver) }); + test.set_assertion::(para_to_para_through_hop_sender_assertions); + test.set_assertion::(para_to_para_asset_hub_hop_assertions); + test.set_assertion::(para_to_para_through_hop_receiver_assertions); test.set_dispatchable::( para_to_para_through_asset_hub_limited_reserve_transfer_assets, ); From 618dfc8267980abdd3eda29c8134bfa8834b40d3 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Mon, 29 Jul 2024 10:42:54 +0200 Subject: [PATCH 08/19] fix: fmt --- .../asset-hub-westend/src/tests/reserve_transfer.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs index ebde459fe3d4..7127e6da3338 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs @@ -1201,10 +1201,9 @@ fn reserve_transfer_native_asset_from_para_to_para_through_relay() { assert!(receiver_assets_after < receiver_assets_before + amount_to_send); } -// ===================================================================================================== -// ==== Reserve Transfers - TrustBacked Asset pay fees with USDT (using pool) - AssetHub->Parachain -// ==== -// ===================================================================================================== +// ============================================================================ +// ==== Reserve Transfers - USDT pay fees using pool - AssetHub->Parachain ==== +// ============================================================================ #[test] fn reserve_transfer_pool_assets_from_system_para_to_para() { let usdt_id = 1984u32; @@ -1341,9 +1340,9 @@ fn reserve_transfer_pool_assets_from_system_para_to_para() { assert!(receiver_after_balance < receiver_initial_balance + asset_amount_to_send); } -// =============================================================================================== -// == Reserve Transfers USDT - Parachain->AssetHub->Parachain - pay fees with USDT (using pool) == -// =============================================================================================== +// =================================================================================== +// == Reserve Transfers USDT - Parachain->AssetHub->Parachain - pay fees using pool == +// =================================================================================== // // Transfer USDT From Penpal A to Penpal B with AssetHub as the reserve, while paying fees using // USDT by making use of existing USDT pools on AssetHub and destination. From 82aaade0508e6dfa8e972588703a070062069e6f Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Mon, 29 Jul 2024 11:36:27 +0200 Subject: [PATCH 09/19] feat(asset-hub-rococo-integration-tests): add same tests as westend --- .../tests/assets/asset-hub-rococo/src/lib.rs | 1 + .../src/tests/reserve_transfer.rs | 419 +++++++++++++++++- .../src/tests/reserve_transfer.rs | 2 +- 3 files changed, 419 insertions(+), 3 deletions(-) diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs index eca358317054..d8faaf6c99cf 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs @@ -62,6 +62,7 @@ mod imports { CustomizableAssetFromSystemAssetHub as PenpalCustomizableAssetFromSystemAssetHub, LocalReservableFromAssetHub as PenpalLocalReservableFromAssetHub, LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub, + UsdtFromAssetHub as PenpalUsdtFromAssetHub, }, PenpalAParaPallet as PenpalAPallet, PenpalAssetOwner, PenpalBParaPallet as PenpalBPallet, ED as PENPAL_ED, diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs index 8b9fedcd4947..0a5b192cd094 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs @@ -60,10 +60,10 @@ pub fn system_para_to_para_sender_assertions(t: SystemParaToParaTest) { AssetHubRococo::assert_xcm_pallet_attempted_complete(None); let sov_acc_of_dest = AssetHubRococo::sovereign_account_id_of(t.args.dest.clone()); - for (idx, asset) in t.args.assets.into_inner().into_iter().enumerate() { + for asset in t.args.assets.into_inner().into_iter() { let expected_id = asset.id.0.clone().try_into().unwrap(); let asset_amount = if let Fungible(a) = asset.fun { Some(a) } else { None }.unwrap(); - if idx == t.args.fee_asset_item as usize { + if asset.id == AssetId(Location::new(1, [])) { assert_expected_events!( AssetHubRococo, vec![ @@ -77,6 +77,23 @@ pub fn system_para_to_para_sender_assertions(t: SystemParaToParaTest) { }, ] ); + } else if matches!( + asset.id.0.unpack(), + (0, [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(_)]) + ) { + assert_expected_events!( + AssetHubRococo, + vec![ + // Amount of trust-backed asset is transferred to Parachain's Sovereign account + RuntimeEvent::Assets( + pallet_assets::Event::Transferred { from, to, amount, .. }, + ) => { + from: *from == t.sender.account_id, + to: *to == sov_acc_of_dest, + amount: *amount == asset_amount, + }, + ] + ); } else { assert_expected_events!( AssetHubRococo, @@ -388,6 +405,38 @@ pub fn para_to_para_through_hop_sender_assertions(t: Test::RuntimeEvent; + let sov_penpal_a_on_ah = AssetHubRococo::sovereign_account_id_of( + AssetHubRococo::sibling_location_of(PenpalA::para_id()), + ); + let sov_penpal_b_on_ah = AssetHubRococo::sovereign_account_id_of( + AssetHubRococo::sibling_location_of(PenpalB::para_id()), + ); + + assert_expected_events!( + AssetHubRococo, + vec![ + // Withdrawn from sender parachain SA + RuntimeEvent::Assets( + pallet_assets::Event::Burned { owner, balance, .. } + ) => { + owner: *owner == sov_penpal_a_on_ah, + balance: *balance == t.args.amount, + }, + // Deposited to receiver parachain SA + RuntimeEvent::Assets( + pallet_assets::Event::Deposited { who, .. } + ) => { + who: *who == sov_penpal_b_on_ah, + }, + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); +} + fn para_to_para_relay_hop_assertions(t: ParaToParaThroughRelayTest) { type RuntimeEvent = ::RuntimeEvent; let sov_penpal_a_on_rococo = @@ -469,6 +518,19 @@ fn system_para_to_para_reserve_transfer_assets(t: SystemParaToParaTest) -> Dispa ) } +fn para_to_para_through_asset_hub_limited_reserve_transfer_assets( + t: ParaToParaThroughAHTest, +) -> DispatchResult { + ::PolkadotXcm::limited_reserve_transfer_assets( + t.signed_origin, + bx!(t.args.dest.into()), + bx!(t.args.beneficiary.into()), + bx!(t.args.assets.into()), + t.args.fee_asset_item, + t.args.weight_limit, + ) +} + fn para_to_system_para_reserve_transfer_assets(t: ParaToSystemParaTest) -> DispatchResult { ::PolkadotXcm::limited_reserve_transfer_assets( t.signed_origin, @@ -1135,3 +1197,356 @@ fn reserve_transfer_native_asset_from_para_to_para_through_relay() { // Receiver's balance is increased assert!(receiver_assets_after > receiver_assets_before); } + +// ============================================================================ +// ==== Reserve Transfers - USDT pay fees using pool - AssetHub->Parachain ==== +// ============================================================================ +#[test] +fn reserve_transfer_pool_assets_from_system_para_to_para() { + let usdt_id = 1984u32; + let penpal_location = AssetHubRococo::sibling_location_of(PenpalA::para_id()); + let penpal_sov_account = AssetHubRococo::sovereign_account_id_of(penpal_location.clone()); + + // Create SA-of-Penpal-on-AHW with ED. + // This ED isn't reflected in any derivative in a PenpalA account. + AssetHubRococo::fund_accounts(vec![(penpal_sov_account.clone().into(), ASSET_HUB_ROCOCO_ED)]); + + let sender = AssetHubRococoSender::get(); + let receiver = PenpalAReceiver::get(); + let asset_amount_to_send = 1_000_000_000_000; + + AssetHubRococo::execute_with(|| { + use frame_support::traits::tokens::fungibles::Mutate; + type Assets = ::Assets; + assert_ok!(>::mint_into( + usdt_id.into(), + &AssetHubRococoSender::get(), + asset_amount_to_send + 10_000_000_000_000, // Make sure it has enough. + )); + }); + + let relay_asset_penpal_pov = RelayLocation::get(); + + let usdt_from_asset_hub = PenpalUsdtFromAssetHub::get(); + + // Setup the pool between `relay_asset_penpal_pov` and `usdt_from_asset_hub` on PenpalA. + // So we can swap the custom asset that comes from AssetHubRococo for native asset to pay for + // fees. + PenpalA::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_ok!(::ForeignAssets::mint( + ::RuntimeOrigin::signed(PenpalAssetOwner::get()), + usdt_from_asset_hub.clone().into(), + PenpalASender::get().into(), + 10_000_000_000_000, // For it to have more than enough. + )); + + assert_ok!(::AssetConversion::create_pool( + ::RuntimeOrigin::signed(PenpalASender::get()), + Box::new(relay_asset_penpal_pov.clone()), + Box::new(usdt_from_asset_hub.clone()), + )); + + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, + ] + ); + + assert_ok!(::AssetConversion::add_liquidity( + ::RuntimeOrigin::signed(PenpalASender::get()), + Box::new(relay_asset_penpal_pov), + Box::new(usdt_from_asset_hub.clone()), + // `usdt_from_asset_hub` is worth a third of `relay_asset_penpal_pov` + 1_000_000_000_000, + 3_000_000_000_000, + 0, + 0, + PenpalASender::get().into() + )); + + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded { .. }) => {}, + ] + ); + }); + + let assets: Assets = vec![( + [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(usdt_id.into())], + asset_amount_to_send, + ) + .into()] + .into(); + + let test_args = TestContext { + sender: sender.clone(), + receiver: receiver.clone(), + args: TestArgs::new_para( + penpal_location, + receiver.clone(), + asset_amount_to_send, + assets, + None, + 0, + ), + }; + let mut test = SystemParaToParaTest::new(test_args); + + let sender_initial_balance = AssetHubRococo::execute_with(|| { + type Assets = ::Assets; + >::balance(usdt_id, &sender) + }); + let sender_initial_native_balance = AssetHubRococo::execute_with(|| { + type Balances = ::Balances; + Balances::free_balance(&sender) + }); + let receiver_initial_balance = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(usdt_from_asset_hub.clone(), &receiver) + }); + + test.set_assertion::(system_para_to_para_sender_assertions); + test.set_assertion::(system_para_to_para_receiver_assertions); + test.set_dispatchable::(system_para_to_para_reserve_transfer_assets); + test.assert(); + + let sender_after_balance = AssetHubRococo::execute_with(|| { + type Assets = ::Assets; + >::balance(usdt_id, &sender) + }); + let sender_after_native_balance = AssetHubRococo::execute_with(|| { + type Balances = ::Balances; + Balances::free_balance(&sender) + }); + let receiver_after_balance = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(usdt_from_asset_hub, &receiver) + }); + + // TODO(https://github.com/paritytech/polkadot-sdk/issues/5160): When we allow payment with different assets locally, this should be the same, since + // they aren't used for fees. + assert!(sender_after_native_balance < sender_initial_native_balance); + // Sender account's balance decreases. + assert_eq!(sender_after_balance, sender_initial_balance - asset_amount_to_send); + // Receiver account's balance increases. + assert!(receiver_after_balance > receiver_initial_balance); + assert!(receiver_after_balance < receiver_initial_balance + asset_amount_to_send); +} + +// =================================================================================== +// == Reserve Transfers USDT - Parachain->AssetHub->Parachain - pay fees using pool == +// =================================================================================== +// +// Transfer USDT From Penpal A to Penpal B with AssetHub as the reserve, while paying fees using +// USDT by making use of existing USDT pools on AssetHub and destination. +#[test] +fn reserve_transfer_usdt_from_para_to_para_through_asset_hub() { + let destination = PenpalA::sibling_location_of(PenpalB::para_id()); + let sender = PenpalASender::get(); + let asset_amount_to_send: Balance = ROCOCO_ED * 10000; + let fee_amount_to_send: Balance = ROCOCO_ED * 10000; + let sender_chain_as_seen_by_asset_hub = AssetHubRococo::sibling_location_of(PenpalA::para_id()); + let sov_of_sender_on_asset_hub = + AssetHubRococo::sovereign_account_id_of(sender_chain_as_seen_by_asset_hub); + let receiver_as_seen_by_asset_hub = AssetHubRococo::sibling_location_of(PenpalB::para_id()); + let sov_of_receiver_on_asset_hub = + AssetHubRococo::sovereign_account_id_of(receiver_as_seen_by_asset_hub); + + // Create SA-of-Penpal-on-AHW with ED. + // This ED isn't reflected in any derivative in a PenpalA account. + AssetHubRococo::fund_accounts(vec![ + (sov_of_sender_on_asset_hub.clone().into(), ASSET_HUB_ROCOCO_ED), + (sov_of_receiver_on_asset_hub.clone().into(), ASSET_HUB_ROCOCO_ED), + ]); + + // Give USDT to sov account of sender. + let usdt_id = 1984; + AssetHubRococo::execute_with(|| { + use frame_support::traits::tokens::fungibles::Mutate; + type Assets = ::Assets; + assert_ok!(>::mint_into( + usdt_id.into(), + &sov_of_sender_on_asset_hub.clone().into(), + asset_amount_to_send + fee_amount_to_send, + )); + }); + + // We create a pool between WND and USDT in AssetHub. + let native_asset = v3::Parent.into(); + let usdt = v3::Location::new( + 0, + [ + v3::Junction::PalletInstance(ASSETS_PALLET_ID), + v3::Junction::GeneralIndex(usdt_id.into()), + ], + ); + + // set up pool with USDT <> native pair + AssetHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_ok!(::Assets::mint( + ::RuntimeOrigin::signed(AssetHubRococoSender::get()), + usdt_id.into(), + AssetHubRococoSender::get().into(), + 10_000_000_000_000, // For it to have more than enough. + )); + + assert_ok!(::AssetConversion::create_pool( + ::RuntimeOrigin::signed(AssetHubRococoSender::get()), + Box::new(native_asset), + Box::new(usdt), + )); + + assert_expected_events!( + AssetHubRococo, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, + ] + ); + + assert_ok!(::AssetConversion::add_liquidity( + ::RuntimeOrigin::signed(AssetHubRococoSender::get()), + Box::new(native_asset), + Box::new(usdt), + 1_000_000_000_000, + 2_000_000_000_000, // usdt is worth half of `native_asset` + 0, + 0, + AssetHubRococoSender::get().into() + )); + + assert_expected_events!( + AssetHubRococo, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded { .. }) => {}, + ] + ); + }); + + let usdt_from_asset_hub = PenpalUsdtFromAssetHub::get(); + + // We also need a pool between WND and USDT on PenpalB. + PenpalB::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + let relay_asset = RelayLocation::get(); + + assert_ok!(::ForeignAssets::mint( + ::RuntimeOrigin::signed(PenpalAssetOwner::get()), + usdt_from_asset_hub.clone().into(), + PenpalBReceiver::get().into(), + 10_000_000_000_000, // For it to have more than enough. + )); + + assert_ok!(::AssetConversion::create_pool( + ::RuntimeOrigin::signed(PenpalBReceiver::get()), + Box::new(relay_asset.clone()), + Box::new(usdt_from_asset_hub.clone()), + )); + + assert_expected_events!( + PenpalB, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, + ] + ); + + assert_ok!(::AssetConversion::add_liquidity( + ::RuntimeOrigin::signed(PenpalBReceiver::get()), + Box::new(relay_asset), + Box::new(usdt_from_asset_hub.clone()), + 1_000_000_000_000, + 2_000_000_000_000, // `usdt_from_asset_hub` is worth half of `relay_asset` + 0, + 0, + PenpalBReceiver::get().into() + )); + + assert_expected_events!( + PenpalB, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded { .. }) => {}, + ] + ); + }); + + PenpalA::execute_with(|| { + use frame_support::traits::tokens::fungibles::Mutate; + type ForeignAssets = ::ForeignAssets; + assert_ok!(>::mint_into( + usdt_from_asset_hub.clone(), + &sender, + asset_amount_to_send + fee_amount_to_send, + )); + }); + + // Prepare assets to transfer. + let assets: Assets = + (usdt_from_asset_hub.clone(), asset_amount_to_send + fee_amount_to_send).into(); + // Just to be very specific we're not including anything other than USDT. + assert_eq!(assets.len(), 1); + + // Give the sender enough Relay tokens to pay for local delivery fees. + // TODO(https://github.com/paritytech/polkadot-sdk/issues/5160): When we support local delivery fee payment in other assets, we don't need this. + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(PenpalAssetOwner::get()), + RelayLocation::get(), + sender.clone(), + 10_000_000_000_000, // Large estimate to make sure it works. + ); + + // Init values for Parachain Destination + let receiver = PenpalBReceiver::get(); + + // Init Test + let fee_asset_index = 0; + let test_args = TestContext { + sender: sender.clone(), + receiver: receiver.clone(), + args: TestArgs::new_para( + destination, + receiver.clone(), + asset_amount_to_send, + assets, + None, + fee_asset_index, + ), + }; + let mut test = ParaToParaThroughAHTest::new(test_args); + + // Query initial balances + let sender_assets_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(usdt_from_asset_hub.clone(), &sender) + }); + let receiver_assets_before = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(usdt_from_asset_hub.clone(), &receiver) + }); + test.set_assertion::(para_to_para_through_hop_sender_assertions); + test.set_assertion::(para_to_para_asset_hub_hop_assertions); + test.set_assertion::(para_to_para_through_hop_receiver_assertions); + test.set_dispatchable::( + para_to_para_through_asset_hub_limited_reserve_transfer_assets, + ); + test.assert(); + + // Query final balances + let sender_assets_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(usdt_from_asset_hub.clone(), &sender) + }); + let receiver_assets_after = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(usdt_from_asset_hub, &receiver) + }); + + // Sender's balance is reduced by amount + assert!(sender_assets_after < sender_assets_before - asset_amount_to_send); + // Receiver's balance is increased + assert!(receiver_assets_after > receiver_assets_before); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs index 7127e6da3338..95cbc9218a08 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs @@ -84,7 +84,7 @@ pub fn system_para_to_para_sender_assertions(t: SystemParaToParaTest) { assert_expected_events!( AssetHubWestend, vec![ - // Amount of foreign asset is transferred to Parachain's Sovereign account + // Amount of trust-backed asset is transferred to Parachain's Sovereign account RuntimeEvent::Assets( pallet_assets::Event::Transferred { from, to, amount, .. }, ) => { From 576744dca3dbf039331b3395714dba2c2ec10cff Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Tue, 30 Jul 2024 10:54:43 +0200 Subject: [PATCH 10/19] chore: address feedback --- .../asset-hub-rococo/src/tests/reserve_transfer.rs | 4 ++-- .../asset-hub-westend/src/tests/reserve_transfer.rs | 4 ++-- .../assets/asset-hub-rococo/src/xcm_config.rs | 6 +++--- .../assets/asset-hub-westend/src/xcm_config.rs | 6 +++--- .../src/asset_exchange/single_asset_adapter/mock.rs | 7 +------ polkadot/xcm/xcm-executor/src/lib.rs | 13 +++++-------- 6 files changed, 16 insertions(+), 24 deletions(-) diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs index 0a5b192cd094..1de176751a7f 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs @@ -1199,10 +1199,10 @@ fn reserve_transfer_native_asset_from_para_to_para_through_relay() { } // ============================================================================ -// ==== Reserve Transfers - USDT pay fees using pool - AssetHub->Parachain ==== +// ==== Reserve Transfers USDT - AssetHub->Parachain - pay fees using pool ==== // ============================================================================ #[test] -fn reserve_transfer_pool_assets_from_system_para_to_para() { +fn reserve_transfer_usdt_from_asset_hub_to_para() { let usdt_id = 1984u32; let penpal_location = AssetHubRococo::sibling_location_of(PenpalA::para_id()); let penpal_sov_account = AssetHubRococo::sovereign_account_id_of(penpal_location.clone()); diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs index 95cbc9218a08..31c6978d6902 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs @@ -1202,10 +1202,10 @@ fn reserve_transfer_native_asset_from_para_to_para_through_relay() { } // ============================================================================ -// ==== Reserve Transfers - USDT pay fees using pool - AssetHub->Parachain ==== +// ==== Reserve Transfers USDT - AssetHub->Parachain - pay fees using pool ==== // ============================================================================ #[test] -fn reserve_transfer_pool_assets_from_system_para_to_para() { +fn reserve_transfer_usdt_from_asset_hub_to_para() { let usdt_id = 1984u32; let penpal_location = AssetHubWestend::sibling_location_of(PenpalA::para_id()); let penpal_sov_account = AssetHubWestend::sovereign_account_id_of(penpal_location.clone()); diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs index a2882f54bd06..978f6adb83b5 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs @@ -331,9 +331,9 @@ pub type TrustedTeleporters = ( ); /// Asset converter for pool assets. -/// Used to convert assets in pools to the asset required for fee payment. -/// The pool must be between the first asset and the one required for fee payment. -/// This type allows paying fees with any asset in a pool with the asset required for fee payment. +/// Used to convert one asset to another, when there is a pool available between the two. +/// This type thus allows paying fees with any asset as long as there is a pool between said +/// asset and the asset required for fee payment. pub type PoolAssetsExchanger = SingleAssetExchangeAdapter< crate::AssetConversion, crate::NativeAndAssets, diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs index 6f2acfdaf6bb..84ef79c212f4 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs @@ -351,9 +351,9 @@ pub type TrustedTeleporters = ( ); /// Asset converter for pool assets. -/// Used to convert assets in pools to the asset required for fee payment. -/// The pool must be between the first asset and the one required for fee payment. -/// This type allows paying fees with any asset in a pool with the asset required for fee payment. +/// Used to convert one asset to another, when there is a pool available between the two. +/// This type thus allows paying fees with any asset as long as there is a pool between said +/// asset and the asset required for fee payment. pub type PoolAssetsExchanger = SingleAssetExchangeAdapter< crate::AssetConversion, crate::NativeAndAssets, diff --git a/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter/mock.rs b/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter/mock.rs index 71b6f247405a..957b49ce75fb 100644 --- a/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter/mock.rs +++ b/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter/mock.rs @@ -145,12 +145,7 @@ impl pallet_asset_conversion::Config for Runtime { type MintMinLiquidity = ConstU128<100>; type WeightInfo = (); #[cfg(feature = "runtime-benchmarks")] - type BenchmarkHelper = assets_common::benchmarks::AssetPairFactory< - HereLocation, - parachain_info::Pallet, - xcm_config::TrustBackedAssetsPalletIndex, - Location, - >; + type BenchmarkHelper = (); } /// We only alias local accounts. diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index bf6ff3671880..a3c4af0304de 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -490,8 +490,7 @@ impl XcmExecutor { Some(fee) => fee, None => return Ok(()), // No delivery fees need to be paid. }; - // If `BuyExecution` was called, we know we can try to use that asset for fees. - // We get the asset the user wants to use to pay for fees. + // If `BuyExecution` was called, we use that asset for transport fees as well. let asset_to_pay_for_fees = self.calculate_asset_for_delivery_fees(asset_needed_for_fees.clone()); tracing::trace!(target: "xcm::fees", ?asset_to_pay_for_fees); @@ -515,7 +514,7 @@ impl XcmExecutor { let asset = iter.next().ok_or(XcmError::NotHoldingFees)?; asset.into() }; - // We perform the swap if we need to to pay fees in the correct asset. + // We perform the swap, if needed, to pay fees. let paid = if asset_to_pay_for_fees.id != asset_needed_for_fees.id { let swapped_asset: Assets = Config::AssetExchanger::exchange_asset( self.origin_ref(), @@ -968,7 +967,7 @@ impl XcmExecutor { message_to_weigh.extend(xcm.0.clone().into_iter()); let (_, fee) = validate_send::(dest.clone(), Xcm(message_to_weigh))?; - let maybe_delivery_fee = if let Some(asset_needed_for_fees) = fee.get(0) { + let maybe_delivery_fee = fee.get(0).map(|asset_needed_for_fees| { tracing::trace!( target: "xcm::DepositReserveAsset", "Asset provided to pay for fees {:?}, asset required for transport fees: {:?}", @@ -980,10 +979,8 @@ impl XcmExecutor { let delivery_fee = self.holding.saturating_take(asset_to_pay_for_fees.into()); tracing::trace!(target: "xcm::DepositReserveAsset", ?delivery_fee); - Some(delivery_fee) - } else { - None - }; + delivery_fee + }); // now take assets to deposit (after having taken delivery fees) let deposited = self.holding.saturating_take(assets); tracing::trace!(target: "xcm::DepositReserveAsset", ?deposited, "Assets except delivery fee"); From d2936303d38a0500d2dd28d38d72cc1962d09728 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Mon, 5 Aug 2024 16:13:34 +0200 Subject: [PATCH 11/19] chore(xcm-builder): not use sp-std --- polkadot/xcm/xcm-builder/Cargo.toml | 2 -- .../xcm-builder/src/asset_exchange/single_asset_adapter.rs | 4 +++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/polkadot/xcm/xcm-builder/Cargo.toml b/polkadot/xcm/xcm-builder/Cargo.toml index 382229e4e5b0..671f0181277a 100644 --- a/polkadot/xcm/xcm-builder/Cargo.toml +++ b/polkadot/xcm/xcm-builder/Cargo.toml @@ -17,7 +17,6 @@ xcm = { workspace = true } xcm-executor = { workspace = true } sp-arithmetic = { workspace = true } sp-io = { workspace = true } -sp-std = { workspace = true } sp-runtime = { workspace = true } sp-weights = { workspace = true } frame-support = { workspace = true } @@ -68,7 +67,6 @@ std = [ "polkadot-parachain-primitives/std", "primitive-types/std", "scale-info/std", - "sp-std/std", "sp-arithmetic/std", "sp-io/std", "sp-runtime/std", diff --git a/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter.rs b/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter.rs index bdb7bffb871a..6853f7fad79d 100644 --- a/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter.rs +++ b/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter.rs @@ -21,10 +21,12 @@ mod mock; #[cfg(test)] mod tests; +extern crate alloc; + +use alloc::vec; use core::marker::PhantomData; use frame_support::{ensure, traits::tokens::fungibles}; use pallet_asset_conversion::{QuotePrice, SwapCredit}; -use sp_std::vec; use xcm::prelude::*; use xcm_executor::{ traits::{AssetExchange, MatchesFungibles}, From 72e96aa605ecb9c50efc8ff09fdbd02712acc997 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Mon, 5 Aug 2024 16:14:25 +0200 Subject: [PATCH 12/19] chore: delete old file --- .../asset_exchange/single_asset_adapter.rs | 200 ------------------ 1 file changed, 200 deletions(-) delete mode 100644 polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter.rs diff --git a/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter.rs b/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter.rs deleted file mode 100644 index 6853f7fad79d..000000000000 --- a/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter.rs +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot 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. - -// Polkadot 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 Polkadot. If not, see . - -//! Single asset exchange adapter. - -#[cfg(test)] -mod mock; -#[cfg(test)] -mod tests; - -extern crate alloc; - -use alloc::vec; -use core::marker::PhantomData; -use frame_support::{ensure, traits::tokens::fungibles}; -use pallet_asset_conversion::{QuotePrice, SwapCredit}; -use xcm::prelude::*; -use xcm_executor::{ - traits::{AssetExchange, MatchesFungibles}, - AssetsInHolding, -}; - -/// An adapter from [`pallet_asset_conversion::SwapCredit`] and -/// [`pallet_asset_conversion::QuotePrice`] to [`xcm_executor::traits::AssetExchange`]. -/// -/// This adapter takes just one fungible asset in `give` and allows only one fungible asset in -/// `want`. If you need to handle more assets in either `give` or `want`, then you should use -/// another type that implements [`xcm_executor::traits::AssetExchange`] or build your own. -/// -/// `exchange_asset` will return an error if there's more than one asset in `want`. -pub struct SingleAssetExchangeAdapter( - PhantomData<(AssetConversion, Fungibles, Matcher, AccountId)>, -); -impl AssetExchange - for SingleAssetExchangeAdapter -where - AssetConversion: SwapCredit< - AccountId, - Balance = u128, - AssetKind = Fungibles::AssetId, - Credit = fungibles::Credit, - > + QuotePrice, - Fungibles: fungibles::Balanced, - Matcher: MatchesFungibles, -{ - fn exchange_asset( - _: Option<&Location>, - give: AssetsInHolding, - want: &Assets, - maximal: bool, - ) -> Result { - let mut give_iter = give.fungible_assets_iter(); - let give_asset = give_iter.next().ok_or_else(|| { - log::trace!( - target: "xcm::SingleAssetExchangeAdapter::exchange_asset", - "No fungible asset was in `give`.", - ); - give.clone() - })?; - ensure!(give_iter.next().is_none(), give.clone()); // We only support 1 asset in `give`. - ensure!(want.len() == 1, give.clone()); // We only support 1 asset in `want`. - let want_asset = if let Some(asset) = want.get(0) { - asset - } else { - log::trace!( - target: "xcm::SingleAssetExchangeAdapter::exchange_asset", - "No asset was in `want`.", - ); - return Ok(give.clone()); - }; - let (give_asset_id, give_amount) = - Matcher::matches_fungibles(&give_asset).map_err(|error| { - log::trace!( - target: "xcm::SingleAssetExchangeAdapter::exchange_asset", - "Could not map XCM asset give {:?} to FRAME asset. Error: {:?}", - give_asset, - error, - ); - give.clone() - })?; - let (want_asset_id, want_amount) = - Matcher::matches_fungibles(&want_asset).map_err(|error| { - log::trace!( - target: "xcm::SingleAssetExchangeAdapter::exchange_asset", - "Could not map XCM asset want {:?} to FRAME asset. Error: {:?}", - want_asset, - error, - ); - give.clone() - })?; - - // We have to do this to convert the XCM assets into credit the pool can use. - let swap_asset = give_asset_id.clone().into(); - let credit_in = Fungibles::issue(give_asset_id, give_amount); - - // Do the swap. - let credit_out = if maximal { - // If `maximal`, then we swap exactly `credit_in` to get as much of `want_asset_id` as - // we can, with a minimum of `want_amount`. - >::swap_exact_tokens_for_tokens( - vec![swap_asset, want_asset_id], - credit_in, - Some(want_amount), - ) - .map_err(|(credit_in, error)| { - log::error!( - target: "xcm::SingleAssetExchangeAdapter::exchange_asset", - "Could not perform the swap, error: {:?}.", - error - ); - drop(credit_in); - give.clone() - })? - } else { - // If `minimal`, then we swap as little of `credit_in` as we can to get exactly - // `want_amount` of `want_asset_id`. - let (credit_out, _credit_change) = - >::swap_tokens_for_exact_tokens( - vec![swap_asset, want_asset_id], - credit_in, - want_amount, - ) - .map_err(|(credit_in, error)| { - log::error!( - target: "xcm::SingleAssetExchangeAdapter::exchange_asset", - "Could not perform the swap, error: {:?}.", - error - ); - drop(credit_in); - give.clone() - })?; - - // TODO: If we want to make this a generic adapter, this need not be 0. Handle it. - // Probably depositing it back to the holding. - // debug_assert!(credit_change.peek() == Zero::zero()); - - credit_out - }; - - let resulting_asset: Asset = (want_asset.id.clone(), credit_out.peek()).into(); - Ok(resulting_asset.into()) - } - - fn quote_exchange_price(asset1: &Asset, asset2: &Asset, maximal: bool) -> Option { - // We first match both XCM assets to the asset ID types `AssetConversion` can handle. - let (asset1_id, _) = Matcher::matches_fungibles(asset1) - .map_err(|error| { - log::trace!( - target: "xcm::SingleAssetExchangeAdapter::quote_exchange_price", - "Could not map XCM asset {:?} to FRAME asset. Error: {:?}.", - asset1, - error, - ); - () - }) - .ok()?; - // For `asset2`, we also want the desired amount. - let (asset2_id, desired_asset2_amount) = Matcher::matches_fungibles(asset2) - .map_err(|error| { - log::trace!( - target: "xcm::SingleAssetExchangeAdapter::quote_exchange_price", - "Could not map XCM asset {:?} to FRAME asset. Error: {:?}.", - asset2, - error, - ); - () - }) - .ok()?; - // We quote the price. - let necessary_asset1_amount = if maximal { - ::quote_price_exact_tokens_for_tokens( - asset1_id, - asset2_id, - desired_asset2_amount, - true, - )? - } else { - ::quote_price_tokens_for_exact_tokens( - asset1_id, - asset2_id, - desired_asset2_amount, - true, - )? - }; - Some(necessary_asset1_amount) - } -} From b2ce064d9420859e9c2dec8e2cd067366dcf51ab Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Mon, 5 Aug 2024 16:20:15 +0200 Subject: [PATCH 13/19] doc(prdoc): add crates --- prdoc/pr_5131.prdoc | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/prdoc/pr_5131.prdoc b/prdoc/pr_5131.prdoc index b3096fa07de7..3c631c1a3058 100644 --- a/prdoc/pr_5131.prdoc +++ b/prdoc/pr_5131.prdoc @@ -22,4 +22,10 @@ doc: It's called `SingleAssetExchangeAdapter`, read more about it in its rust docs. This item is already configured in Asset Hub. -crates: [ ] +crates: + - name: staging-xcm-executor + bump: minor + - name: asset-hub-westend-runtime + bump: minor + - name: asset-hub-rococo-runtime + bump: minor From d0d1737541806a9c79447b5ddf8c4e96dc9f5fd9 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Mon, 5 Aug 2024 16:45:54 +0200 Subject: [PATCH 14/19] fix(xcm-executor): adjust to new AssetExchange API --- Cargo.lock | 1 - polkadot/xcm/xcm-executor/src/lib.rs | 9 ++++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 91d097f46207..27601338738a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21044,7 +21044,6 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0", "sp-weights", "staging-xcm", "staging-xcm-executor", diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index a3c4af0304de..baaeff250aba 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -552,11 +552,14 @@ impl XcmExecutor { if *asset_wanted_for_fees != asset_needed_for_fees.id { match Config::AssetExchanger::quote_exchange_price( &(asset_wanted_for_fees.clone(), 1u128).into(), - &asset_needed_for_fees, + &asset_needed_for_fees.clone().into(), false, // Minimal. ) { - Some(necessary_amount) => - (asset_wanted_for_fees.clone(), necessary_amount).into(), + Some(necessary_assets) => + // We only use the first asset for fees. + // If this is not enough to swap for the fee asset then it will error later down + // the line. + necessary_assets.get(0).unwrap_or(&asset_needed_for_fees.clone()).clone(), // If we can't convert, then we return the original asset. // It will error later in any case. None => { From 970375cd66f2eff024a340de2ae40e02925ccbfb Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Mon, 5 Aug 2024 17:30:07 +0200 Subject: [PATCH 15/19] fix(xcm-builder): handle case where exchange is called right after quote --- .../src/asset_exchange/single_asset_adapter/adapter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter/adapter.rs b/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter/adapter.rs index fa94ee5f1caa..3108068686f9 100644 --- a/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter/adapter.rs +++ b/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter/adapter.rs @@ -136,7 +136,7 @@ where give.clone() })?; - (credit_out, Some(credit_change)) + (credit_out, if credit_change.peek() > 0 { Some(credit_change) } else { None }) }; // We create an `AssetsInHolding` instance by putting in the resulting asset From 037588640763ba165f7e16d9ea262b75a7af0ef6 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Tue, 6 Aug 2024 12:45:14 +0200 Subject: [PATCH 16/19] doc(prdoc): correct bumps --- prdoc/pr_5131.prdoc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/prdoc/pr_5131.prdoc b/prdoc/pr_5131.prdoc index 3c631c1a3058..a213b999693a 100644 --- a/prdoc/pr_5131.prdoc +++ b/prdoc/pr_5131.prdoc @@ -29,3 +29,9 @@ crates: bump: minor - name: asset-hub-rococo-runtime bump: minor + - name: staging-xcm-builder + bump: patch + - name: assets-common + bump: patch + - name: penpal-runtime + bump: minor From ccd07ad7f08c0e367ca06a7879f15f3abc8b54e6 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Thu, 8 Aug 2024 16:14:48 +0200 Subject: [PATCH 17/19] doc(xcm-executor): improve some comments --- polkadot/xcm/xcm-executor/src/lib.rs | 4 ++-- prdoc/pr_5131.prdoc | 13 +++++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index baaeff250aba..359723929e81 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -490,7 +490,7 @@ impl XcmExecutor { Some(fee) => fee, None => return Ok(()), // No delivery fees need to be paid. }; - // If `BuyExecution` was called, we use that asset for transport fees as well. + // If `BuyExecution` was called, we use that asset for delivery fees as well. let asset_to_pay_for_fees = self.calculate_asset_for_delivery_fees(asset_needed_for_fees.clone()); tracing::trace!(target: "xcm::fees", ?asset_to_pay_for_fees); @@ -973,7 +973,7 @@ impl XcmExecutor { let maybe_delivery_fee = fee.get(0).map(|asset_needed_for_fees| { tracing::trace!( target: "xcm::DepositReserveAsset", - "Asset provided to pay for fees {:?}, asset required for transport fees: {:?}", + "Asset provided to pay for fees {:?}, asset required for delivery fees: {:?}", self.asset_used_for_fees, asset_needed_for_fees, ); let asset_to_pay_for_fees = diff --git a/prdoc/pr_5131.prdoc b/prdoc/pr_5131.prdoc index a213b999693a..db1003ab4033 100644 --- a/prdoc/pr_5131.prdoc +++ b/prdoc/pr_5131.prdoc @@ -8,20 +8,25 @@ doc: description: | If the `AssetExchanger` is configured on a runtime, the XCM executor is now able to swap assets to pay for delivery fees. - This was already possible for execution fees. + This was already possible for execution fees via the `SwapFirstAssetTrader`. A runtime where this will be possible is Asset Hub. That means reserve asset transfers from Parachain A to Parachain B passing through Asset Hub no - longer need to have any DOT. + longer need to have any DOT to pay for fees on AssetHub. They can have any asset in a pool with DOT on Asset Hub, for example USDT or USDC. - audience: Runtime Dev description: | Using the `AssetExchanger` XCM config item, the executor now swaps fees to use for delivery fees, if possible. If you want your runtime to support this, you need to configure this new item. - Thankfully, xcm-builder now has a new adapter for this, letting you use pallet-asset-conversion for it. - It's called `SingleAssetExchangeAdapter`, read more about it in its rust docs. + Thankfully, `xcm-builder` now has a new adapter for this, which lets you use `pallet-asset-conversion` + or any type that implements the `SwapCredit` and `QuotePrice` traits. + It's called `SingleAssetExchangeAdapter`, you can read more about it in its rust docs. This item is already configured in Asset Hub. + IMPORTANT: The executor now only takes the first asset for delivery fees. If you have configured a custom router + that returns more than one asset for delivery fees, then only the first one will be taken into account. + This is most likely not what you want. + crates: - name: staging-xcm-executor bump: minor From 9b6790c3b419f61819f129586b1eeffcf02ed350 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Mon, 12 Aug 2024 11:21:20 +0200 Subject: [PATCH 18/19] doc(xcm-executor): address feedback --- polkadot/xcm/xcm-executor/src/config.rs | 6 ++++++ polkadot/xcm/xcm-executor/src/lib.rs | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/polkadot/xcm/xcm-executor/src/config.rs b/polkadot/xcm/xcm-executor/src/config.rs index 63b113bc250f..d33af669845a 100644 --- a/polkadot/xcm/xcm-executor/src/config.rs +++ b/polkadot/xcm/xcm-executor/src/config.rs @@ -33,6 +33,10 @@ pub trait Config { type RuntimeCall: Parameter + Dispatchable + GetDispatchInfo; /// How to send an onward XCM message. + /// + /// The sender is tasked with returning the assets it needs to pay for delivery fees. + /// Only one asset should be returned as delivery fees, any other will be ignored by + /// the executor. type XcmSender: SendXcm; /// How to withdraw and deposit an asset. @@ -74,6 +78,8 @@ pub trait Config { type AssetLocker: AssetLock; /// Handler for exchanging assets. + /// + /// This is used in the executor to swap the asset wanted for fees with the asset needed for delivery fees. type AssetExchanger: AssetExchange; /// The handler for when there is an instruction to claim assets. diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index 359723929e81..4c5300297593 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -551,7 +551,7 @@ impl XcmExecutor { if let Some(asset_wanted_for_fees) = &self.asset_used_for_fees { if *asset_wanted_for_fees != asset_needed_for_fees.id { match Config::AssetExchanger::quote_exchange_price( - &(asset_wanted_for_fees.clone(), 1u128).into(), + &(asset_wanted_for_fees.clone(), Fungible(0)).into(), &asset_needed_for_fees.clone().into(), false, // Minimal. ) { @@ -564,7 +564,7 @@ impl XcmExecutor { // It will error later in any case. None => { tracing::trace!( - target: "xcm::take_fee", + target: "xcm::calculate_asset_for_delivery_fees", ?asset_wanted_for_fees, "Could not convert fees", ); From 4df3d432ec144e281b57b1561d75e86c0a85f044 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Mon, 2 Sep 2024 12:10:23 +0200 Subject: [PATCH 19/19] fix: v3 -> v4 --- .../src/tests/reserve_transfer.rs | 13 +++++-------- .../src/tests/reserve_transfer.rs | 13 +++++-------- .../assets/asset-hub-rococo/src/xcm_config.rs | 6 +++--- .../assets/asset-hub-westend/src/xcm_config.rs | 17 +++++++++-------- polkadot/xcm/xcm-executor/src/config.rs | 3 ++- 5 files changed, 24 insertions(+), 28 deletions(-) diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs index ddfd31def0dd..faff5f7660c2 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs @@ -1377,13 +1377,10 @@ fn reserve_transfer_usdt_from_para_to_para_through_asset_hub() { }); // We create a pool between WND and USDT in AssetHub. - let native_asset = v3::Parent.into(); - let usdt = v3::Location::new( + let native_asset: Location = Parent.into(); + let usdt = Location::new( 0, - [ - v3::Junction::PalletInstance(ASSETS_PALLET_ID), - v3::Junction::GeneralIndex(usdt_id.into()), - ], + [Junction::PalletInstance(ASSETS_PALLET_ID), Junction::GeneralIndex(usdt_id.into())], ); // set up pool with USDT <> native pair @@ -1399,8 +1396,8 @@ fn reserve_transfer_usdt_from_para_to_para_through_asset_hub() { assert_ok!(::AssetConversion::create_pool( ::RuntimeOrigin::signed(AssetHubRococoSender::get()), - Box::new(native_asset), - Box::new(usdt), + Box::new(native_asset.clone()), + Box::new(usdt.clone()), )); assert_expected_events!( diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs index 05c8fd5ad052..53b6939298da 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs @@ -1381,13 +1381,10 @@ fn reserve_transfer_usdt_from_para_to_para_through_asset_hub() { }); // We create a pool between WND and USDT in AssetHub. - let native_asset = v3::Parent.into(); - let usdt = v3::Location::new( + let native_asset: Location = Parent.into(); + let usdt = Location::new( 0, - [ - v3::Junction::PalletInstance(ASSETS_PALLET_ID), - v3::Junction::GeneralIndex(usdt_id.into()), - ], + [Junction::PalletInstance(ASSETS_PALLET_ID), Junction::GeneralIndex(usdt_id.into())], ); // set up pool with USDT <> native pair @@ -1403,8 +1400,8 @@ fn reserve_transfer_usdt_from_para_to_para_through_asset_hub() { assert_ok!(::AssetConversion::create_pool( ::RuntimeOrigin::signed(AssetHubWestendSender::get()), - Box::new(native_asset), - Box::new(usdt), + Box::new(native_asset.clone()), + Box::new(usdt.clone()), )); assert_expected_events!( diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs index 64539b08381a..f263baf4bef8 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs @@ -335,14 +335,14 @@ pub type PoolAssetsExchanger = SingleAssetExchangeAdapter< crate::AssetConversion, crate::NativeAndAssets, ( - TrustBackedAssetsAsLocation, + TrustBackedAssetsAsLocation, ForeignAssetsConvertedConcreteId, // `ForeignAssetsConvertedConcreteId` excludes the relay token, so we add it back here. MatchedConvertedConcreteId< - xcm::v3::Location, + xcm::v4::Location, Balance, Equals, - WithLatestLocationConverter, + WithLatestLocationConverter, TryConvertInto, >, ), diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs index 00dfddf1f2ea..bc5d07f552b0 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs @@ -51,12 +51,13 @@ use xcm_builder::{ DenyReserveTransferToRelayChain, DenyThenTry, DescribeFamily, DescribePalletTerminal, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, GlobalConsensusParachainConvertsFor, HashedDescription, IsConcrete, LocalMint, - MatchedConvertedConcreteId, NetworkExportTableItem, NoChecking, NonFungiblesAdapter, ParentAsSuperuser, ParentIsPreset, - RelayChainAsNative, SendXcmFeeToAccount, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SingleAssetExchangeAdapter, SovereignPaidRemoteExporter, + MatchedConvertedConcreteId, NetworkExportTableItem, NoChecking, NonFungiblesAdapter, + ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SendXcmFeeToAccount, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SingleAssetExchangeAdapter, SovereignPaidRemoteExporter, SovereignSignedViaLocation, StartsWith, StartsWithExplicitGlobalConsensus, TakeWeightCredit, - TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithLatestLocationConverter, WithComputedOrigin, WithUniqueTopic, - XcmFeeManagerFromComponents, + TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, + WithLatestLocationConverter, WithUniqueTopic, XcmFeeManagerFromComponents, }; use xcm_executor::XcmExecutor; @@ -358,14 +359,14 @@ pub type PoolAssetsExchanger = SingleAssetExchangeAdapter< crate::AssetConversion, crate::NativeAndAssets, ( - TrustBackedAssetsAsLocation, + TrustBackedAssetsAsLocation, ForeignAssetsConvertedConcreteId, // `ForeignAssetsConvertedConcreteId` excludes the relay token, so we add it back here. MatchedConvertedConcreteId< - xcm::v3::Location, + xcm::v4::Location, Balance, Equals, - WithLatestLocationConverter, + WithLatestLocationConverter, TryConvertInto, >, ), diff --git a/polkadot/xcm/xcm-executor/src/config.rs b/polkadot/xcm/xcm-executor/src/config.rs index d33af669845a..5bcbbd3466e8 100644 --- a/polkadot/xcm/xcm-executor/src/config.rs +++ b/polkadot/xcm/xcm-executor/src/config.rs @@ -79,7 +79,8 @@ pub trait Config { /// Handler for exchanging assets. /// - /// This is used in the executor to swap the asset wanted for fees with the asset needed for delivery fees. + /// This is used in the executor to swap the asset wanted for fees with the asset needed for + /// delivery fees. type AssetExchanger: AssetExchange; /// The handler for when there is an instruction to claim assets.