From eea690bd5dce704bebcbf0e36e5a39f157338f62 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Wed, 8 May 2024 02:16:47 +0200 Subject: [PATCH 01/43] feat(xcm-executor): add AssetConverter config item to support multiple fees --- polkadot/xcm/xcm-executor/src/config.rs | 5 ++- .../xcm-executor/src/traits/fee_manager.rs | 40 +++++++++++++++++++ polkadot/xcm/xcm-executor/src/traits/mod.rs | 2 +- 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/polkadot/xcm/xcm-executor/src/config.rs b/polkadot/xcm/xcm-executor/src/config.rs index b296d32ca2ad..1564eff227a4 100644 --- a/polkadot/xcm/xcm-executor/src/config.rs +++ b/polkadot/xcm/xcm-executor/src/config.rs @@ -15,7 +15,7 @@ // along with Polkadot. If not, see . use crate::traits::{ - AssetExchange, AssetLock, CallDispatcher, ClaimAssets, ConvertOrigin, DropAssets, ExportXcm, + AssetExchange, AssetConversion, AssetLock, CallDispatcher, ClaimAssets, ConvertOrigin, DropAssets, ExportXcm, FeeManager, HandleHrmpChannelAccepted, HandleHrmpChannelClosing, HandleHrmpNewChannelOpenRequest, OnResponse, ProcessTransaction, ShouldExecute, TransactAsset, VersionChangeNotifier, WeightBounds, WeightTrader, @@ -122,4 +122,7 @@ pub trait Config { type HrmpChannelAcceptedHandler: HandleHrmpChannelAccepted; /// Allows optional logic execution for the `HrmpChannelClosing` XCM notification. type HrmpChannelClosingHandler: HandleHrmpChannelClosing; + + /// Allows converting a balance of one asset into another. + type AssetConverter: AssetConversion; } diff --git a/polkadot/xcm/xcm-executor/src/traits/fee_manager.rs b/polkadot/xcm/xcm-executor/src/traits/fee_manager.rs index b6e303daaad8..69766e3a1aea 100644 --- a/polkadot/xcm/xcm-executor/src/traits/fee_manager.rs +++ b/polkadot/xcm/xcm-executor/src/traits/fee_manager.rs @@ -58,3 +58,43 @@ impl FeeManager for () { fn handle_fee(_: Assets, _: Option<&XcmContext>, _: FeeReason) {} } + +/// Not about exchanging assets, just converting an amount of one +/// into one of another. +/// Used for paying fees in different assets. +pub trait AssetConversion { + /// Convert `asset` to the specified `asset_id`. + /// If the conversion can be done, the returned Asset + /// has the specified `asset_id` and a new balance. + /// If it can't be converted, an error is returned. + fn convert_asset(asset: &Asset, asset_id: &AssetId) -> Result; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl AssetConversion for Tuple { + fn convert_asset(asset: &Asset, asset_id: &AssetId) -> Result { + let mut last_error = None; + + for_tuples!( #( + match Tuple::convert_asset(asset, asset_id) { + Ok(new_asset) => { + log::trace!( + target: "xcm::AssetConversion::convert_asset", + "Found successful implementation in tuple.", + ); + return Ok(new_asset) + }, + Err(error) => { + log::error!( + target: "xcm::AssetConversion::convert_asset", + "Implementation in tuple errored: {:?}, trying with next one, unless this was the last one.", + error, + ); + last_error = Some(error); + } + } + )* ); + + Err(last_error.unwrap_or(XcmError::TooExpensive)) + } +} diff --git a/polkadot/xcm/xcm-executor/src/traits/mod.rs b/polkadot/xcm/xcm-executor/src/traits/mod.rs index aa3f0d26e302..0f742c298827 100644 --- a/polkadot/xcm/xcm-executor/src/traits/mod.rs +++ b/polkadot/xcm/xcm-executor/src/traits/mod.rs @@ -29,7 +29,7 @@ pub use asset_transfer::{Error as AssetTransferError, TransferType, XcmAssetTran mod export; pub use export::{export_xcm, validate_export, ExportXcm}; mod fee_manager; -pub use fee_manager::{FeeManager, FeeReason}; +pub use fee_manager::{FeeManager, FeeReason, AssetConversion}; mod filter_asset_location; #[allow(deprecated)] pub use filter_asset_location::FilterAssetLocation; From 5d74482d9b44f01cea08e16086bd2c206ea4b096 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Wed, 8 May 2024 02:19:39 +0200 Subject: [PATCH 02/43] fix: implement AssetConverter in all runtimes --- .../runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs | 1 + .../runtimes/collectives/collectives-westend/src/xcm_config.rs | 1 + .../parachains/runtimes/people/people-westend/src/xcm_config.rs | 1 + polkadot/runtime/rococo/src/xcm_config.rs | 1 + polkadot/runtime/westend/src/xcm_config.rs | 1 + 5 files changed, 5 insertions(+) diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs index f147cd9653fe..286a27c82b75 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs @@ -206,6 +206,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type AssetConverter = (); } pub type PriceForParentDelivery = diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs index 84697c3e3634..47b95a945b2e 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs @@ -220,6 +220,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type AssetConverter = (); } /// Converts a local signed origin into an XCM location. diff --git a/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs index 0a903f915056..1a5b34a4cbea 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs @@ -238,6 +238,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type AssetConverter = (); } /// Converts a local signed origin into an XCM location. Forms the basis for local origins diff --git a/polkadot/runtime/rococo/src/xcm_config.rs b/polkadot/runtime/rococo/src/xcm_config.rs index c7063bd7ad61..80d514f7280d 100644 --- a/polkadot/runtime/rococo/src/xcm_config.rs +++ b/polkadot/runtime/rococo/src/xcm_config.rs @@ -224,6 +224,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type AssetConverter = (); } parameter_types! { diff --git a/polkadot/runtime/westend/src/xcm_config.rs b/polkadot/runtime/westend/src/xcm_config.rs index f661c4b0e4f4..5f75334c8219 100644 --- a/polkadot/runtime/westend/src/xcm_config.rs +++ b/polkadot/runtime/westend/src/xcm_config.rs @@ -222,6 +222,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type AssetConverter = (); } parameter_types! { From 8e9df560c3efd1be1c544e19fca22b3997c75a91 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Wed, 8 May 2024 02:20:29 +0200 Subject: [PATCH 03/43] feat: add AssetConverters copying the traders --- .../asset-hub-westend/src/xcm_config.rs | 34 +++++++++ .../runtimes/assets/common/Cargo.toml | 3 + .../runtimes/assets/common/src/lib.rs | 63 ++++++++++++++- .../runtimes/testing/penpal/src/lib.rs | 11 ++- .../runtimes/testing/penpal/src/xcm_config.rs | 18 ++++- polkadot/xcm/pallet-xcm/src/lib.rs | 23 +++--- polkadot/xcm/src/v4/traits.rs | 4 +- polkadot/xcm/xcm-executor/src/lib.rs | 76 +++++++++++++++---- 8 files changed, 199 insertions(+), 33 deletions(-) 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 d610bfd768cd..06699bd26b4d 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 @@ -23,7 +23,9 @@ use super::{ use assets_common::{ matching::{FromSiblingParachain, IsForeignConcreteAsset}, TrustBackedAssetsAsLocation, + SufficientAssetConverter, }; +use core::marker::PhantomData; use frame_support::{ parameter_types, traits::{ @@ -349,6 +351,33 @@ pub type TrustedTeleporters = ( IsForeignConcreteAsset>>, ); +/// Asset converter for trust-backed assets. +/// Used to convert assets marked as `sufficient` into the asset needed for fee payment. +/// This type allows paying fees in `sufficient` trust backed-assets. +pub type TrustBackedSufficientAssetsConverter = SufficientAssetConverter< + Runtime, + TrustBackedAssetsConvertedConcreteId, + pallet_assets::BalanceToAssetBalance, + TrustBackedAssetsInstance, +>; + +/// Asset converter for foreign assets. +/// Used to convert assets marked as `sufficient` into the asset needed for fee payment. +/// This type allows paying fees in `sufficient` foreign assets. +pub type ForeignSufficientAssetsConverter = SufficientAssetConverter< + Runtime, + ForeignAssetsConvertedConcreteId, + pallet_assets::BalanceToAssetBalance, + ForeignAssetsInstance, +>; + +/// 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. +// TODO: Finish. +pub type PoolAssetsConverter = (); + pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; @@ -419,6 +448,11 @@ impl xcm_executor::Config for XcmConfig { >, >, ); + type AssetConverter = ( + TrustBackedSufficientAssetsConverter, + ForeignSufficientAssetsConverter, + PoolAssetsConverter, + ); type ResponseHandler = PolkadotXcm; type AssetTrap = PolkadotXcm; type AssetClaims = PolkadotXcm; diff --git a/cumulus/parachains/runtimes/assets/common/Cargo.toml b/cumulus/parachains/runtimes/assets/common/Cargo.toml index 12dfd9da1fff..83b96d3a5bf2 100644 --- a/cumulus/parachains/runtimes/assets/common/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/common/Cargo.toml @@ -20,6 +20,7 @@ frame-support = { path = "../../../../../substrate/frame/support", default-featu sp-api = { path = "../../../../../substrate/primitives/api", default-features = false } sp-std = { path = "../../../../../substrate/primitives/std", default-features = false } sp-runtime = { path = "../../../../../substrate/primitives/runtime", default-features = false } +pallet-assets = { path = "../../../../../substrate/frame/assets", default-features = false } pallet-asset-conversion = { path = "../../../../../substrate/frame/asset-conversion", default-features = false } # Polkadot @@ -42,6 +43,7 @@ std = [ "cumulus-primitives-core/std", "frame-support/std", "log/std", + "pallet-assets/std", "pallet-asset-conversion/std", "pallet-xcm/std", "parachains-common/std", @@ -57,6 +59,7 @@ std = [ runtime-benchmarks = [ "cumulus-primitives-core/runtime-benchmarks", "frame-support/runtime-benchmarks", + "pallet-assets/runtime-benchmarks", "pallet-asset-conversion/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", "parachains-common/runtime-benchmarks", diff --git a/cumulus/parachains/runtimes/assets/common/src/lib.rs b/cumulus/parachains/runtimes/assets/common/src/lib.rs index 431b5766147a..bf3487cda6f2 100644 --- a/cumulus/parachains/runtimes/assets/common/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/common/src/lib.rs @@ -24,13 +24,15 @@ pub mod matching; pub mod runtime_api; use crate::matching::{LocalLocationPattern, ParentLocation}; -use frame_support::traits::{Equals, EverythingBut}; +use core::marker::PhantomData; +use frame_support::traits::{Equals, EverythingBut, tokens::ConversionToAssetBalance}; 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, }; +use xcm_executor::traits::{MatchesFungibles, AssetConversion}; /// `Location` vs `AssetIdForTrustBackedAssets` converter for `TrustBackedAssets` pub type AssetIdForTrustBackedAssetsConvert = @@ -132,6 +134,63 @@ pub type PoolAssetsConvertedConcreteId = TryConvertInto, >; +pub struct SufficientAssetConverter(PhantomData<(Runtime, Matcher, BalanceConverter, AssetsInstance)>); +impl AssetConversion for SufficientAssetConverter +where + Runtime: pallet_assets::Config, + Matcher: MatchesFungibles< + >::AssetId, + >::Balance + >, + BalanceConverter: ConversionToAssetBalance< + u128, + >::AssetId, + >::Balance + >, + AssetsInstance: 'static, +{ + fn convert_asset(asset: &Asset, asset_id: &AssetId) -> Result { + // TODO: Not the best still. + let desired_asset: Asset = (asset_id.clone(), 1u128).into(); // To comply with the interface. + let (local_asset_id, _) = Matcher::matches_fungibles(&desired_asset) + .map_err(|error| { + log::error!( + target: "xcm::SufficientAssetConverter::convert_asset", + "Could not map XCM asset to FRAME asset", + ); + XcmError::AssetNotFound + })?; + log::trace!(target: "xcm::SufficientAssetConverter::convert_asset", "local asset id: {:?}", local_asset_id); + let Fungibility::Fungible(old_asset_amount) = asset.fun else { + log::error!( + target: "xcm::SufficientAssetConverter::convert_asset", + "Fee asset is not fungible", + ); + return Err(XcmError::AssetNotFound); + }; + let new_asset_amount = BalanceConverter::to_asset_balance(old_asset_amount, local_asset_id) + .map_err(|error| { + log::error!( + target: "xcm::SufficientAssetConverter::convert_asset", + "Couldn't convert balance of {:?} to balance of {:?}", + asset.id, + desired_asset.id, + ); + XcmError::TooExpensive + })?; + let new_asset_amount: u128 = new_asset_amount.try_into() + .map_err(|error| { + log::error!( + target: "xcm::SufficientAssetConverter::convert_asset", + "Converting balance of {:?} to u128 would overflow", + desired_asset.id, + ); + XcmError::Overflow + })?; + Ok((desired_asset.id.clone(), new_asset_amount).into()) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs index 89885d77378b..538bda8ce4f3 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs @@ -42,6 +42,7 @@ use frame_support::{ parameter_types, traits::{ AsEnsureOriginWithArg, ConstBool, ConstU32, ConstU64, ConstU8, Everything, TransformOrigin, + PalletInfoAccess, }, weights::{ constants::WEIGHT_REF_TIME_PER_SECOND, ConstantMultiplier, FeePolynomial, @@ -80,7 +81,7 @@ pub use sp_runtime::BuildStorage; use parachains_common::{AccountId, Signature}; use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate}; use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; -use xcm::latest::prelude::{AssetId as AssetLocationId, BodyId}; +use xcm::latest::prelude::{AssetId as AssetLocationId, BodyId, PalletInstance, Location}; /// Balance of an account. pub type Balance = u128; @@ -432,7 +433,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; @@ -628,9 +631,9 @@ impl pallet_asset_tx_payment::Config for Runtime { Balances, Runtime, ConvertInto, - pallet_assets::Instance1, + TrustBackedAssetsInstance, >, - AssetsToBlockAuthor, + AssetsToBlockAuthor, >; } diff --git a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs index 711041f6d6e2..e7a3b835f83d 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs @@ -26,9 +26,10 @@ use super::{ AccountId, AllPalletsWithSystem, AssetId as AssetIdPalletAssets, Assets, Authorship, Balance, Balances, ForeignAssets, ForeignAssetsInstance, NonZeroIssuance, ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, WeightToFee, - XcmpQueue, + XcmpQueue, TrustBackedAssetsInstance, }; use crate::{BaseDeliveryFee, FeeAssetId, TransactionByteFee}; +use assets_common::SufficientAssetConverter; use core::marker::PhantomData; use frame_support::{ parameter_types, @@ -312,6 +313,20 @@ pub type TrustedReserves = ( pub type TrustedTeleporters = (AssetFromChain,); +/// `AssetId`/`Balance` converter for `TrustBackedAssets`. +pub type TrustBackedAssetsConvertedConcreteId = + assets_common::TrustBackedAssetsConvertedConcreteId; + +/// Asset converter for trust-backed assets. +/// Used to convert assets marked as `sufficient` into the asset needed for fee payment. +/// This type allows paying fees in `sufficient` trust backed-assets. +pub type TrustBackedSufficientAssetsConverter = SufficientAssetConverter< + Runtime, + TrustBackedAssetsConvertedConcreteId, + pallet_assets::BalanceToAssetBalance, + TrustBackedAssetsInstance, +>; + pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; @@ -362,6 +377,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type AssetConverter = TrustBackedSufficientAssetsConverter; } /// Multiplier used for dedicated `TakeFirstAssetTrader` with `ForeignAssets` instance. diff --git a/polkadot/xcm/pallet-xcm/src/lib.rs b/polkadot/xcm/pallet-xcm/src/lib.rs index af3b66121ea1..d3940836bafe 100644 --- a/polkadot/xcm/pallet-xcm/src/lib.rs +++ b/polkadot/xcm/pallet-xcm/src/lib.rs @@ -1568,10 +1568,10 @@ impl Pallet { Either::Left(beneficiary), assets, assets_transfer_type, - FeesHandling::Batched { fees }, + FeesHandling::Batched { fees: fees.clone() }, weight_limit, )?; - Self::execute_xcm_transfer(origin, dest, local_xcm, remote_xcm) + Self::execute_xcm_transfer(origin, dest, local_xcm, remote_xcm, fees.id) } fn do_teleport_assets( @@ -1610,10 +1610,10 @@ impl Pallet { Either::Left(beneficiary), assets, TransferType::Teleport, - FeesHandling::Batched { fees }, + FeesHandling::Batched { fees: fees.clone() }, weight_limit, )?; - Self::execute_xcm_transfer(origin_location, dest, local_xcm, remote_xcm) + Self::execute_xcm_transfer(origin_location, dest, local_xcm, remote_xcm, fees.id) } fn do_transfer_assets( @@ -1626,11 +1626,11 @@ impl Pallet { fees_transfer_type: TransferType, weight_limit: WeightLimit, ) -> DispatchResult { + let asset_for_fees = assets.get(fee_asset_index).ok_or(Error::::Empty)?.clone(); // local and remote XCM programs to potentially handle fees separately let fees = if fees_transfer_type == assets_transfer_type { - let fees = assets.get(fee_asset_index).ok_or(Error::::Empty)?.clone(); // no need for custom fees instructions, fees are batched with assets - FeesHandling::Batched { fees } + FeesHandling::Batched { fees: asset_for_fees.clone() } } else { // Disallow _remote reserves_ unless assets & fees have same remote reserve (covered // by branch above). The reason for this is that we'd need to send XCMs to separate @@ -1679,7 +1679,7 @@ impl Pallet { fees, weight_limit, )?; - Self::execute_xcm_transfer(origin, dest, local_xcm, remote_xcm) + Self::execute_xcm_transfer(origin, dest, local_xcm, remote_xcm, asset_for_fees.id) } fn build_xcm_transfer_type( @@ -1749,6 +1749,7 @@ impl Pallet { dest: Location, mut local_xcm: Xcm<::RuntimeCall>, remote_xcm: Option>, + asset_for_fees: AssetId, ) -> DispatchResult { log::debug!( target: "xcm::pallet_xcm::execute_xcm_transfer", @@ -1779,7 +1780,7 @@ impl Pallet { let (ticket, price) = validate_send::(dest.clone(), remote_xcm.clone()) .map_err(Error::::from)?; if origin != Here.into_location() { - Self::charge_fees(origin.clone(), price).map_err(|error| { + Self::charge_fees(origin.clone(), price, &asset_for_fees).map_err(|error| { log::error!( target: "xcm::pallet_xcm::execute_xcm_transfer", "Unable to charge fee with error {:?}", error @@ -2403,7 +2404,7 @@ impl Pallet { log::debug!(target: "xcm::send_xcm", "dest: {:?}, message: {:?}", &dest, &message); let (ticket, price) = validate_send::(dest, message)?; if let Some(fee_payer) = maybe_fee_payer { - Self::charge_fees(fee_payer, price).map_err(|_| SendError::Fees)?; + Self::charge_fees(fee_payer, price, &AssetId(Here.into())).map_err(|_| SendError::Fees)?; } T::XcmRouter::deliver(ticket) } @@ -2546,8 +2547,8 @@ impl Pallet { /// Fails if: /// - the `assets` are not known on this chain; /// - the `assets` cannot be withdrawn with that location as the Origin. - fn charge_fees(location: Location, assets: Assets) -> DispatchResult { - T::XcmExecutor::charge_fees(location.clone(), assets.clone()) + fn charge_fees(location: Location, assets: Assets, asset_for_fees: &AssetId) -> DispatchResult { + T::XcmExecutor::charge_fees(location.clone(), assets.clone(), asset_for_fees) .map_err(|_| Error::::FeesNotMet)?; Self::deposit_event(Event::FeesPaid { paying: location, fees: assets }); Ok(()) diff --git a/polkadot/xcm/src/v4/traits.rs b/polkadot/xcm/src/v4/traits.rs index f6136c76d808..930ae864a763 100644 --- a/polkadot/xcm/src/v4/traits.rs +++ b/polkadot/xcm/src/v4/traits.rs @@ -102,7 +102,7 @@ pub trait ExecuteXcm { /// Deduct some `fees` to the sovereign account of the given `location` and place them as per /// the convention for fees. - fn charge_fees(location: impl Into, fees: Assets) -> Result; + fn charge_fees(location: impl Into, fees: Assets, asset_for_fees: &AssetId) -> Result; } pub enum Weightless {} @@ -120,7 +120,7 @@ impl ExecuteXcm for () { fn execute(_: impl Into, _: Self::Prepared, _: &mut XcmHash, _: Weight) -> Outcome { unreachable!() } - fn charge_fees(_location: impl Into, _fees: Assets) -> Result { + fn charge_fees(_location: impl Into, _fees: Assets, _asset_for_fees: &AssetId) -> Result { Err(Error::Unimplemented) } } diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index a7052328da00..884e936d094d 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -30,7 +30,7 @@ use xcm::latest::prelude::*; pub mod traits; use traits::{ - validate_export, AssetExchange, AssetLock, CallDispatcher, ClaimAssets, ConvertOrigin, + validate_export, AssetExchange, AssetConversion, AssetLock, CallDispatcher, ClaimAssets, ConvertOrigin, DropAssets, Enact, ExportXcm, FeeManager, FeeReason, HandleHrmpChannelAccepted, HandleHrmpChannelClosing, HandleHrmpNewChannelOpenRequest, OnResponse, ProcessTransaction, Properties, ShouldExecute, TransactAsset, VersionChangeNotifier, WeightBounds, WeightTrader, @@ -78,6 +78,7 @@ pub struct XcmExecutor { appendix_weight: Weight, transact_status: MaybeErrorCode, fees_mode: FeesMode, + asset_for_fees: Option, _config: PhantomData, } @@ -245,12 +246,32 @@ impl ExecuteXcm for XcmExecutor, fees: Assets) -> XcmResult { + fn charge_fees(origin: impl Into, fees: Assets, asset_for_fees: &AssetId) -> XcmResult { let origin = origin.into(); if !Config::FeeManager::is_waived(Some(&origin), FeeReason::ChargeFees) { - for asset in fees.inner() { - Config::AssetTransactor::withdraw_asset(&asset, &origin, None)?; - } + // We allow only one asset for fee payment. + log::trace!(target: "xcm::charge_fees", "Fees: {:?}", fees); + let first = fees.get(0).ok_or(XcmError::AssetNotFound)?; + log::trace!(target: "xcm::charge_fees", "Old asset: {:?}", first); + // New config item, it will just return `first` unaffected if there's no + // way to convert it. This will result in an error later if they're not present. + log::trace!(target: "xcm::charge_fees", "Asset for fees: {:?}", asset_for_fees); + // If no conversion can be made, we use the original asset even if it's not + // the desired one, as best effort. + let actual_asset_to_use = match Config::AssetConverter::convert_asset(&first, asset_for_fees) { + Ok(new_asset) => new_asset, + Err(error) => { + log::error!( + target: "xcm::charge_fees", + "Could not convert fees to {:?}. Error: {:?}", + asset_for_fees, + error, + ); + first.clone() + }, + }; + log::trace!(target: "xcm::charge_fees", "New asset: {:?}", actual_asset_to_use); + Config::AssetTransactor::withdraw_asset(&actual_asset_to_use, &origin, None)?; Config::FeeManager::handle_fee(fees, None, FeeReason::ChargeFees); } Ok(()) @@ -301,6 +322,7 @@ impl XcmExecutor { appendix_weight: Weight::zero(), transact_status: Default::default(), fees_mode: FeesMode { jit_withdraw: false }, + asset_for_fees: None, _config: PhantomData, } } @@ -435,26 +457,42 @@ 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(()) } log::trace!( target: "xcm::fees", - "taking fee: {:?} from origin_ref: {:?} in fees_mode: {:?} for a reason: {:?}", - fee, + "taking fees: {:?} from origin_ref: {:?} in fees_mode: {:?} for a reason: {:?}", + fees, self.origin_ref(), self.fees_mode, reason, ); + let first = fees.get(0).ok_or(XcmError::AssetNotFound)?; + // TODO: The problem here is `asset_for_fees` is None since no `BuyExecution` was called. + let actual_asset_to_use = if let Some(asset_for_fees) = &self.asset_for_fees { + match Config::AssetConverter::convert_asset(&first, &asset_for_fees) { + Ok(new_asset) => new_asset, + Err(error) => { + log::error!( + target: "xcm::take_fee", + "Could not convert fees to {:?}. Error: {:?}", + asset_for_fees, + error, + ); + first.clone() + }, + } + } else { + first.clone() + }; 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))?; - } - fee + Config::AssetTransactor::withdraw_asset(&actual_asset_to_use, origin, Some(&self.context))?; + vec![actual_asset_to_use].into() } else { - self.holding.try_take(fee.into()).map_err(|_| XcmError::NotHoldingFees)?.into() + self.holding.try_take(actual_asset_to_use.into()).map_err(|_| XcmError::NotHoldingFees)?.into() }; Config::FeeManager::handle_fee(paid, Some(&self.context), reason); Ok(()) @@ -843,6 +881,16 @@ impl XcmExecutor { message_to_weigh.extend(xcm.0.clone().into_iter()); let (_, fee) = validate_send::(dest.clone(), Xcm(message_to_weigh))?; + let first = fee.get(0).ok_or(XcmError::AssetNotFound)?; + let asset_id = self.asset_for_fees.as_ref().unwrap_or(&first.id); + // TODO: Deal with this case. + // Need to make a test specifically for this. + let actual_asset_to_use = match Config::AssetConverter::convert_asset(&first, asset_id) { + Ok(new_asset) => new_asset, + Err(error) => { + todo!() + }, + }; // set aside fee to be charged by XcmSender let transport_fee = self.holding.saturating_take(fee.into()); @@ -937,6 +985,8 @@ impl XcmExecutor { let Some(weight) = Option::::from(weight_limit) else { return Ok(()) }; let old_holding = self.holding.clone(); // pay for `weight` using up to `fees` of the holding register. + self.asset_for_fees = Some(fees.id.clone()); + log::trace!(target: "xcm::executor::BuyExecution", "Asset for fees: {:?}", self.asset_for_fees); let max_fee = self.holding.try_take(fees.into()).map_err(|_| XcmError::NotHoldingFees)?; let result = || -> Result<(), XcmError> { From 3ed04307141f7dc4fcc1a54098a962dcc227dfb6 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Wed, 8 May 2024 02:22:31 +0200 Subject: [PATCH 04/43] test(asset-hub-westend): add tests for paying delivery fees with different assets --- .../src/tests/hybrid_transfers.rs | 149 +++++++++++ .../src/tests/reserve_transfer.rs | 246 ++++++++++++++++++ 2 files changed, 395 insertions(+) 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 d39c72c7c5f0..e8aeabdc6164 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 @@ -275,6 +275,155 @@ fn transfer_foreign_assets_from_asset_hub_to_para() { assert_eq!(receiver_rocs_after, receiver_rocs_before + foreign_amount_to_send); } +// ========================================================================= +// ======= Reserve Transfers - Sufficient Foreign Asset - AssetHub<>Parachain ====== +// ========================================================================= +/// Reserve Transfers of a sufficient foreign asset from System Parachain to Parachain should +/// work without using any other token. +#[test] +fn reserve_transfer_sufficient_foreign_assets_from_system_para_to_para() { + use penpal_runtime::xcm_config::ASSET_HUB_ID; + + let destination = AssetHubWestend::sibling_location_of(PenpalA::para_id()); + let sov_penpal_on_ahr = AssetHubWestend::sovereign_account_id_of(destination.clone()); + let sender = AssetHubWestendSender::get(); + let fee_amount_to_send = ASSET_HUB_WESTEND_ED * 100; + let asset_amount_to_send = ASSET_HUB_WESTEND_ED * 100; + let asset_owner = AssetHubWestendAssetOwner::get(); + let asset_owner_signer = ::RuntimeOrigin::signed(asset_owner.clone()); + let receiver = PenpalAReceiver::get(); + + let roc_at_westend_parachains = Location::new(2, GlobalConsensus(NetworkId::Rococo)); + // Configure destination chain to trust AH as reserve of ROC + PenpalA::execute_with(|| { + assert_ok!(::System::set_storage( + ::RuntimeOrigin::root(), + vec![( + penpal_runtime::xcm_config::CustomizableAssetFromSystemAssetHub::key().to_vec(), + roc_at_westend_parachains.encode(), + )], + )); + }); + // Create sufficient asset. + PenpalA::force_create_foreign_asset( + roc_at_westend_parachains.clone(), + PenpalAssetOwner::get(), + true, // Mark it as sufficient. + 10_000_000_000, + vec![], + ); + AssetHubWestend::force_create_foreign_asset( + roc_at_westend_parachains.clone().try_into().unwrap(), + asset_owner, + true, // Mark it as sufficient. + 10_000_000_000, + vec![(sender.clone(), ASSET_HUB_WESTEND_ED * 10000)], + ); + + let assets: Assets = vec![ + ( + (Parent, Parent, GlobalConsensus(NetworkId::Rococo)), + asset_amount_to_send + fee_amount_to_send, + ).into(), + ].into(); + // Just to be very clear we don't need to send any native asset. + assert_eq!(assets.len(), 1); + let fee_asset_index = 0; + + // Create SA-of-Penpal-on-AHR with ED. + // This ED isn't reflected in any derivative in a PenpalA account. + AssetHubWestend::fund_accounts(vec![(sov_penpal_on_ahr.into(), ASSET_HUB_WESTEND_ED)]); + + // We want to make sure the sender doesn't have ANY native asset other than ED, so we can make + // sure we are paying delivery fees with the sufficient asset. + AssetHubWestend::execute_with(|| { + assert_ok!(::Balances::force_set_balance( + ::RuntimeOrigin::root(), + sender.clone().into(), + ASSET_HUB_WESTEND_ED, + )); + }); + + let para_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 = SystemParaToParaTest::new(para_test_args); + + // Query initial balances. + let sender_balance_before = test.sender.balance; + let sender_foreign_assets_before = AssetHubWestend::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(roc_at_westend_parachains.clone().try_into().unwrap(), &sender) + }); + let receiver_foreign_assets_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance( + roc_at_westend_parachains.clone(), + &receiver, + ) + }); + + // Set assertions and dispatchables + test.set_assertion::(system_para_to_para_sender_sufficient_foreign_asset_assertions); + test.set_assertion::(system_para_to_para_receiver_assertions); + test.set_dispatchable::(ah_to_para_transfer_assets); + test.assert(); + + // Query initial balances. + let sender_balance_after = test.sender.balance; + let sender_foreign_assets_after = AssetHubWestend::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(roc_at_westend_parachains.clone().try_into().unwrap(), &sender) + }); + let receiver_foreign_assets_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance( + roc_at_westend_parachains.clone(), + &receiver, + ) + }); + + // The native asset balance is the same. They weren't send or used for fees. + assert_eq!(sender_balance_before, sender_balance_after); + // The assets have been sent and used for fees. + assert!(sender_foreign_assets_after < sender_foreign_assets_before - asset_amount_to_send); + assert!(receiver_foreign_assets_after > receiver_foreign_assets_before); +} + +pub fn system_para_to_para_sender_sufficient_foreign_asset_assertions(t: SystemParaToParaTest) { + type RuntimeEvent = ::RuntimeEvent; + AssetHubWestend::assert_xcm_pallet_attempted_complete(None); + + let sov_account_of_dest = AssetHubWestend::sovereign_account_id_of(t.args.dest.clone()); + for (index, asset) in t.args.assets.into_inner().into_iter().enumerate() { + let expected_id = asset.id.0.clone().try_into().unwrap(); + let asset_amount = if let Fungible(a) = asset.fun { Some(a) } else { None }.unwrap(); + assert_expected_events!( + AssetHubWestend, + vec![ + // Amount of foreign asset is transferred to Parachain's sovereign account. + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Transferred { asset_id, from, to, amount }, + ) => { + asset_id: *asset_id == expected_id, + from: *from == t.sender.account_id, // Sender is test sender. + to: *to == sov_account_of_dest, // Receiver is parachain's sovereign account. + amount: *amount == asset_amount, // Asset amount is the one specified in the test. + }, + ] + ); + } +} + /// Reserve Transfers of native asset from Parachain to System Parachain should work // =========================================================================== // ======= Transfer - Native + Bridged Assets - Parachain->AssetHub ========== 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..8691bb04ee2e 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() { @@ -1137,3 +1150,236 @@ 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 - Sufficient Asset - AssetHub->Parachain ====== +// ========================================================================= +/// Reserve Transfers of a sufficient asset from System Parachain to Parachain should +/// work without using any other token. +#[test] +fn reserve_transfer_sufficient_assets_from_system_para_to_para() { + use penpal_runtime::xcm_config::ASSET_HUB_ID; + + let destination = AssetHubWestend::sibling_location_of(PenpalA::para_id()); + let sov_penpal_on_ahr = AssetHubWestend::sovereign_account_id_of(destination.clone()); + let sender = AssetHubWestendSender::get(); + let fee_amount_to_send = ASSET_HUB_WESTEND_ED * 100; + let asset_amount_to_send = ASSET_HUB_WESTEND_ED * 100; + let asset_owner = AssetHubWestendAssetOwner::get(); + let asset_owner_signer = ::RuntimeOrigin::signed(asset_owner.clone()); + let receiver = PenpalAReceiver::get(); + + // Create sufficient asset. + let asset_id = 1984; + AssetHubWestend::force_create_asset( + asset_id, + asset_owner, + true, // Mark it as sufficient. + 70_000, + vec![(sender.clone(), ASSET_HUB_WESTEND_ED * 10000)], + ); + let system_para_foreign_asset_location = Location::new( + 1, + [ + Parachain(ASSET_HUB_ID), + PalletInstance(ASSETS_PALLET_ID), + GeneralIndex(asset_id.into()), + ], + ); + + // Create sufficient foreign asset. + PenpalA::force_create_foreign_asset( + system_para_foreign_asset_location.clone(), + PenpalAssetOwner::get(), + true, // Mark it as sufficient. + 70_000, + vec![], + ); + + let assets: Assets = vec![ + ( + (PalletInstance(ASSETS_PALLET_ID), GeneralIndex(asset_id.into())), + asset_amount_to_send + fee_amount_to_send, + ).into(), + ].into(); + // Just to be very specific you don't need any native asset. + assert_eq!(assets.len(), 1); + let fee_asset_index = 0; + + // Create SA-of-Penpal-on-AHR with ED. + // This ED isn't reflected in any derivative in a PenpalA account. + AssetHubWestend::fund_accounts(vec![(sov_penpal_on_ahr.into(), ASSET_HUB_WESTEND_ED)]); + + // We want to make sure the sender doesn't have ANY native asset, so we can make + // sure we are paying delivery fees with the sufficient asset. + ::execute_with(|| { + assert_ok!(::Balances::force_set_balance( + ::RuntimeOrigin::root(), + sender.clone().into(), + ASSET_HUB_WESTEND_ED, + )); + }); + + let para_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 = SystemParaToParaTest::new(para_test_args); + + // Query initial balances. + let sender_balance_before = test.sender.balance; + let sender_assets_before = AssetHubWestend::execute_with(|| { + type Assets = ::Assets; + >::balance(asset_id, &sender) + }); + let receiver_foreign_assets_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance( + system_para_foreign_asset_location.clone(), + &receiver, + ) + }); + + // Set assertions and dispatchables + // test.set_assertion::(system_para_to_para_assets_sender_assertions); + // test.set_assertion::(system_para_to_para_assets_receiver_assertions); + test.set_dispatchable::(system_para_to_para_reserve_transfer_assets); + test.assert(); + + // Query initial balances. + let sender_balance_after = test.sender.balance; + let sender_assets_after = AssetHubWestend::execute_with(|| { + type Assets = ::Assets; + >::balance(asset_id, &sender) + }); + let receiver_foreign_assets_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance( + system_para_foreign_asset_location.clone(), + &receiver, + ) + }); + + // The native asset balance is the same. They weren't send or used for fees. + assert_eq!(sender_balance_before, sender_balance_after); + // The assets have been sent and used for fees. + assert!(sender_assets_after < sender_assets_before - asset_amount_to_send); + assert!(receiver_foreign_assets_after > receiver_foreign_assets_before); +} + +// ================================================================================ +// ===== Reserve Transfers - Sufficient Asset - Parachain->AssetHub->Parachain ==== +// ================================================================================ +/// Reserve Transfers of a sufficient trust-backed asset from Parachain to Parachain (through Relay reserve) should +/// work +#[test] +fn reserve_transfer_sufficient_asset_from_para_to_para_through_asset_hub() { + use penpal_runtime::xcm_config::ASSET_HUB_ID; + + // Init values for Parachain Origin + 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 asset_owner = PenpalAssetOwner::get(); + let sender_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_as_seen_by_asset_hub); + + // Create sufficient asset. + let asset_id = 1984; + AssetHubWestend::force_create_asset( + asset_id, + asset_owner, + true, // Mark it as sufficient. + 70_000, + vec![(sov_of_sender_on_asset_hub.clone(), asset_amount_to_send + fee_amount_to_send)], + ); + let system_para_foreign_asset_location = Location::new( + 1, + [ + Parachain(ASSET_HUB_ID), + PalletInstance(ASSETS_PALLET_ID), + GeneralIndex(asset_id.into()), + ], + ); + + // Create sufficient foreign asset. + PenpalA::force_create_foreign_asset( + system_para_foreign_asset_location.clone(), + PenpalAssetOwner::get(), + true, // Mark it as sufficient. + 70_000, + vec![(sender.clone(), asset_amount_to_send + fee_amount_to_send)], + ); + PenpalB::force_create_foreign_asset( + system_para_foreign_asset_location.clone(), + PenpalAssetOwner::get(), + true, // Mark it as sufficient. + 70_000, + vec![], + ); + + let assets: Assets = vec![ + ( + (Parent, Parachain(ASSET_HUB_ID), PalletInstance(ASSETS_PALLET_ID), GeneralIndex(asset_id.into())), + asset_amount_to_send + fee_amount_to_send, + ).into(), + ].into(); + // Just to be very specific you don't need any native asset. + assert_eq!(assets.len(), 1); + let fee_asset_index = 0; + + // fund the Parachain Origin's SA on Relay Chain with the native tokens held in reserve + AssetHubWestend::fund_accounts(vec![(sov_of_sender_on_asset_hub.into(), asset_amount_to_send * 2)]); + + // Init values for Parachain Destination + let receiver = PenpalBReceiver::get(); + + // Init Test + let test_args = TestContext { + sender: sender.clone(), + receiver: receiver.clone(), + args: TestArgs::new_para(destination, receiver.clone(), asset_amount_to_send, assets, None, 0), + }; + let mut test = ParaToParaThroughAHTest::new(test_args); + + // Query initial balances + let sender_assets_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(system_para_foreign_asset_location.clone(), &sender) + }); + let receiver_assets_before = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(system_para_foreign_asset_location.clone(), &receiver) + }); + + // Set assertions and dispatchables + // test.set_assertion::(para_to_para_through_hop_sender_assertions); + // test.set_assertion::(para_to_para_relay_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(system_para_foreign_asset_location.clone(), &sender) + }); + let receiver_assets_after = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(system_para_foreign_asset_location, &receiver) + }); + + // Sender's balance is reduced by amount sent plus delivery fees + assert!(sender_assets_after < sender_assets_before - asset_amount_to_send); + // Receiver's balance is increased + assert!(receiver_assets_after > receiver_assets_before); +} From da83c684e9c04dc3a0dfbd36bb7d50b15fe7c75f Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Wed, 8 May 2024 02:22:45 +0200 Subject: [PATCH 05/43] fix: update lock --- Cargo.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.lock b/Cargo.lock index 1fe4012070a0..258c134442ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1050,6 +1050,7 @@ dependencies = [ "impl-trait-for-tuples", "log", "pallet-asset-conversion", + "pallet-assets", "pallet-xcm", "parachains-common", "parity-scale-codec", From efab825587ceb509d769ae3817c326f5ec8ab6bb Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Sat, 11 May 2024 10:39:09 +0200 Subject: [PATCH 06/43] fix(pallet-xcm): remove jit withdrawal to allow paying delivery fees with different assets --- .../src/tests/hybrid_transfers.rs | 12 ++- .../src/tests/reserve_transfer.rs | 8 +- .../runtimes/assets/common/src/lib.rs | 3 +- .../runtimes/testing/penpal/src/xcm_config.rs | 23 +++++- polkadot/xcm/pallet-xcm/src/lib.rs | 1 - polkadot/xcm/xcm-executor/src/lib.rs | 77 +++++++++++++++++-- 6 files changed, 108 insertions(+), 16 deletions(-) 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 e8aeabdc6164..8c29f37634f3 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 @@ -762,13 +762,16 @@ 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 - assert!(sender_wnds_after < sender_wnds_before - wnd_to_send); + // Sender's balance is reduced by amount sent. + assert_eq!(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 + let delivery_fees_amount = 31_340_000_000; // TODO: Estimate this. + // Delivery fees stay in the sender chain, so the balance of the sender's + // sovereign account reflects this. assert_eq!( wnds_in_sender_reserve_on_ah_after, - wnds_in_sender_reserve_on_ah_before - wnd_to_send + wnds_in_sender_reserve_on_ah_before - wnd_to_send + delivery_fees_amount ); assert_eq!( rocs_in_sender_reserve_on_ah_after, @@ -779,8 +782,9 @@ 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!(receiver_wnds_after < receiver_wnds_before + wnd_to_send); 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 8691bb04ee2e..ea7cd65b6856 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 @@ -1145,10 +1145,12 @@ 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 - assert!(sender_assets_after < sender_assets_before - amount_to_send); - // Receiver's balance is increased + // Sender's balance is reduced by amount sent plus delivery fees. + // Delivery fees are taken from the amount to send. + assert_eq!(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); } // ========================================================================= diff --git a/cumulus/parachains/runtimes/assets/common/src/lib.rs b/cumulus/parachains/runtimes/assets/common/src/lib.rs index bf3487cda6f2..f617a315b0a8 100644 --- a/cumulus/parachains/runtimes/assets/common/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/common/src/lib.rs @@ -156,7 +156,8 @@ where .map_err(|error| { log::error!( target: "xcm::SufficientAssetConverter::convert_asset", - "Could not map XCM asset to FRAME asset", + "Could not map XCM asset {:?} to FRAME asset", + asset_id, ); XcmError::AssetNotFound })?; diff --git a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs index e7a3b835f83d..11b5bcb0229a 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs @@ -327,6 +327,23 @@ pub type TrustBackedSufficientAssetsConverter = SufficientAssetConverter< TrustBackedAssetsInstance, >; +/// Asset converter for foreign assets. +/// Used to convert assets marked as `sufficient` into the asset needed for fee payment. +/// This type allows paying fees in `sufficient` foreign assets. +pub type ForeignSufficientAssetsConverter = SufficientAssetConverter< + Runtime, + ForeignAssetsConvertedConcreteId, + pallet_assets::BalanceToAssetBalance, + ForeignAssetsInstance, +>; + +/// 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. +// TODO: Finish. +pub type PoolAssetsConverter = (); + pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; @@ -377,7 +394,11 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); - type AssetConverter = TrustBackedSufficientAssetsConverter; + type AssetConverter = ( + TrustBackedSufficientAssetsConverter, + ForeignSufficientAssetsConverter, + PoolAssetsConverter, + ); } /// Multiplier used for dedicated `TakeFirstAssetTrader` with `ForeignAssets` instance. diff --git a/polkadot/xcm/pallet-xcm/src/lib.rs b/polkadot/xcm/pallet-xcm/src/lib.rs index d3940836bafe..288a78ed223c 100644 --- a/polkadot/xcm/pallet-xcm/src/lib.rs +++ b/polkadot/xcm/pallet-xcm/src/lib.rs @@ -2035,7 +2035,6 @@ impl Pallet { ]); Ok(Xcm(vec![ WithdrawAsset(assets.into()), - SetFeesMode { jit_withdraw: true }, InitiateReserveWithdraw { assets: Wild(AllCounted(max_assets)), reserve, diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index 884e936d094d..7c4409fdb413 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -380,6 +380,11 @@ impl XcmExecutor { target: "xcm::send", "Sending msg: {msg:?}, to destination: {dest:?}, (reason: {reason:?})" ); let (ticket, fee) = validate_send::(dest, msg)?; + // `take_fee` takes in the fee in DOT. + // It should use `AssetConverter` to change it to USDC, for example. + // How it knows that is a mystery. + // `self.asset_for_fees` is not populated most of the time. + // `self.holding` can only have enough USDC if I do the same calculation before to park the fees. self.take_fee(fee, reason)?; Config::XcmSender::deliver(ticket).map_err(Into::into) } @@ -470,10 +475,13 @@ impl XcmExecutor { reason, ); let first = fees.get(0).ok_or(XcmError::AssetNotFound)?; - // TODO: The problem here is `asset_for_fees` is None since no `BuyExecution` was called. + // If `BuyExecution` was called, we know we can try to use that asset for fees. + log::trace!(target: "xcm", "Asset for fees: {:?}", self.asset_for_fees); let actual_asset_to_use = if let Some(asset_for_fees) = &self.asset_for_fees { match Config::AssetConverter::convert_asset(&first, &asset_for_fees) { Ok(new_asset) => new_asset, + // If we can't convert, then we return the original asset. + // It will error later in any case. Err(error) => { log::error!( target: "xcm::take_fee", @@ -485,8 +493,34 @@ impl XcmExecutor { }, } } else { - first.clone() + // We try each item in holding. + // TODO: Might increase benchmarks too much. Should optimize for the average case. + let mut asset_for_fees = first.clone(); + // Holding is empty when using jit withdrawal. Could remove it. + // It's not empty because of jit withdrawal, it's empty because it's taken from holding before sending. + // That's the problem jit withdrawal was solving, we need to "park" delivery fees in the same way we did + // for `DepositReserveAsset`. + for asset in self.holding.fungible_assets_iter() { + log::trace!(target: "xcm", "Asset being tested to convert: {:?}", asset); + match Config::AssetConverter::convert_asset(&first, &asset.id) { + Ok(new_asset) => { + asset_for_fees = new_asset; + break; + }, + Err(error) => { + log::error!( + target: "xcm::take_fee", + "Could not convert fees to {:?}. Error: {:?}", + asset.id, + error, + ); + continue; + } + } + } + asset_for_fees }; + log::trace!(target: "xcm", "Actual asset for fees: {:?}", actual_asset_to_use); let paid = if self.fees_mode.jit_withdraw { let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; Config::AssetTransactor::withdraw_asset(&actual_asset_to_use, origin, Some(&self.context))?; @@ -885,14 +919,15 @@ impl XcmExecutor { let asset_id = self.asset_for_fees.as_ref().unwrap_or(&first.id); // TODO: Deal with this case. // Need to make a test specifically for this. - let actual_asset_to_use = match Config::AssetConverter::convert_asset(&first, asset_id) { + let actual_asset_to_use_for_fees = match Config::AssetConverter::convert_asset(&first, asset_id) { Ok(new_asset) => new_asset, Err(error) => { - todo!() + log::error!(target: "xcm::DepositReserveAsset", "What happened?"); + first.clone() }, }; // set aside fee to be charged by XcmSender - let transport_fee = self.holding.saturating_take(fee.into()); + let transport_fee = self.holding.saturating_take(actual_asset_to_use_for_fees.into()); // now take assets to deposit (excluding transport_fee) let deposited = self.holding.saturating_take(assets); @@ -918,6 +953,31 @@ impl XcmExecutor { InitiateReserveWithdraw { assets, reserve, xcm } => { let old_holding = self.holding.clone(); let result = Config::TransactionalProcessor::process(|| { + // We need to do this take/put cycle to solve wildcards and get exact assets to + // be weighed. + let to_weigh = self.holding.saturating_take(assets.clone()); + self.holding.subsume_assets(to_weigh.clone()); + let to_weigh_reanchored = Self::reanchored(to_weigh, &reserve, None); // TODO: Is `None` fine here? + let mut message_to_weigh = vec![WithdrawAsset(to_weigh_reanchored), ClearOrigin]; + message_to_weigh.extend(xcm.0.clone().into_iter()); + let (_, delivery_fees) = validate_send::(reserve.clone(), Xcm(message_to_weigh))?; + let first = delivery_fees.get(0).ok_or(XcmError::AssetNotFound)?; + let actual_asset_to_use_for_fees = { + let mut asset_to_use_for_fees = first.clone(); + // We loop through all assets in holding. + for asset in self.holding.fungible_assets_iter() { + match Config::AssetConverter::convert_asset(&first, &asset.id) { + Ok(new_asset) => { + asset_to_use_for_fees = new_asset; + break; + }, + Err(_) => continue, + }; + } + asset_to_use_for_fees + }; + log::trace!(target: "xcm", "Asset to use for fees in InitiateReserveWithdraw: {:?}", actual_asset_to_use_for_fees); + let parked_delivery_fee = self.holding.saturating_take(actual_asset_to_use_for_fees.into()); // Note that here we are able to place any assets which could not be reanchored // back into Holding. let assets = Self::reanchored( @@ -927,6 +987,10 @@ impl XcmExecutor { ); let mut message = vec![WithdrawAsset(assets), ClearOrigin]; message.extend(xcm.0.into_iter()); + // Put back delivery fees in holding register to be charged by XcmSender. + self.holding.subsume_assets(parked_delivery_fee); + // TODO: It would be great if we could handle all conversion in `take_fees`. + // Don't know if possible. self.send(reserve, Xcm(message), FeeReason::InitiateReserveWithdraw)?; Ok(()) }); @@ -984,9 +1048,10 @@ impl XcmExecutor { // should be executed. let Some(weight) = Option::::from(weight_limit) else { return Ok(()) }; let old_holding = self.holding.clone(); - // pay for `weight` using up to `fees` of the holding register. + // Store the asset being used for fees, so delivery fees can access it. self.asset_for_fees = Some(fees.id.clone()); log::trace!(target: "xcm::executor::BuyExecution", "Asset for fees: {:?}", self.asset_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)?; let result = || -> Result<(), XcmError> { From da7b05420132792ca7aad064254b81937effc20b Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Sat, 11 May 2024 12:43:38 +0200 Subject: [PATCH 07/43] doc: add TODOs I'm considering --- .../asset-hub-westend/src/tests/hybrid_transfers.rs | 4 ++-- .../asset-hub-westend/src/tests/reserve_transfer.rs | 2 ++ polkadot/xcm/xcm-executor/src/lib.rs | 10 ++++++++++ polkadot/xcm/xcm-executor/src/traits/fee_manager.rs | 1 + 4 files changed, 15 insertions(+), 2 deletions(-) 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 8c29f37634f3..34e577dc1efa 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 @@ -275,9 +275,9 @@ fn transfer_foreign_assets_from_asset_hub_to_para() { assert_eq!(receiver_rocs_after, receiver_rocs_before + foreign_amount_to_send); } -// ========================================================================= +// ================================================================================= // ======= Reserve Transfers - Sufficient Foreign Asset - AssetHub<>Parachain ====== -// ========================================================================= +// ================================================================================= /// Reserve Transfers of a sufficient foreign asset from System Parachain to Parachain should /// work without using any other token. #[test] 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 ea7cd65b6856..870ae9883c47 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 @@ -1251,6 +1251,7 @@ fn reserve_transfer_sufficient_assets_from_system_para_to_para() { }); // Set assertions and dispatchables + // TODO: Bring back assertions. // test.set_assertion::(system_para_to_para_assets_sender_assertions); // test.set_assertion::(system_para_to_para_assets_receiver_assertions); test.set_dispatchable::(system_para_to_para_reserve_transfer_assets); @@ -1364,6 +1365,7 @@ fn reserve_transfer_sufficient_asset_from_para_to_para_through_asset_hub() { }); // Set assertions and dispatchables + // TODO: Bring back assertions. // test.set_assertion::(para_to_para_through_hop_sender_assertions); // test.set_assertion::(para_to_para_relay_hop_assertions); // test.set_assertion::(para_to_para_through_hop_receiver_assertions); diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index 7c4409fdb413..1780eee848c9 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -494,6 +494,16 @@ impl XcmExecutor { } } else { // We try each item in holding. + // TODO: We don't need this if we had a way to tell the executor what asset we want to use + // for fees. + // Option 1: Always using a `BuyExecution` instruction, even locally where we would normally not use it. + // This has the downside of being a worse experience. + // Option 2: Having an instruction that sets the asset to be used for fees. + // Can only do it on a new version. + // Option 3: Change the entrypoint `prepare_and_execute` to take the fee item. + // Is a breaking change. + // Option 4: Create a new entrypoint `prepare_and_execute_with_fee_asset`. + // Should work and is not a breaking change. // TODO: Might increase benchmarks too much. Should optimize for the average case. let mut asset_for_fees = first.clone(); // Holding is empty when using jit withdrawal. Could remove it. diff --git a/polkadot/xcm/xcm-executor/src/traits/fee_manager.rs b/polkadot/xcm/xcm-executor/src/traits/fee_manager.rs index 69766e3a1aea..e08a2d48b579 100644 --- a/polkadot/xcm/xcm-executor/src/traits/fee_manager.rs +++ b/polkadot/xcm/xcm-executor/src/traits/fee_manager.rs @@ -85,6 +85,7 @@ impl AssetConversion for Tuple { return Ok(new_asset) }, Err(error) => { + // TODO: log::error is too noisy and this might get called a lot. log::error!( target: "xcm::AssetConversion::convert_asset", "Implementation in tuple errored: {:?}, trying with next one, unless this was the last one.", From bdfbeaf4cc30e83215c503686dfcfce493189d13 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Sat, 11 May 2024 13:57:07 +0200 Subject: [PATCH 08/43] feat: start with SwapAssetConverter --- .../src/tests/reserve_transfer.rs | 80 +++++++++++++++++++ .../asset-hub-westend/src/xcm_config.rs | 8 +- .../runtimes/assets/common/src/lib.rs | 57 ++++++++++++- 3 files changed, 143 insertions(+), 2 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 870ae9883c47..ad3339b55ad1 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 @@ -1387,3 +1387,83 @@ fn reserve_transfer_sufficient_asset_from_para_to_para_through_asset_hub() { // Receiver's balance is increased assert!(receiver_assets_after > receiver_assets_before); } + +#[test] +fn reserve_transfer_pool_assets_from_system_para_to_para() { + let native_asset = RelayLocation::get(); + let asset_id = 9999; + let pool_asset = Location::new(0, [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(asset_id.into())]); + + // Setup the pool between `native_asset` and `pool_asset`. + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // set up pool with ASSET_ID <> NATIVE pair + assert_ok!(::Assets::create( + ::RuntimeOrigin::signed(AssetHubWestendSender::get()), + ASSET_ID.into(), + AssetHubWestendSender::get().into(), + ASSET_MIN_BALANCE, + )); + assert!(::Assets::asset_exists(ASSET_ID)); + + assert_ok!(::Assets::mint( + ::RuntimeOrigin::signed(AssetHubWestendSender::get()), + ASSET_ID.into(), + AssetHubWestendSender::get().into(), + 3_000_000_000_000, + )); + + assert_ok!(::AssetConversion::create_pool( + ::RuntimeOrigin::signed(AssetHubWestendSender::get()), + Box::new(asset_native), + Box::new(asset_one), + )); + + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, + ] + ); + + assert_ok!(::AssetConversion::add_liquidity( + ::RuntimeOrigin::signed(AssetHubWestendSender::get()), + Box::new(asset_native), + Box::new(asset_one), + 1_000_000_000_000, + 2_000_000_000_000, + 0, + 0, + AssetHubWestendSender::get().into() + )); + + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {lp_token_minted, .. }) => { lp_token_minted: *lp_token_minted == 1414213562273, }, + ] + ); + + // ensure `penpal` sovereign account has no native tokens and mint some `ASSET_ID` + assert_eq!( + ::Balances::free_balance(penpal.clone()), + 0 + ); + + assert_ok!(::Assets::touch_other( + ::RuntimeOrigin::signed(AssetHubWestendSender::get()), + ASSET_ID.into(), + penpal.clone().into(), + )); + + assert_ok!(::Assets::mint( + ::RuntimeOrigin::signed(AssetHubWestendSender::get()), + ASSET_ID.into(), + penpal.clone().into(), + 10_000_000_000_000, + )); + }); + + // TODO: Transfer assets and assert. +} 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 06699bd26b4d..b2f3f3ab40a1 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 @@ -376,7 +376,12 @@ pub type ForeignSufficientAssetsConverter = SufficientAssetConverter< /// 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. // TODO: Finish. -pub type PoolAssetsConverter = (); +pub type PoolAssetsConverter = SwapAssetConverter< + crate::Assets, + TrustBackedAssetsConvertedConcreteId, + crate::AssetConversion, + AccountId, +>; pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { @@ -460,6 +465,7 @@ impl xcm_executor::Config for XcmConfig { type PalletInstancesInfo = AllPalletsWithSystem; type MaxAssetsIntoHolding = MaxAssetsIntoHolding; type AssetLocker = (); + // TODO: Might also use `AssetExchanger` instead of `AssetConverter` if API fits. type AssetExchanger = (); type FeeManager = XcmFeeManagerFromComponents< WaivedLocations, diff --git a/cumulus/parachains/runtimes/assets/common/src/lib.rs b/cumulus/parachains/runtimes/assets/common/src/lib.rs index f617a315b0a8..da16a17625db 100644 --- a/cumulus/parachains/runtimes/assets/common/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/common/src/lib.rs @@ -25,7 +25,7 @@ pub mod runtime_api; use crate::matching::{LocalLocationPattern, ParentLocation}; use core::marker::PhantomData; -use frame_support::traits::{Equals, EverythingBut, tokens::ConversionToAssetBalance}; +use frame_support::traits::{Equals, EverythingBut, tokens::ConversionToAssetBalance, fungibles}; use parachains_common::{AssetIdForTrustBackedAssets, CollectionId, ItemId}; use sp_runtime::traits::TryConvertInto; use xcm::prelude::*; @@ -33,6 +33,7 @@ use xcm_builder::{ AsPrefixedGeneralIndex, MatchedConvertedConcreteId, StartsWith, WithLatestLocationConverter, }; use xcm_executor::traits::{MatchesFungibles, AssetConversion}; +use pallet_asset_conversion::SwapCredit as SwapCreditT; /// `Location` vs `AssetIdForTrustBackedAssets` converter for `TrustBackedAssets` pub type AssetIdForTrustBackedAssetsConvert = @@ -192,6 +193,60 @@ where } } +pub struct SwapAssetConverter(PhantomData<(Fungibles, Matcher, SwapCredit, AccountId)>); +impl AssetConversion for SwapAssetConverter +where + Fungibles: fungibles::Balanced, + Matcher: MatchesFungibles, + SwapCredit: SwapCreditT< + AccountId, + Balance = Fungibles::Balance, + AssetKind = Fungibles::AssetId, + Credit = fungibles::Credit, + >, +{ + fn convert_asset(asset: &Asset, asset_id: &Asset) -> Result { + // TODO: Not the best still. + let desired_asset: Asset = (asset_id.clone(), 1u128).into(); // To comply with the interface. + let (fungibles_asset, balance) = Matcher::matches_fungibles(&desired_asset) + .map_err(|error| { + log::error!( + target: "xcm::SufficientAssetConverter::convert_asset", + "Could not map XCM asset {:?} to FRAME asset", + asset_id, + ); + XcmError::AssetNotFound + })?; + let Fungibility::Fungible(old_asset_amount) = asset.fun else { + log::error!( + target: "xcm::SufficientAssetConverter::convert_asset", + "Fee asset is not fungible", + ); + return Err(XcmError::AssetNotFound); + }; + let swap_asset = fungibles_asset.clone().into(); + if asset.eq(&swap_asset) { + // Converter not applicable. + return Err(XcmError::FeesNotMet); + } + + let credit_in = Fungibles::issue(fungibles_asset, balance); + + // Swap the user's asset for `asset`. + let (credit_out, credit_change) = SwapCredit::swap_tokens_for_exact_tokens( + vec![swap_asset, asset.clone()], + credit_in, + old_asset_amount, + ).map_err(|(credit_in, _)| { + drop(credit_in); + XcmError::FeesNotMet + })?; + + // TODO: Is this right? + credit_change.peek().into() + } +} + #[cfg(test)] mod tests { use super::*; From c69095f019aad6f874c815cd9b69c3676fce76b1 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Wed, 22 May 2024 12:02:46 +0200 Subject: [PATCH 09/43] feat: SwapAssetConverter. Some tests failing --- Cargo.lock | 2 + .../parachains/testing/penpal/src/lib.rs | 1 + .../tests/assets/asset-hub-westend/src/lib.rs | 1 + .../src/tests/reserve_transfer.rs | 170 +++++++++++++++--- .../assets/asset-hub-rococo/src/xcm_config.rs | 1 + .../asset-hub-westend/src/xcm_config.rs | 17 +- .../runtimes/assets/common/src/lib.rs | 95 ++++++++-- .../bridge-hub-rococo/src/xcm_config.rs | 1 + .../people/people-rococo/src/xcm_config.rs | 1 + .../runtimes/testing/penpal/Cargo.toml | 6 + .../runtimes/testing/penpal/src/lib.rs | 111 +++++++++++- .../runtimes/testing/penpal/src/xcm_config.rs | 47 ++++- cumulus/primitives/utility/src/lib.rs | 21 ++- polkadot/xcm/xcm-executor/src/lib.rs | 23 +-- .../xcm-executor/src/traits/fee_manager.rs | 37 +++- substrate/frame/asset-conversion/src/lib.rs | 2 +- 16 files changed, 463 insertions(+), 73 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e83c1714cff1..3f31d25b5e73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12162,6 +12162,7 @@ dependencies = [ "frame-try-runtime", "hex-literal", "log", + "pallet-asset-conversion", "pallet-asset-tx-payment", "pallet-assets", "pallet-aura", @@ -12180,6 +12181,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/testing/penpal/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/lib.rs index c268b014bfa3..6662069d05f2 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 @@ -54,6 +54,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 1c4a0ef4c8d2..c2d44b0722ca 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 @@ -69,6 +69,7 @@ mod imports { // Runtimes pub use asset_hub_westend_runtime::xcm_config::{ WestendLocation as RelayLocation, XcmConfig as AssetHubWestendXcmConfig, + WestendLocationV3 as RelayLocationV3, }; pub use penpal_runtime::xcm_config::{ LocalReservableFromAssetHub as PenpalLocalReservableFromAssetHub, 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 ad3339b55ad1..934aedc1e533 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 @@ -1388,36 +1388,48 @@ fn reserve_transfer_sufficient_asset_from_para_to_para_through_asset_hub() { assert!(receiver_assets_after > receiver_assets_before); } +// =============================================================== +// ===== Reserve Transfers - Pool Asset - AssetHub->Parachain ==== +// =============================================================== #[test] fn reserve_transfer_pool_assets_from_system_para_to_para() { - let native_asset = RelayLocation::get(); - let asset_id = 9999; - let pool_asset = Location::new(0, [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(asset_id.into())]); + let native_asset = RelayLocationV3::get(); + let asset_id = 9999u32; + let pool_asset = xcm::v3::Location::new(0, [ + xcm::v3::Junction::PalletInstance(ASSETS_PALLET_ID), + xcm::v3::Junction::GeneralIndex(asset_id.into()) + ]); + let penpal_location = AssetHubWestend::sibling_location_of(PenpalA::para_id()); + let penpal_sov_account = AssetHubWestend::sovereign_account_id_of(penpal_location.clone()); - // Setup the pool between `native_asset` and `pool_asset`. + // Create SA-of-Penpal-on-AHR 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)]); + + // Setup the pool between `native_asset` and `pool_asset` on AssetHub. AssetHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; - // set up pool with ASSET_ID <> NATIVE pair + // set up pool with asset_id <> native pair assert_ok!(::Assets::create( ::RuntimeOrigin::signed(AssetHubWestendSender::get()), - ASSET_ID.into(), + asset_id.into(), AssetHubWestendSender::get().into(), ASSET_MIN_BALANCE, )); - assert!(::Assets::asset_exists(ASSET_ID)); + assert!(::Assets::asset_exists(asset_id)); assert_ok!(::Assets::mint( ::RuntimeOrigin::signed(AssetHubWestendSender::get()), - ASSET_ID.into(), + asset_id.into(), AssetHubWestendSender::get().into(), - 3_000_000_000_000, + 10_000_000_000_000, // For it to have more than enough. )); assert_ok!(::AssetConversion::create_pool( ::RuntimeOrigin::signed(AssetHubWestendSender::get()), - Box::new(asset_native), - Box::new(asset_one), + Box::new(native_asset), + Box::new(pool_asset), )); assert_expected_events!( @@ -1429,10 +1441,10 @@ fn reserve_transfer_pool_assets_from_system_para_to_para() { assert_ok!(::AssetConversion::add_liquidity( ::RuntimeOrigin::signed(AssetHubWestendSender::get()), - Box::new(asset_native), - Box::new(asset_one), + Box::new(native_asset), + Box::new(pool_asset), 1_000_000_000_000, - 2_000_000_000_000, + 2_000_000_000_000, // `pool_asset` is worth half of `native_asset` 0, 0, AssetHubWestendSender::get().into() @@ -1444,26 +1456,128 @@ fn reserve_transfer_pool_assets_from_system_para_to_para() { RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {lp_token_minted, .. }) => { lp_token_minted: *lp_token_minted == 1414213562273, }, ] ); + }); - // ensure `penpal` sovereign account has no native tokens and mint some `ASSET_ID` - assert_eq!( - ::Balances::free_balance(penpal.clone()), - 0 + let penpal_native_asset = Location::here(); + 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(), + false, // Asset not sufficient, has a pool to pay for fees. + 1_000_000, + vec![(PenpalASender::get(), 10_000_000_000_000)], // We give it some funds to be able to add liquidity later. + ); + + // Setup the pool between `native_penpal_asset` and `pool_asset` on PenpalA. + // So we can swap the custom asset that comes from AssetHubWestend for native + // penpal asset to pay for fees. + PenpalA::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_ok!(::AssetConversion::create_pool( + ::RuntimeOrigin::signed(PenpalASender::get()), + Box::new(penpal_native_asset.clone()), + Box::new(custom_asset_penpal_pov.clone()), + )); + + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, + ] ); - assert_ok!(::Assets::touch_other( - ::RuntimeOrigin::signed(AssetHubWestendSender::get()), - ASSET_ID.into(), - penpal.clone().into(), + assert_ok!(::AssetConversion::add_liquidity( + ::RuntimeOrigin::signed(PenpalASender::get()), + Box::new(penpal_native_asset), + Box::new(custom_asset_penpal_pov.clone()), + 1_000_000_000_000, + 3_000_000_000_000, // `custom_asset_penpal_pov` is worth a third of `penpal_native_asset` + 0, + 0, + PenpalASender::get().into() )); - assert_ok!(::Assets::mint( - ::RuntimeOrigin::signed(AssetHubWestendSender::get()), - ASSET_ID.into(), - penpal.clone().into(), - 10_000_000_000_000, + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {lp_token_minted, .. }) => { lp_token_minted: *lp_token_minted == 1732050807468, }, + ] + ); + }); + + 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); + + // We want to make sure the sender doesn't have ANY native asset (other than ED), + // so we can make sure we are paying delivery fees with the pool asset. + ::execute_with(|| { + assert_ok!(::Balances::force_set_balance( + ::RuntimeOrigin::root(), + sender.clone().into(), + ASSET_HUB_WESTEND_ED, )); }); - // TODO: Transfer assets and assert. + 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) + }); + assert_eq!(sender_initial_native_balance, ASSET_HUB_WESTEND_ED); + let receiver_initial_balance = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(custom_asset_penpal_pov.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 = 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) + }); + + // Native amount stays the same. + assert_eq!(sender_after_native_balance, sender_initial_native_balance); + // Sender account's balance decreases. + assert!(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); } 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 664d2b9c9dd5..980860425d98 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 @@ -425,6 +425,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); type XcmRecorder = PolkadotXcm; + type AssetConverter = (); } /// Converts a local signed origin into an XCM location. 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 5949380b701b..c77b1eb1438c 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 @@ -24,6 +24,7 @@ use assets_common::{ matching::{FromSiblingParachain, IsForeignConcreteAsset}, TrustBackedAssetsAsLocation, SufficientAssetConverter, + SwapAssetConverter, }; use core::marker::PhantomData; use frame_support::{ @@ -375,10 +376,18 @@ pub type ForeignSufficientAssetsConverter = SufficientAssetConverter< /// 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. -// TODO: Finish. pub type PoolAssetsConverter = SwapAssetConverter< - crate::Assets, - TrustBackedAssetsConvertedConcreteId, + WestendLocationV3, + Runtime, + crate::NativeAndAssets, + ( + TrustBackedAssetsAsLocation< + TrustBackedAssetsPalletLocation, + Balance, + xcm::v3::Location, + >, + ForeignAssetsConvertedConcreteId, + ), crate::AssetConversion, AccountId, >; @@ -454,9 +463,9 @@ impl xcm_executor::Config for XcmConfig { >, ); type AssetConverter = ( + PoolAssetsConverter, TrustBackedSufficientAssetsConverter, ForeignSufficientAssetsConverter, - PoolAssetsConverter, ); type ResponseHandler = PolkadotXcm; type AssetTrap = PolkadotXcm; diff --git a/cumulus/parachains/runtimes/assets/common/src/lib.rs b/cumulus/parachains/runtimes/assets/common/src/lib.rs index da16a17625db..4b5103764121 100644 --- a/cumulus/parachains/runtimes/assets/common/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/common/src/lib.rs @@ -25,9 +25,10 @@ pub mod runtime_api; use crate::matching::{LocalLocationPattern, ParentLocation}; use core::marker::PhantomData; -use frame_support::traits::{Equals, EverythingBut, tokens::ConversionToAssetBalance, fungibles}; +use frame_support::{ensure, traits::{Equals, EverythingBut, Get, tokens::ConversionToAssetBalance, fungibles}}; use parachains_common::{AssetIdForTrustBackedAssets, CollectionId, ItemId}; -use sp_runtime::traits::TryConvertInto; +use sp_std::vec; +use sp_runtime::traits::{Zero, TryConvertInto}; use xcm::prelude::*; use xcm_builder::{ AsPrefixedGeneralIndex, MatchedConvertedConcreteId, StartsWith, WithLatestLocationConverter, @@ -149,6 +150,7 @@ where >::Balance >, AssetsInstance: 'static, + >::Balance: Into, { fn convert_asset(asset: &Asset, asset_id: &AssetId) -> Result { // TODO: Not the best still. @@ -191,41 +193,101 @@ where })?; Ok((desired_asset.id.clone(), new_asset_amount).into()) } + + fn swap(give: &Asset, want: &Asset) -> Result { + // We need the matcher to select this implementation from the tuple. + let (fungibles_asset, balance) = Matcher::matches_fungibles(give) + .map_err(|error| { + log::error!( + target: "xcm::SufficientAssetConverter::swap", + "Could not map XCM asset {:?} to FRAME asset", + give, + ); + XcmError::AssetNotFound + })?; + // We don't do a swap, we just returned the asset we need for fees. + Ok(want.clone()) + } } -pub struct SwapAssetConverter(PhantomData<(Fungibles, Matcher, SwapCredit, AccountId)>); -impl AssetConversion for SwapAssetConverter +/// Implementation of `AssetConverter` that pays delivery fees by swapping the given asset to the +/// `Target` asset accepted for fees. +pub struct SwapAssetConverter(PhantomData<(Target, Runtime, Fungibles, Matcher, SwapCredit, AccountId)>); +impl AssetConversion for SwapAssetConverter where - Fungibles: fungibles::Balanced, + Target: Get, + Runtime: pallet_asset_conversion::Config, + Fungibles: fungibles::Balanced, Matcher: MatchesFungibles, SwapCredit: SwapCreditT< AccountId, - Balance = Fungibles::Balance, + Balance = u128, AssetKind = Fungibles::AssetId, Credit = fungibles::Credit, >, { - fn convert_asset(asset: &Asset, asset_id: &Asset) -> Result { + fn convert_asset(asset: &Asset, asset_id: &AssetId) -> Result { // TODO: Not the best still. let desired_asset: Asset = (asset_id.clone(), 1u128).into(); // To comply with the interface. - let (fungibles_asset, balance) = Matcher::matches_fungibles(&desired_asset) + let (fungibles_asset, _) = Matcher::matches_fungibles(&desired_asset) .map_err(|error| { - log::error!( - target: "xcm::SufficientAssetConverter::convert_asset", + // Using `trace` instead of `error` since we expect this to happen + // when using multiple implementations in a tuple. + log::trace!( + target: "xcm::SwapAssetConverter::convert_asset", "Could not map XCM asset {:?} to FRAME asset", asset_id, ); XcmError::AssetNotFound })?; let Fungibility::Fungible(old_asset_amount) = asset.fun else { + // Using `trace` instead of `error` since this could happen if an + // implementation was added to pay fees with NFTs. + // It just wouldn't be this one. + log::trace!( + target: "xcm::SwapAssetConverter::convert_asset", + "Fee asset {:?} is not fungible", + asset, + ); + return Err(XcmError::AssetNotFound); + }; + let swap_asset = fungibles_asset.clone().into(); + if Target::get().eq(&swap_asset) { + // Converter not applicable. + return Err(XcmError::FeesNotMet); + } + + let new_asset_amount = pallet_asset_conversion::Pallet::::quote_price_tokens_for_exact_tokens( + fungibles_asset, + Target::get(), + old_asset_amount, + true, + ).ok_or(XcmError::FeesNotMet)?; + + Ok((asset_id.clone(), new_asset_amount).into()) + } + + fn swap(give: &Asset, want: &Asset) -> Result { + let (fungibles_asset, balance) = Matcher::matches_fungibles(&give) + .map_err(|error| { + log::trace!( + target: "xcm::SwapAssetConverter::convert_asset", + "Could not map XCM asset {:?} to FRAME asset. Error: {:?}", + give, + error, + ); + XcmError::AssetNotFound + })?; + let Fungibility::Fungible(fee_amount) = want.fun else { log::error!( - target: "xcm::SufficientAssetConverter::convert_asset", + target: "xcm::SwapAssetConverter::convert_asset", "Fee asset is not fungible", ); return Err(XcmError::AssetNotFound); }; + let swap_asset = fungibles_asset.clone().into(); - if asset.eq(&swap_asset) { + if Target::get().eq(&swap_asset) { // Converter not applicable. return Err(XcmError::FeesNotMet); } @@ -234,16 +296,17 @@ where // Swap the user's asset for `asset`. let (credit_out, credit_change) = SwapCredit::swap_tokens_for_exact_tokens( - vec![swap_asset, asset.clone()], + vec![swap_asset, Target::get()], credit_in, - old_asset_amount, + fee_amount, ).map_err(|(credit_in, _)| { drop(credit_in); XcmError::FeesNotMet })?; - // TODO: Is this right? - credit_change.peek().into() + assert!(credit_change.peek() == Zero::zero()); + + Ok((want.id.clone(), credit_out.peek()).into()) } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs index a0d2e91dffd2..7e5328df861b 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs @@ -240,6 +240,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); type XcmRecorder = PolkadotXcm; + type AssetConverter = (); } pub type PriceForParentDelivery = diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/people/people-rococo/src/xcm_config.rs index cca964fb2441..c08bc9af4ccf 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/xcm_config.rs @@ -231,6 +231,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); type XcmRecorder = PolkadotXcm; + type AssetConverter = (); } /// Converts a local signed origin into an XCM location. Forms the basis for local origins diff --git a/cumulus/parachains/runtimes/testing/penpal/Cargo.toml b/cumulus/parachains/runtimes/testing/penpal/Cargo.toml index 0ac79a3eab5a..606e2e0fcbdb 100644 --- a/cumulus/parachains/runtimes/testing/penpal/Cargo.toml +++ b/cumulus/parachains/runtimes/testing/penpal/Cargo.toml @@ -42,6 +42,7 @@ pallet-transaction-payment = { path = "../../../../../substrate/frame/transactio pallet-transaction-payment-rpc-runtime-api = { path = "../../../../../substrate/frame/transaction-payment/rpc/runtime-api", default-features = false } pallet-asset-tx-payment = { path = "../../../../../substrate/frame/transaction-payment/asset-tx-payment", default-features = false } pallet-assets = { path = "../../../../../substrate/frame/assets", default-features = false } +pallet-asset-conversion = { path = "../../../../../substrate/frame/asset-conversion", default-features = false } sp-api = { path = "../../../../../substrate/primitives/api", default-features = false } sp-block-builder = { path = "../../../../../substrate/primitives/block-builder", default-features = false } sp-consensus-aura = { path = "../../../../../substrate/primitives/consensus/aura", default-features = false } @@ -55,6 +56,8 @@ sp-std = { path = "../../../../../substrate/primitives/std", default-features = sp-storage = { path = "../../../../../substrate/primitives/storage", default-features = false } sp-transaction-pool = { path = "../../../../../substrate/primitives/transaction-pool", default-features = false } sp-version = { path = "../../../../../substrate/primitives/version", default-features = false } +# num-traits feature needed for dex integer sq root: +primitive-types = { version = "0.12.1", default-features = false, features = ["codec", "num-traits", "scale-info"] } # Polkadot polkadot-primitives = { path = "../../../../../polkadot/primitives", default-features = false } @@ -102,6 +105,7 @@ std = [ "log/std", "pallet-asset-tx-payment/std", "pallet-assets/std", + "pallet-asset-conversion/std", "pallet-aura/std", "pallet-authorship/std", "pallet-balances/std", @@ -153,6 +157,7 @@ runtime-benchmarks = [ "hex-literal", "pallet-asset-tx-payment/runtime-benchmarks", "pallet-assets/runtime-benchmarks", + "pallet-asset-conversion/runtime-benchmarks", "pallet-balances/runtime-benchmarks", "pallet-collator-selection/runtime-benchmarks", "pallet-message-queue/runtime-benchmarks", @@ -180,6 +185,7 @@ try-runtime = [ "frame-try-runtime/try-runtime", "pallet-asset-tx-payment/try-runtime", "pallet-assets/try-runtime", + "pallet-asset-conversion/try-runtime", "pallet-aura/try-runtime", "pallet-authorship/try-runtime", "pallet-balances/try-runtime", diff --git a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs index 697592a9478b..66c6c1a74296 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs @@ -32,6 +32,10 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); mod weights; pub mod xcm_config; +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}; @@ -40,10 +44,11 @@ use frame_support::{ dispatch::DispatchClass, genesis_builder_helper::{build_state, get_preset}, pallet_prelude::Weight, - parameter_types, + parameter_types, ord_parameter_types, traits::{ - AsEnsureOriginWithArg, ConstBool, ConstU32, ConstU64, ConstU8, Everything, TransformOrigin, + AsEnsureOriginWithArg, ConstBool, ConstU32, ConstU64, ConstU128, ConstU8, Everything, TransformOrigin, PalletInfoAccess, + tokens::{imbalance::ResolveAssetTo, fungible, fungibles}, }, weights::{ constants::WEIGHT_REF_TIME_PER_SECOND, ConstantMultiplier, FeePolynomial, WeightToFee as _, @@ -54,6 +59,7 @@ use frame_support::{ use frame_system::{ limits::{BlockLength, BlockWeights}, EnsureRoot, EnsureSigned, + EnsureSignedBy, }; use parachains_common::{ impls::{AssetsToBlockAuthor, NonZeroIssuance}, @@ -65,7 +71,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}, + traits::{AccountIdLookup, BlakeTwo256, Block as BlockT, AccountIdConversion}, transaction_validity::{TransactionSource, TransactionValidity}, ApplyExtrinsicResult, }; @@ -501,6 +507,103 @@ 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, + 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::PenpalNativeCurrency, + 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::PenpalNativeCurrency; + 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::PenpalNativeCurrency, + 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); @@ -682,6 +785,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 c1049a4ecc8e..851f28fbbf96 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs @@ -24,16 +24,16 @@ //! soon. use super::{ AccountId, AllPalletsWithSystem, AssetId as AssetIdPalletAssets, Assets, Authorship, Balance, - Balances, ForeignAssets, ForeignAssetsInstance, NonZeroIssuance, ParachainInfo, + Balances, CollatorSelection, ForeignAssets, ForeignAssetsInstance, NonZeroIssuance, ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, WeightToFee, XcmpQueue, TrustBackedAssetsInstance, }; use crate::{BaseDeliveryFee, FeeAssetId, TransactionByteFee}; -use assets_common::SufficientAssetConverter; +use assets_common::{SufficientAssetConverter, SwapAssetConverter, TrustBackedAssetsAsLocation}; use core::marker::PhantomData; use frame_support::{ parameter_types, - traits::{ConstU32, Contains, ContainsPair, Everything, EverythingBut, Get, Nothing}, + traits::{ConstU32, Contains, ContainsPair, Everything, EverythingBut, Get, Nothing, tokens::imbalance::ResolveAssetTo, PalletInfoAccess}, weights::Weight, }; use frame_system::EnsureRoot; @@ -58,7 +58,7 @@ 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. @@ -71,6 +71,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 @@ -341,8 +345,21 @@ pub type ForeignSufficientAssetsConverter = SufficientAssetConverter< /// 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. -// TODO: Finish. -pub type PoolAssetsConverter = (); +pub type PoolAssetsConverter = SwapAssetConverter< + RelayLocation, + Runtime, + crate::NativeAndAssets, + ( + TrustBackedAssetsAsLocation< + TrustBackedAssetsPalletLocation, + Balance, + xcm::latest::Location, + >, + ForeignAssetsConvertedConcreteId, + ), + crate::AssetConversion, + AccountId, +>; pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { @@ -361,6 +378,22 @@ impl xcm_executor::Config for XcmConfig { UsingComponents>, // This trader allows to pay with `is_sufficient=true` "Foreign" assets from dedicated // `pallet_assets` instance - `ForeignAssets`. + cumulus_primitives_utility::SwapFirstAssetTrader< + RelayLocation, + crate::AssetConversion, + WeightToFee, + crate::NativeAndAssets, + ( + TrustBackedAssetsAsLocation< + TrustBackedAssetsPalletLocation, + Balance, + xcm::latest::Location, + >, + ForeignAssetsConvertedConcreteId, + ), + ResolveAssetTo, + AccountId, + >, cumulus_primitives_utility::TakeFirstAssetTrader< AccountId, ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger, @@ -395,9 +428,9 @@ impl xcm_executor::Config for XcmConfig { type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); type AssetConverter = ( + PoolAssetsConverter, TrustBackedSufficientAssetsConverter, ForeignSufficientAssetsConverter, - PoolAssetsConverter, ); type XcmRecorder = PolkadotXcm; } diff --git a/cumulus/primitives/utility/src/lib.rs b/cumulus/primitives/utility/src/lib.rs index 64784eb36f84..b2cfef418c60 100644 --- a/cumulus/primitives/utility/src/lib.rs +++ b/cumulus/primitives/utility/src/lib.rs @@ -404,10 +404,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) } @@ -421,8 +433,13 @@ impl< credit_in, fee, ) - .map_err(|(credit_in, _)| { + .map_err(|(credit_in, error)| { drop(credit_in); + log::trace!( + target: "xcm::weight", + "SwapFirstAssetTrader::buy_weight swap couldn't be done. Error was: {:?}", + error, + ); XcmError::FeesNotMet })?; diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index 7824f0d29310..05d3bdb66f36 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -258,16 +258,12 @@ impl ExecuteXcm for XcmExecutor, fees: Assets, asset_for_fees: &AssetId) -> XcmResult { let origin = origin.into(); if !Config::FeeManager::is_waived(Some(&origin), FeeReason::ChargeFees) { - // We allow only one asset for fee payment. log::trace!(target: "xcm::charge_fees", "Fees: {:?}", fees); let first = fees.get(0).ok_or(XcmError::AssetNotFound)?; - log::trace!(target: "xcm::charge_fees", "Old asset: {:?}", first); - // New config item, it will just return `first` unaffected if there's no - // way to convert it. This will result in an error later if they're not present. log::trace!(target: "xcm::charge_fees", "Asset for fees: {:?}", asset_for_fees); // If no conversion can be made, we use the original asset even if it's not // the desired one, as best effort. - let actual_asset_to_use = match Config::AssetConverter::convert_asset(&first, asset_for_fees) { + let asset_to_withdraw = match Config::AssetConverter::convert_asset(&first, asset_for_fees) { Ok(new_asset) => new_asset, Err(error) => { log::error!( @@ -279,8 +275,8 @@ impl ExecuteXcm for XcmExecutor XcmExecutor { break; }, Err(error) => { - log::error!( + // It's `trace` and not `error` because we expect it to happen + // until we reach the correct implementation in the tuple. + log::trace!( target: "xcm::take_fee", "Could not convert fees to {:?}. Error: {:?}", asset.id, @@ -540,12 +538,17 @@ impl XcmExecutor { asset_for_fees }; log::trace!(target: "xcm", "Actual asset for fees: {:?}", actual_asset_to_use); + // TODO: Remove `jit_withdrawal`, it makes this much harder. let paid = if self.fees_mode.jit_withdraw { let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; Config::AssetTransactor::withdraw_asset(&actual_asset_to_use, origin, Some(&self.context))?; - vec![actual_asset_to_use].into() + let swapped_asset = Config::AssetConverter::swap(&actual_asset_to_use, first)?; + vec![swapped_asset].into() } else { - self.holding.try_take(actual_asset_to_use.into()).map_err(|_| XcmError::NotHoldingFees)?.into() + let assets = self.holding.try_take(actual_asset_to_use.clone().into()).map_err(|_| XcmError::NotHoldingFees)?; + let taken_asset = assets.into_assets_iter().next().ok_or(XcmError::AssetNotFound)?; + let swapped_asset = Config::AssetConverter::swap(&taken_asset, first)?; + vec![swapped_asset].into() }; Config::FeeManager::handle_fee(paid, Some(&self.context), reason); Ok(()) diff --git a/polkadot/xcm/xcm-executor/src/traits/fee_manager.rs b/polkadot/xcm/xcm-executor/src/traits/fee_manager.rs index e08a2d48b579..25fb68fcd8b5 100644 --- a/polkadot/xcm/xcm-executor/src/traits/fee_manager.rs +++ b/polkadot/xcm/xcm-executor/src/traits/fee_manager.rs @@ -68,6 +68,11 @@ pub trait AssetConversion { /// has the specified `asset_id` and a new balance. /// If it can't be converted, an error is returned. fn convert_asset(asset: &Asset, asset_id: &AssetId) -> Result; + /// Swaps `give` for `want`. + /// Returns the newly swapped `Asset` or an error. + /// An error might just mean this particular element of the tuple is not applicable. + /// The `Asset` returned should be the same as the `asset` passed in to `convert_asset`. + fn swap(give: &Asset, want: &Asset) -> Result; } #[impl_trait_for_tuples::impl_for_tuples(30)] @@ -85,8 +90,9 @@ impl AssetConversion for Tuple { return Ok(new_asset) }, Err(error) => { - // TODO: log::error is too noisy and this might get called a lot. - log::error!( + // This is `trace` and not `error` since it's expected that some + // implementations in the tuple won't match. + log::trace!( target: "xcm::AssetConversion::convert_asset", "Implementation in tuple errored: {:?}, trying with next one, unless this was the last one.", error, @@ -98,4 +104,31 @@ impl AssetConversion for Tuple { Err(last_error.unwrap_or(XcmError::TooExpensive)) } + + fn swap(give: &Asset, want: &Asset) -> Result { + for_tuples!( #( + match Tuple::swap(give, want) { + Ok(asset) => { + log::trace!( + target: "xcm::AssetConversion::swap", + "Found successful implementation in tuple.", + ); + + return Ok(asset) + }, + Err(error) => { + // This is `trace` and not `error` since it's expected that some + // implementations in the tuple won't match. + log::trace!( + target: "xcm::AssetConversion::swap", + "Implementation in tuple errored: {:?}, trying with next one, unless this was the last one.", + error, + ); + }, + } + )* ); + + // If all tuple implementations fail, we want to return the fees. + Ok(want.clone()) + } } diff --git a/substrate/frame/asset-conversion/src/lib.rs b/substrate/frame/asset-conversion/src/lib.rs index 62acb693efb1..3b99f673d31c 100644 --- a/substrate/frame/asset-conversion/src/lib.rs +++ b/substrate/frame/asset-conversion/src/lib.rs @@ -433,7 +433,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 0193e60d54d5555f4987d557c8238f987999a281 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Wed, 22 May 2024 13:56:49 +0200 Subject: [PATCH 10/43] fix(asset-hub-rococo-integration-tests): delivery fee taken from holding --- .../assets/asset-hub-rococo/src/tests/hybrid_transfers.rs | 5 +++-- .../assets/asset-hub-rococo/src/tests/reserve_transfer.rs | 7 ++++--- polkadot/xcm/pallet-xcm/src/lib.rs | 1 + 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/hybrid_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/hybrid_transfers.rs index edaaa998a9ca..934e4bed4833 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/hybrid_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/hybrid_transfers.rs @@ -798,8 +798,9 @@ fn transfer_native_asset_from_relay_to_para_through_asset_hub() { >::balance(relay_native_asset_location, &receiver) }); - // Sender's balance is reduced by amount sent plus delivery fees - assert!(sender_balance_after < sender_balance_before - amount_to_send); + // Sender's balance is reduced by amount sent plus delivery fees. + // Delivery fees are taken from `amount_to_send`. + assert_eq!(sender_balance_after, sender_balance_before - amount_to_send); // SA on AH balance is increased assert!(sov_penpal_on_ah_after > sov_penpal_on_ah_before); // Receiver's asset balance is increased 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..4f39eece307e 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 @@ -1130,8 +1130,9 @@ 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 - assert!(sender_assets_after < sender_assets_before - amount_to_send); - // Receiver's balance is increased + // Sender's balance is reduced by amount sent plus delivery fees. + // Delivery fees are taken from `amount_to_send`. + assert_eq!(sender_assets_after, sender_assets_before - amount_to_send); + // Receiver's balance is increased. assert!(receiver_assets_after > receiver_assets_before); } diff --git a/polkadot/xcm/pallet-xcm/src/lib.rs b/polkadot/xcm/pallet-xcm/src/lib.rs index 29cfb828e7df..4027d89cf8de 100644 --- a/polkadot/xcm/pallet-xcm/src/lib.rs +++ b/polkadot/xcm/pallet-xcm/src/lib.rs @@ -2054,6 +2054,7 @@ impl Pallet { ]); Ok(Xcm(vec![ WithdrawAsset(assets.into()), + // TODO: Is it okay to have removed `SetFeesMode { jit_withdraw: true }`? InitiateReserveWithdraw { assets: Wild(AllCounted(max_assets)), reserve, From 0b0437c390693b91fbd1452c698234bba9269ae0 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Wed, 22 May 2024 16:36:20 +0200 Subject: [PATCH 11/43] fix(integration-tests): stick to jit withdrawal for now --- .../src/tests/hybrid_transfers.rs | 5 ++-- .../src/tests/reserve_transfer.rs | 3 +- .../src/tests/hybrid_transfers.rs | 2 +- .../src/tests/reserve_transfer.rs | 16 +++++----- .../runtimes/testing/penpal/src/lib.rs | 8 ++--- polkadot/xcm/pallet-xcm/src/lib.rs | 2 +- polkadot/xcm/xcm-executor/src/lib.rs | 29 ------------------- 7 files changed, 18 insertions(+), 47 deletions(-) diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/hybrid_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/hybrid_transfers.rs index 934e4bed4833..6a9a34a5c6b7 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/hybrid_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/hybrid_transfers.rs @@ -612,7 +612,7 @@ fn transfer_foreign_assets_from_para_to_para_through_asset_hub() { >::balance(wnd_at_rococo_parachains, &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_rocs_after < sender_rocs_before - roc_to_send); assert_eq!(sender_wnds_after, sender_wnds_before - wnd_to_send); // Sovereign accounts on reserve are changed accordingly @@ -799,8 +799,7 @@ fn transfer_native_asset_from_relay_to_para_through_asset_hub() { }); // Sender's balance is reduced by amount sent plus delivery fees. - // Delivery fees are taken from `amount_to_send`. - assert_eq!(sender_balance_after, sender_balance_before - amount_to_send); + assert!(sender_balance_after < sender_balance_before - amount_to_send); // SA on AH balance is increased assert!(sov_penpal_on_ah_after > sov_penpal_on_ah_before); // Receiver's asset balance is increased 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 4f39eece307e..b964f39cb3a0 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 @@ -1131,8 +1131,7 @@ fn reserve_transfer_native_asset_from_para_to_para_through_relay() { }); // Sender's balance is reduced by amount sent plus delivery fees. - // Delivery fees are taken from `amount_to_send`. - assert_eq!(sender_assets_after, sender_assets_before - amount_to_send); + assert!(sender_assets_after < sender_assets_before - 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/hybrid_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/hybrid_transfers.rs index 34e577dc1efa..b94c21d71691 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 @@ -763,7 +763,7 @@ fn transfer_foreign_assets_from_para_to_para_through_asset_hub() { }); // Sender's balance is reduced by amount sent. - assert_eq!(sender_wnds_after, sender_wnds_before - wnd_to_send); + 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 let delivery_fees_amount = 31_340_000_000; // TODO: Estimate this. 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 934aedc1e533..22a4f23c3a04 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 @@ -1146,8 +1146,7 @@ fn reserve_transfer_native_asset_from_para_to_para_through_relay() { }); // Sender's balance is reduced by amount sent plus delivery fees. - // Delivery fees are taken from the amount to send. - assert_eq!(sender_assets_after, sender_assets_before - amount_to_send); + 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); @@ -1314,13 +1313,16 @@ fn reserve_transfer_sufficient_asset_from_para_to_para_through_asset_hub() { ], ); + // Need to make sure account has enough for jit withdrawal of delivery fees. + // This number is too big of an estimate. + let delivery_fees_estimate = 1_000_000_000_000; // Create sufficient foreign asset. PenpalA::force_create_foreign_asset( system_para_foreign_asset_location.clone(), PenpalAssetOwner::get(), true, // Mark it as sufficient. 70_000, - vec![(sender.clone(), asset_amount_to_send + fee_amount_to_send)], + vec![(sender.clone(), asset_amount_to_send + fee_amount_to_send + delivery_fees_estimate)], ); PenpalB::force_create_foreign_asset( system_para_foreign_asset_location.clone(), @@ -1458,7 +1460,7 @@ fn reserve_transfer_pool_assets_from_system_para_to_para() { ); }); - let penpal_native_asset = Location::here(); + let relay_asset_penpal_pov = Location::parent(); let custom_asset_penpal_pov = Location::new(1, [ Parachain(1000), PalletInstance(ASSETS_PALLET_ID), @@ -1480,7 +1482,7 @@ fn reserve_transfer_pool_assets_from_system_para_to_para() { assert_ok!(::AssetConversion::create_pool( ::RuntimeOrigin::signed(PenpalASender::get()), - Box::new(penpal_native_asset.clone()), + Box::new(relay_asset_penpal_pov.clone()), Box::new(custom_asset_penpal_pov.clone()), )); @@ -1493,10 +1495,10 @@ fn reserve_transfer_pool_assets_from_system_para_to_para() { assert_ok!(::AssetConversion::add_liquidity( ::RuntimeOrigin::signed(PenpalASender::get()), - Box::new(penpal_native_asset), + Box::new(relay_asset_penpal_pov), Box::new(custom_asset_penpal_pov.clone()), 1_000_000_000_000, - 3_000_000_000_000, // `custom_asset_penpal_pov` is worth a third of `penpal_native_asset` + 3_000_000_000_000, // `custom_asset_penpal_pov` is worth a third of `relay_asset_penpal_pov` 0, 0, PenpalASender::get().into() diff --git a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs index 66c6c1a74296..1e4eee6c972f 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs @@ -561,7 +561,7 @@ pub type LocalAndForeignAssets = fungibles::UnionOf< pub type NativeAndAssets = fungible::UnionOf< Balances, LocalAndForeignAssets, - TargetFromLeft, + TargetFromLeft, xcm::latest::Location, AccountId, >; @@ -579,7 +579,7 @@ impl pallet_asset_conversion::Config for Runtime { type Assets = NativeAndAssets; type PoolId = (Self::AssetKind, Self::AssetKind); type PoolLocator = pallet_asset_conversion::WithFirstAsset< - xcm_config::PenpalNativeCurrency, + xcm_config::RelayLocation, AccountId, Self::AssetKind, PoolIdToAccountId, @@ -587,7 +587,7 @@ impl pallet_asset_conversion::Config for Runtime { type PoolAssetId = u32; type PoolAssets = PoolAssets; type PoolSetupFee = ConstU128<0>; // Asset class deposit fees are sufficient to prevent spam - type PoolSetupFeeAsset = xcm_config::PenpalNativeCurrency; + type PoolSetupFeeAsset = xcm_config::RelayLocation; type PoolSetupFeeTarget = ResolveAssetTo; type LiquidityWithdrawalFee = LiquidityWithdrawalFee; type LPFee = ConstU32<3>; @@ -597,7 +597,7 @@ impl pallet_asset_conversion::Config for Runtime { type WeightInfo = (); #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = assets_common::benchmarks::AssetPairFactory< - xcm_config::PenpalNativeCurrency, + xcm_config::RelayLocation, parachain_info::Pallet, xcm_config::TrustBackedAssetsPalletIndex, xcm::latest::Location, diff --git a/polkadot/xcm/pallet-xcm/src/lib.rs b/polkadot/xcm/pallet-xcm/src/lib.rs index 4027d89cf8de..28012ac46c23 100644 --- a/polkadot/xcm/pallet-xcm/src/lib.rs +++ b/polkadot/xcm/pallet-xcm/src/lib.rs @@ -2054,7 +2054,7 @@ impl Pallet { ]); Ok(Xcm(vec![ WithdrawAsset(assets.into()), - // TODO: Is it okay to have removed `SetFeesMode { jit_withdraw: true }`? + SetFeesMode { jit_withdraw: true }, InitiateReserveWithdraw { assets: Wild(AllCounted(max_assets)), reserve, diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index 05d3bdb66f36..f8e25faab389 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -975,31 +975,6 @@ impl XcmExecutor { InitiateReserveWithdraw { assets, reserve, xcm } => { let old_holding = self.holding.clone(); let result = Config::TransactionalProcessor::process(|| { - // We need to do this take/put cycle to solve wildcards and get exact assets to - // be weighed. - let to_weigh = self.holding.saturating_take(assets.clone()); - self.holding.subsume_assets(to_weigh.clone()); - let to_weigh_reanchored = Self::reanchored(to_weigh, &reserve, None); // TODO: Is `None` fine here? - let mut message_to_weigh = vec![WithdrawAsset(to_weigh_reanchored), ClearOrigin]; - message_to_weigh.extend(xcm.0.clone().into_iter()); - let (_, delivery_fees) = validate_send::(reserve.clone(), Xcm(message_to_weigh))?; - let first = delivery_fees.get(0).ok_or(XcmError::AssetNotFound)?; - let actual_asset_to_use_for_fees = { - let mut asset_to_use_for_fees = first.clone(); - // We loop through all assets in holding. - for asset in self.holding.fungible_assets_iter() { - match Config::AssetConverter::convert_asset(&first, &asset.id) { - Ok(new_asset) => { - asset_to_use_for_fees = new_asset; - break; - }, - Err(_) => continue, - }; - } - asset_to_use_for_fees - }; - log::trace!(target: "xcm", "Asset to use for fees in InitiateReserveWithdraw: {:?}", actual_asset_to_use_for_fees); - let parked_delivery_fee = self.holding.saturating_take(actual_asset_to_use_for_fees.into()); // Note that here we are able to place any assets which could not be reanchored // back into Holding. let assets = Self::reanchored( @@ -1009,10 +984,6 @@ impl XcmExecutor { ); let mut message = vec![WithdrawAsset(assets), ClearOrigin]; message.extend(xcm.0.into_iter()); - // Put back delivery fees in holding register to be charged by XcmSender. - self.holding.subsume_assets(parked_delivery_fee); - // TODO: It would be great if we could handle all conversion in `take_fees`. - // Don't know if possible. self.send(reserve, Xcm(message), FeeReason::InitiateReserveWithdraw)?; Ok(()) }); From 502ebf6ae83e254900f82070c119e5ec67861703 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Mon, 1 Jul 2024 19:29:49 +0200 Subject: [PATCH 12/43] chore: update Cargo.lock --- Cargo.lock | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 3c3a6b1dc290..150963b5ac6b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12525,7 +12525,6 @@ dependencies = [ "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-runtime-common", - "primitive-types", "scale-info", "smallvec", "sp-api", From 550d92aaf8d7573b343751076c948910724f3e2c Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Mon, 1 Jul 2024 19:34:35 +0200 Subject: [PATCH 13/43] fix: fmt --- .../tests/assets/asset-hub-westend/src/lib.rs | 4 +- .../src/tests/hybrid_transfers.rs | 40 ++--- .../src/tests/reserve_transfer.rs | 106 ++++++++------ .../asset-hub-westend/src/xcm_config.rs | 10 +- .../runtimes/assets/common/src/lib.rs | 137 +++++++++--------- .../runtimes/testing/penpal/src/lib.rs | 19 ++- .../runtimes/testing/penpal/src/xcm_config.rs | 11 +- polkadot/xcm/pallet-xcm/src/lib.rs | 3 +- polkadot/xcm/src/v4/traits.rs | 9 +- polkadot/xcm/xcm-executor/src/config.rs | 4 +- polkadot/xcm/xcm-executor/src/lib.rs | 83 ++++++----- polkadot/xcm/xcm-executor/src/traits/mod.rs | 2 +- 12 files changed, 239 insertions(+), 189 deletions(-) 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 c2d44b0722ca..b849b63dd8cd 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 @@ -68,8 +68,8 @@ mod imports { // Runtimes pub use asset_hub_westend_runtime::xcm_config::{ - WestendLocation as RelayLocation, XcmConfig as AssetHubWestendXcmConfig, - WestendLocationV3 as RelayLocationV3, + WestendLocation as RelayLocation, WestendLocationV3 as RelayLocationV3, + XcmConfig as AssetHubWestendXcmConfig, }; pub use penpal_runtime::xcm_config::{ LocalReservableFromAssetHub as PenpalLocalReservableFromAssetHub, 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 b94c21d71691..2481fd79eb97 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 @@ -320,12 +320,12 @@ fn reserve_transfer_sufficient_foreign_assets_from_system_para_to_para() { vec![(sender.clone(), ASSET_HUB_WESTEND_ED * 10000)], ); - let assets: Assets = vec![ - ( - (Parent, Parent, GlobalConsensus(NetworkId::Rococo)), - asset_amount_to_send + fee_amount_to_send, - ).into(), - ].into(); + let assets: Assets = vec![( + (Parent, Parent, GlobalConsensus(NetworkId::Rococo)), + asset_amount_to_send + fee_amount_to_send, + ) + .into()] + .into(); // Just to be very clear we don't need to send any native asset. assert_eq!(assets.len(), 1); let fee_asset_index = 0; @@ -362,18 +362,20 @@ fn reserve_transfer_sufficient_foreign_assets_from_system_para_to_para() { let sender_balance_before = test.sender.balance; let sender_foreign_assets_before = AssetHubWestend::execute_with(|| { type Assets = ::ForeignAssets; - >::balance(roc_at_westend_parachains.clone().try_into().unwrap(), &sender) + >::balance( + roc_at_westend_parachains.clone().try_into().unwrap(), + &sender, + ) }); let receiver_foreign_assets_before = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance( - roc_at_westend_parachains.clone(), - &receiver, - ) + >::balance(roc_at_westend_parachains.clone(), &receiver) }); // Set assertions and dispatchables - test.set_assertion::(system_para_to_para_sender_sufficient_foreign_asset_assertions); + test.set_assertion::( + system_para_to_para_sender_sufficient_foreign_asset_assertions, + ); test.set_assertion::(system_para_to_para_receiver_assertions); test.set_dispatchable::(ah_to_para_transfer_assets); test.assert(); @@ -382,14 +384,14 @@ fn reserve_transfer_sufficient_foreign_assets_from_system_para_to_para() { let sender_balance_after = test.sender.balance; let sender_foreign_assets_after = AssetHubWestend::execute_with(|| { type Assets = ::ForeignAssets; - >::balance(roc_at_westend_parachains.clone().try_into().unwrap(), &sender) + >::balance( + roc_at_westend_parachains.clone().try_into().unwrap(), + &sender, + ) }); let receiver_foreign_assets_after = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance( - roc_at_westend_parachains.clone(), - &receiver, - ) + >::balance(roc_at_westend_parachains.clone(), &receiver) }); // The native asset balance is the same. They weren't send or used for fees. @@ -767,8 +769,8 @@ fn transfer_foreign_assets_from_para_to_para_through_asset_hub() { assert_eq!(sender_rocs_after, sender_rocs_before - roc_to_send); // Sovereign accounts on reserve are changed accordingly let delivery_fees_amount = 31_340_000_000; // TODO: Estimate this. - // Delivery fees stay in the sender chain, so the balance of the sender's - // sovereign account reflects this. + // Delivery fees stay in the sender chain, so the balance of the sender's + // sovereign account reflects this. assert_eq!( wnds_in_sender_reserve_on_ah_after, wnds_in_sender_reserve_on_ah_before - wnd_to_send + delivery_fees_amount 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 22a4f23c3a04..61a4f3bc2ff3 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 @@ -1181,11 +1181,7 @@ fn reserve_transfer_sufficient_assets_from_system_para_to_para() { ); let system_para_foreign_asset_location = Location::new( 1, - [ - Parachain(ASSET_HUB_ID), - PalletInstance(ASSETS_PALLET_ID), - GeneralIndex(asset_id.into()), - ], + [Parachain(ASSET_HUB_ID), PalletInstance(ASSETS_PALLET_ID), GeneralIndex(asset_id.into())], ); // Create sufficient foreign asset. @@ -1197,12 +1193,12 @@ fn reserve_transfer_sufficient_assets_from_system_para_to_para() { vec![], ); - let assets: Assets = vec![ - ( - (PalletInstance(ASSETS_PALLET_ID), GeneralIndex(asset_id.into())), - asset_amount_to_send + fee_amount_to_send, - ).into(), - ].into(); + let assets: Assets = vec![( + (PalletInstance(ASSETS_PALLET_ID), GeneralIndex(asset_id.into())), + asset_amount_to_send + fee_amount_to_send, + ) + .into()] + .into(); // Just to be very specific you don't need any native asset. assert_eq!(assets.len(), 1); let fee_asset_index = 0; @@ -1280,8 +1276,8 @@ fn reserve_transfer_sufficient_assets_from_system_para_to_para() { // ================================================================================ // ===== Reserve Transfers - Sufficient Asset - Parachain->AssetHub->Parachain ==== // ================================================================================ -/// Reserve Transfers of a sufficient trust-backed asset from Parachain to Parachain (through Relay reserve) should -/// work +/// Reserve Transfers of a sufficient trust-backed asset from Parachain to Parachain (through Relay +/// reserve) should work #[test] fn reserve_transfer_sufficient_asset_from_para_to_para_through_asset_hub() { use penpal_runtime::xcm_config::ASSET_HUB_ID; @@ -1293,7 +1289,8 @@ fn reserve_transfer_sufficient_asset_from_para_to_para_through_asset_hub() { let fee_amount_to_send: Balance = WESTEND_ED * 10000; let asset_owner = PenpalAssetOwner::get(); let sender_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_as_seen_by_asset_hub); + let sov_of_sender_on_asset_hub = + AssetHubWestend::sovereign_account_id_of(sender_as_seen_by_asset_hub); // Create sufficient asset. let asset_id = 1984; @@ -1306,11 +1303,7 @@ fn reserve_transfer_sufficient_asset_from_para_to_para_through_asset_hub() { ); let system_para_foreign_asset_location = Location::new( 1, - [ - Parachain(ASSET_HUB_ID), - PalletInstance(ASSETS_PALLET_ID), - GeneralIndex(asset_id.into()), - ], + [Parachain(ASSET_HUB_ID), PalletInstance(ASSETS_PALLET_ID), GeneralIndex(asset_id.into())], ); // Need to make sure account has enough for jit withdrawal of delivery fees. @@ -1332,18 +1325,26 @@ fn reserve_transfer_sufficient_asset_from_para_to_para_through_asset_hub() { vec![], ); - let assets: Assets = vec![ + let assets: Assets = vec![( ( - (Parent, Parachain(ASSET_HUB_ID), PalletInstance(ASSETS_PALLET_ID), GeneralIndex(asset_id.into())), - asset_amount_to_send + fee_amount_to_send, - ).into(), - ].into(); + Parent, + Parachain(ASSET_HUB_ID), + PalletInstance(ASSETS_PALLET_ID), + GeneralIndex(asset_id.into()), + ), + asset_amount_to_send + fee_amount_to_send, + ) + .into()] + .into(); // Just to be very specific you don't need any native asset. assert_eq!(assets.len(), 1); let fee_asset_index = 0; // fund the Parachain Origin's SA on Relay Chain with the native tokens held in reserve - AssetHubWestend::fund_accounts(vec![(sov_of_sender_on_asset_hub.into(), asset_amount_to_send * 2)]); + AssetHubWestend::fund_accounts(vec![( + sov_of_sender_on_asset_hub.into(), + asset_amount_to_send * 2, + )]); // Init values for Parachain Destination let receiver = PenpalBReceiver::get(); @@ -1352,7 +1353,14 @@ fn reserve_transfer_sufficient_asset_from_para_to_para_through_asset_hub() { let test_args = TestContext { sender: sender.clone(), receiver: receiver.clone(), - args: TestArgs::new_para(destination, receiver.clone(), asset_amount_to_send, assets, None, 0), + args: TestArgs::new_para( + destination, + receiver.clone(), + asset_amount_to_send, + assets, + None, + 0, + ), }; let mut test = ParaToParaThroughAHTest::new(test_args); @@ -1363,7 +1371,10 @@ fn reserve_transfer_sufficient_asset_from_para_to_para_through_asset_hub() { }); let receiver_assets_before = PenpalB::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(system_para_foreign_asset_location.clone(), &receiver) + >::balance( + system_para_foreign_asset_location.clone(), + &receiver, + ) }); // Set assertions and dispatchables @@ -1371,7 +1382,9 @@ fn reserve_transfer_sufficient_asset_from_para_to_para_through_asset_hub() { // test.set_assertion::(para_to_para_through_hop_sender_assertions); // test.set_assertion::(para_to_para_relay_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.set_dispatchable::( + para_to_para_through_asset_hub_limited_reserve_transfer_assets, + ); test.assert(); // Query final balances @@ -1397,10 +1410,13 @@ fn reserve_transfer_sufficient_asset_from_para_to_para_through_asset_hub() { fn reserve_transfer_pool_assets_from_system_para_to_para() { let native_asset = RelayLocationV3::get(); let asset_id = 9999u32; - let pool_asset = xcm::v3::Location::new(0, [ - xcm::v3::Junction::PalletInstance(ASSETS_PALLET_ID), - xcm::v3::Junction::GeneralIndex(asset_id.into()) - ]); + let pool_asset = xcm::v3::Location::new( + 0, + [ + xcm::v3::Junction::PalletInstance(ASSETS_PALLET_ID), + xcm::v3::Junction::GeneralIndex(asset_id.into()), + ], + ); let penpal_location = AssetHubWestend::sibling_location_of(PenpalA::para_id()); let penpal_sov_account = AssetHubWestend::sovereign_account_id_of(penpal_location.clone()); @@ -1461,17 +1477,17 @@ fn reserve_transfer_pool_assets_from_system_para_to_para() { }); let relay_asset_penpal_pov = Location::parent(); - let custom_asset_penpal_pov = Location::new(1, [ - Parachain(1000), - PalletInstance(ASSETS_PALLET_ID), - GeneralIndex(asset_id.into()), - ]); + 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(), false, // Asset not sufficient, has a pool to pay for fees. 1_000_000, - vec![(PenpalASender::get(), 10_000_000_000_000)], // We give it some funds to be able to add liquidity later. + vec![(PenpalASender::get(), 10_000_000_000_000)], /* We give it some funds to be able to + * add liquidity later. */ ); // Setup the pool between `native_penpal_asset` and `pool_asset` on PenpalA. @@ -1498,7 +1514,8 @@ fn reserve_transfer_pool_assets_from_system_para_to_para() { Box::new(relay_asset_penpal_pov), Box::new(custom_asset_penpal_pov.clone()), 1_000_000_000_000, - 3_000_000_000_000, // `custom_asset_penpal_pov` is worth a third of `relay_asset_penpal_pov` + 3_000_000_000_000, /* `custom_asset_penpal_pov` is worth a third of + * `relay_asset_penpal_pov` */ 0, 0, PenpalASender::get().into() @@ -1515,9 +1532,12 @@ 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(); + 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(), @@ -1528,7 +1548,7 @@ fn reserve_transfer_pool_assets_from_system_para_to_para() { asset_amount_to_send, assets, None, - 0 + 0, ), }; let mut test = SystemParaToParaTest::new(test_args); 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 3367467c560c..8c626e8e839a 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 @@ -22,9 +22,7 @@ use super::{ }; use assets_common::{ matching::{FromSiblingParachain, IsForeignConcreteAsset}, - TrustBackedAssetsAsLocation, - SufficientAssetConverter, - SwapAssetConverter, + SufficientAssetConverter, SwapAssetConverter, TrustBackedAssetsAsLocation, }; use core::marker::PhantomData; use frame_support::{ @@ -381,11 +379,7 @@ pub type PoolAssetsConverter = SwapAssetConverter< Runtime, crate::NativeAndAssets, ( - TrustBackedAssetsAsLocation< - TrustBackedAssetsPalletLocation, - Balance, - xcm::v3::Location, - >, + TrustBackedAssetsAsLocation, ForeignAssetsConvertedConcreteId, ), crate::AssetConversion, diff --git a/cumulus/parachains/runtimes/assets/common/src/lib.rs b/cumulus/parachains/runtimes/assets/common/src/lib.rs index 4b5103764121..cc2e328088ea 100644 --- a/cumulus/parachains/runtimes/assets/common/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/common/src/lib.rs @@ -25,16 +25,19 @@ pub mod runtime_api; use crate::matching::{LocalLocationPattern, ParentLocation}; use core::marker::PhantomData; -use frame_support::{ensure, traits::{Equals, EverythingBut, Get, tokens::ConversionToAssetBalance, fungibles}}; +use frame_support::{ + ensure, + traits::{fungibles, tokens::ConversionToAssetBalance, Equals, EverythingBut, Get}, +}; +use pallet_asset_conversion::SwapCredit as SwapCreditT; use parachains_common::{AssetIdForTrustBackedAssets, CollectionId, ItemId}; +use sp_runtime::traits::{TryConvertInto, Zero}; use sp_std::vec; -use sp_runtime::traits::{Zero, TryConvertInto}; use xcm::prelude::*; use xcm_builder::{ AsPrefixedGeneralIndex, MatchedConvertedConcreteId, StartsWith, WithLatestLocationConverter, }; -use xcm_executor::traits::{MatchesFungibles, AssetConversion}; -use pallet_asset_conversion::SwapCredit as SwapCreditT; +use xcm_executor::traits::{AssetConversion, MatchesFungibles}; /// `Location` vs `AssetIdForTrustBackedAssets` converter for `TrustBackedAssets` pub type AssetIdForTrustBackedAssetsConvert = @@ -136,18 +139,21 @@ pub type PoolAssetsConvertedConcreteId = TryConvertInto, >; -pub struct SufficientAssetConverter(PhantomData<(Runtime, Matcher, BalanceConverter, AssetsInstance)>); -impl AssetConversion for SufficientAssetConverter +pub struct SufficientAssetConverter( + PhantomData<(Runtime, Matcher, BalanceConverter, AssetsInstance)>, +); +impl AssetConversion + for SufficientAssetConverter where Runtime: pallet_assets::Config, Matcher: MatchesFungibles< >::AssetId, - >::Balance + >::Balance, >, BalanceConverter: ConversionToAssetBalance< u128, >::AssetId, - >::Balance + >::Balance, >, AssetsInstance: 'static, >::Balance: Into, @@ -155,15 +161,14 @@ where fn convert_asset(asset: &Asset, asset_id: &AssetId) -> Result { // TODO: Not the best still. let desired_asset: Asset = (asset_id.clone(), 1u128).into(); // To comply with the interface. - let (local_asset_id, _) = Matcher::matches_fungibles(&desired_asset) - .map_err(|error| { - log::error!( - target: "xcm::SufficientAssetConverter::convert_asset", - "Could not map XCM asset {:?} to FRAME asset", - asset_id, - ); - XcmError::AssetNotFound - })?; + let (local_asset_id, _) = Matcher::matches_fungibles(&desired_asset).map_err(|error| { + log::error!( + target: "xcm::SufficientAssetConverter::convert_asset", + "Could not map XCM asset {:?} to FRAME asset", + asset_id, + ); + XcmError::AssetNotFound + })?; log::trace!(target: "xcm::SufficientAssetConverter::convert_asset", "local asset id: {:?}", local_asset_id); let Fungibility::Fungible(old_asset_amount) = asset.fun else { log::error!( @@ -182,29 +187,27 @@ where ); XcmError::TooExpensive })?; - let new_asset_amount: u128 = new_asset_amount.try_into() - .map_err(|error| { - log::error!( - target: "xcm::SufficientAssetConverter::convert_asset", - "Converting balance of {:?} to u128 would overflow", - desired_asset.id, - ); - XcmError::Overflow - })?; + let new_asset_amount: u128 = new_asset_amount.try_into().map_err(|error| { + log::error!( + target: "xcm::SufficientAssetConverter::convert_asset", + "Converting balance of {:?} to u128 would overflow", + desired_asset.id, + ); + XcmError::Overflow + })?; Ok((desired_asset.id.clone(), new_asset_amount).into()) } fn swap(give: &Asset, want: &Asset) -> Result { // We need the matcher to select this implementation from the tuple. - let (fungibles_asset, balance) = Matcher::matches_fungibles(give) - .map_err(|error| { - log::error!( - target: "xcm::SufficientAssetConverter::swap", - "Could not map XCM asset {:?} to FRAME asset", - give, - ); - XcmError::AssetNotFound - })?; + let (fungibles_asset, balance) = Matcher::matches_fungibles(give).map_err(|error| { + log::error!( + target: "xcm::SufficientAssetConverter::swap", + "Could not map XCM asset {:?} to FRAME asset", + give, + ); + XcmError::AssetNotFound + })?; // We don't do a swap, we just returned the asset we need for fees. Ok(want.clone()) } @@ -212,8 +215,11 @@ where /// Implementation of `AssetConverter` that pays delivery fees by swapping the given asset to the /// `Target` asset accepted for fees. -pub struct SwapAssetConverter(PhantomData<(Target, Runtime, Fungibles, Matcher, SwapCredit, AccountId)>); -impl AssetConversion for SwapAssetConverter +pub struct SwapAssetConverter( + PhantomData<(Target, Runtime, Fungibles, Matcher, SwapCredit, AccountId)>, +); +impl AssetConversion + for SwapAssetConverter where Target: Get, Runtime: pallet_asset_conversion::Config, @@ -229,17 +235,16 @@ where fn convert_asset(asset: &Asset, asset_id: &AssetId) -> Result { // TODO: Not the best still. let desired_asset: Asset = (asset_id.clone(), 1u128).into(); // To comply with the interface. - let (fungibles_asset, _) = Matcher::matches_fungibles(&desired_asset) - .map_err(|error| { - // Using `trace` instead of `error` since we expect this to happen - // when using multiple implementations in a tuple. - log::trace!( - target: "xcm::SwapAssetConverter::convert_asset", - "Could not map XCM asset {:?} to FRAME asset", - asset_id, - ); - XcmError::AssetNotFound - })?; + let (fungibles_asset, _) = Matcher::matches_fungibles(&desired_asset).map_err(|error| { + // Using `trace` instead of `error` since we expect this to happen + // when using multiple implementations in a tuple. + log::trace!( + target: "xcm::SwapAssetConverter::convert_asset", + "Could not map XCM asset {:?} to FRAME asset", + asset_id, + ); + XcmError::AssetNotFound + })?; let Fungibility::Fungible(old_asset_amount) = asset.fun else { // Using `trace` instead of `error` since this could happen if an // implementation was added to pay fees with NFTs. @@ -257,27 +262,28 @@ where return Err(XcmError::FeesNotMet); } - let new_asset_amount = pallet_asset_conversion::Pallet::::quote_price_tokens_for_exact_tokens( - fungibles_asset, - Target::get(), - old_asset_amount, - true, - ).ok_or(XcmError::FeesNotMet)?; + let new_asset_amount = + pallet_asset_conversion::Pallet::::quote_price_tokens_for_exact_tokens( + fungibles_asset, + Target::get(), + old_asset_amount, + true, + ) + .ok_or(XcmError::FeesNotMet)?; Ok((asset_id.clone(), new_asset_amount).into()) } fn swap(give: &Asset, want: &Asset) -> Result { - let (fungibles_asset, balance) = Matcher::matches_fungibles(&give) - .map_err(|error| { - log::trace!( - target: "xcm::SwapAssetConverter::convert_asset", - "Could not map XCM asset {:?} to FRAME asset. Error: {:?}", - give, - error, - ); - XcmError::AssetNotFound - })?; + let (fungibles_asset, balance) = Matcher::matches_fungibles(&give).map_err(|error| { + log::trace!( + target: "xcm::SwapAssetConverter::convert_asset", + "Could not map XCM asset {:?} to FRAME asset. Error: {:?}", + give, + error, + ); + XcmError::AssetNotFound + })?; let Fungibility::Fungible(fee_amount) = want.fun else { log::error!( target: "xcm::SwapAssetConverter::convert_asset", @@ -299,7 +305,8 @@ where vec![swap_asset, Target::get()], credit_in, fee_amount, - ).map_err(|(credit_in, _)| { + ) + .map_err(|(credit_in, _)| { drop(credit_in); XcmError::FeesNotMet })?; diff --git a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs index 97c68b47297a..0e52e6943b6e 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs @@ -43,12 +43,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, ord_parameter_types, + parameter_types, traits::{ - AsEnsureOriginWithArg, ConstBool, ConstU32, ConstU64, ConstU128, ConstU8, Everything, TransformOrigin, - PalletInfoAccess, - tokens::{imbalance::ResolveAssetTo, fungible, fungibles}, + tokens::{fungible, fungibles, imbalance::ResolveAssetTo}, + AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, Everything, + PalletInfoAccess, TransformOrigin, }, weights::{ constants::WEIGHT_REF_TIME_PER_SECOND, ConstantMultiplier, FeePolynomial, WeightToFee as _, @@ -58,8 +59,7 @@ use frame_support::{ }; use frame_system::{ limits::{BlockLength, BlockWeights}, - EnsureRoot, EnsureSigned, - EnsureSignedBy, + EnsureRoot, EnsureSigned, EnsureSignedBy, }; use parachains_common::{ impls::{AssetsToBlockAuthor, NonZeroIssuance}, @@ -71,7 +71,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, AccountIdConversion, Dispatchable}, + traits::{AccountIdConversion, AccountIdLookup, BlakeTwo256, Block as BlockT, Dispatchable}, transaction_validity::{TransactionSource, TransactionValidity}, ApplyExtrinsicResult, }; @@ -549,7 +549,10 @@ pub type LocalAndForeignAssets = fungibles::UnionOf< Assets, ForeignAssets, LocalFromLeft< - AssetIdForTrustBackedAssetsConvert, + AssetIdForTrustBackedAssetsConvert< + xcm_config::TrustBackedAssetsPalletLocation, + xcm::latest::Location, + >, parachains_common::AssetIdForTrustBackedAssets, xcm::latest::Location, >, diff --git a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs index 851f28fbbf96..4b90f052ebdc 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs @@ -24,16 +24,19 @@ //! soon. use super::{ AccountId, AllPalletsWithSystem, AssetId as AssetIdPalletAssets, Assets, Authorship, Balance, - Balances, CollatorSelection, ForeignAssets, ForeignAssetsInstance, NonZeroIssuance, ParachainInfo, - ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, WeightToFee, - XcmpQueue, TrustBackedAssetsInstance, + Balances, CollatorSelection, ForeignAssets, ForeignAssetsInstance, NonZeroIssuance, + ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, + TrustBackedAssetsInstance, WeightToFee, XcmpQueue, }; use crate::{BaseDeliveryFee, FeeAssetId, TransactionByteFee}; use assets_common::{SufficientAssetConverter, SwapAssetConverter, TrustBackedAssetsAsLocation}; use core::marker::PhantomData; use frame_support::{ parameter_types, - traits::{ConstU32, Contains, ContainsPair, Everything, EverythingBut, Get, Nothing, tokens::imbalance::ResolveAssetTo, PalletInfoAccess}, + traits::{ + tokens::imbalance::ResolveAssetTo, ConstU32, Contains, ContainsPair, Everything, + EverythingBut, Get, Nothing, PalletInfoAccess, + }, weights::Weight, }; use frame_system::EnsureRoot; diff --git a/polkadot/xcm/pallet-xcm/src/lib.rs b/polkadot/xcm/pallet-xcm/src/lib.rs index 902da3bbeb90..dedf6fa70fe9 100644 --- a/polkadot/xcm/pallet-xcm/src/lib.rs +++ b/polkadot/xcm/pallet-xcm/src/lib.rs @@ -2429,7 +2429,8 @@ impl Pallet { log::debug!(target: "xcm::send_xcm", "dest: {:?}, message: {:?}", &dest, &message); let (ticket, price) = validate_send::(dest, message)?; if let Some(fee_payer) = maybe_fee_payer { - Self::charge_fees(fee_payer, price, &AssetId(Here.into())).map_err(|_| SendError::Fees)?; + Self::charge_fees(fee_payer, price, &AssetId(Here.into())) + .map_err(|_| SendError::Fees)?; } T::XcmRouter::deliver(ticket) } diff --git a/polkadot/xcm/src/v4/traits.rs b/polkadot/xcm/src/v4/traits.rs index 1c58b84e13d7..0ddaff8facbb 100644 --- a/polkadot/xcm/src/v4/traits.rs +++ b/polkadot/xcm/src/v4/traits.rs @@ -102,7 +102,8 @@ pub trait ExecuteXcm { /// Deduct some `fees` to the sovereign account of the given `location` and place them as per /// the convention for fees. - fn charge_fees(location: impl Into, fees: Assets, asset_for_fees: &AssetId) -> Result; + fn charge_fees(location: impl Into, fees: Assets, asset_for_fees: &AssetId) + -> Result; } pub enum Weightless {} @@ -120,7 +121,11 @@ impl ExecuteXcm for () { fn execute(_: impl Into, _: Self::Prepared, _: &mut XcmHash, _: Weight) -> Outcome { unreachable!() } - fn charge_fees(_location: impl Into, _fees: Assets, _asset_for_fees: &AssetId) -> Result { + fn charge_fees( + _location: impl Into, + _fees: Assets, + _asset_for_fees: &AssetId, + ) -> Result { Err(Error::Unimplemented) } } diff --git a/polkadot/xcm/xcm-executor/src/config.rs b/polkadot/xcm/xcm-executor/src/config.rs index e292f93bda04..a6f76fb5d4fc 100644 --- a/polkadot/xcm/xcm-executor/src/config.rs +++ b/polkadot/xcm/xcm-executor/src/config.rs @@ -15,8 +15,8 @@ // along with Polkadot. If not, see . use crate::traits::{ - AssetExchange, AssetConversion, AssetLock, CallDispatcher, ClaimAssets, ConvertOrigin, DropAssets, ExportXcm, - FeeManager, HandleHrmpChannelAccepted, HandleHrmpChannelClosing, + AssetConversion, AssetExchange, AssetLock, CallDispatcher, ClaimAssets, ConvertOrigin, + DropAssets, ExportXcm, FeeManager, HandleHrmpChannelAccepted, HandleHrmpChannelClosing, HandleHrmpNewChannelOpenRequest, OnResponse, ProcessTransaction, RecordXcm, ShouldExecute, TransactAsset, VersionChangeNotifier, WeightBounds, WeightTrader, }; diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index b9aeab67f042..cbae20fbddf0 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -30,8 +30,8 @@ use xcm::latest::prelude::*; pub mod traits; use traits::{ - validate_export, AssetExchange, AssetConversion, AssetLock, CallDispatcher, ClaimAssets, ConvertOrigin, - DropAssets, Enact, ExportXcm, FeeManager, FeeReason, HandleHrmpChannelAccepted, + validate_export, AssetConversion, AssetExchange, AssetLock, CallDispatcher, ClaimAssets, + ConvertOrigin, DropAssets, Enact, ExportXcm, FeeManager, FeeReason, HandleHrmpChannelAccepted, HandleHrmpChannelClosing, HandleHrmpNewChannelOpenRequest, OnResponse, ProcessTransaction, Properties, ShouldExecute, TransactAsset, VersionChangeNotifier, WeightBounds, WeightTrader, XcmAssetTransfers, @@ -255,7 +255,11 @@ impl ExecuteXcm for XcmExecutor, fees: Assets, asset_for_fees: &AssetId) -> XcmResult { + fn charge_fees( + origin: impl Into, + fees: Assets, + asset_for_fees: &AssetId, + ) -> XcmResult { let origin = origin.into(); if !Config::FeeManager::is_waived(Some(&origin), FeeReason::ChargeFees) { log::trace!(target: "xcm::charge_fees", "Fees: {:?}", fees); @@ -263,18 +267,19 @@ impl ExecuteXcm for XcmExecutor new_asset, - Err(error) => { - log::error!( - target: "xcm::charge_fees", - "Could not convert fees to {:?}. Error: {:?}", - asset_for_fees, - error, - ); - first.clone() - }, - }; + let asset_to_withdraw = + match Config::AssetConverter::convert_asset(&first, asset_for_fees) { + Ok(new_asset) => new_asset, + Err(error) => { + log::error!( + target: "xcm::charge_fees", + "Could not convert fees to {:?}. Error: {:?}", + asset_for_fees, + error, + ); + first.clone() + }, + }; log::trace!(target: "xcm::charge_fees", "New asset: {:?}", asset_to_withdraw); Config::AssetTransactor::withdraw_asset(&asset_to_withdraw, &origin, None)?; Config::FeeManager::handle_fee(fees, None, FeeReason::ChargeFees); @@ -389,7 +394,8 @@ impl XcmExecutor { // It should use `AssetConverter` to change it to USDC, for example. // How it knows that is a mystery. // `self.asset_for_fees` is not populated most of the time. - // `self.holding` can only have enough USDC if I do the same calculation before to park the fees. + // `self.holding` can only have enough USDC if I do the same calculation before to park the + // fees. self.take_fee(fee, reason)?; Config::XcmSender::deliver(ticket).map_err(Into::into) } @@ -499,10 +505,10 @@ impl XcmExecutor { } } else { // We try each item in holding. - // TODO: We don't need this if we had a way to tell the executor what asset we want to use - // for fees. - // Option 1: Always using a `BuyExecution` instruction, even locally where we would normally not use it. - // This has the downside of being a worse experience. + // TODO: We don't need this if we had a way to tell the executor what asset we want to + // use for fees. + // Option 1: Always using a `BuyExecution` instruction, even locally where we would + // normally not use it. This has the downside of being a worse experience. // Option 2: Having an instruction that sets the asset to be used for fees. // Can only do it on a new version. // Option 3: Change the entrypoint `prepare_and_execute` to take the fee item. @@ -512,9 +518,9 @@ impl XcmExecutor { // TODO: Might increase benchmarks too much. Should optimize for the average case. let mut asset_for_fees = first.clone(); // Holding is empty when using jit withdrawal. Could remove it. - // It's not empty because of jit withdrawal, it's empty because it's taken from holding before sending. - // That's the problem jit withdrawal was solving, we need to "park" delivery fees in the same way we did - // for `DepositReserveAsset`. + // It's not empty because of jit withdrawal, it's empty because it's taken from holding + // before sending. That's the problem jit withdrawal was solving, we need to "park" + // delivery fees in the same way we did for `DepositReserveAsset`. for asset in self.holding.fungible_assets_iter() { log::trace!(target: "xcm", "Asset being tested to convert: {:?}", asset); match Config::AssetConverter::convert_asset(&first, &asset.id) { @@ -532,7 +538,7 @@ impl XcmExecutor { error, ); continue; - } + }, } } asset_for_fees @@ -541,11 +547,18 @@ impl XcmExecutor { // TODO: Remove `jit_withdrawal`, it makes this much harder. let paid = if self.fees_mode.jit_withdraw { let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; - Config::AssetTransactor::withdraw_asset(&actual_asset_to_use, origin, Some(&self.context))?; + Config::AssetTransactor::withdraw_asset( + &actual_asset_to_use, + origin, + Some(&self.context), + )?; let swapped_asset = Config::AssetConverter::swap(&actual_asset_to_use, first)?; vec![swapped_asset].into() } else { - let assets = self.holding.try_take(actual_asset_to_use.clone().into()).map_err(|_| XcmError::NotHoldingFees)?; + let assets = self + .holding + .try_take(actual_asset_to_use.clone().into()) + .map_err(|_| XcmError::NotHoldingFees)?; let taken_asset = assets.into_assets_iter().next().ok_or(XcmError::AssetNotFound)?; let swapped_asset = Config::AssetConverter::swap(&taken_asset, first)?; vec![swapped_asset].into() @@ -941,15 +954,17 @@ impl XcmExecutor { let asset_id = self.asset_for_fees.as_ref().unwrap_or(&first.id); // TODO: Deal with this case. // Need to make a test specifically for this. - let actual_asset_to_use_for_fees = match Config::AssetConverter::convert_asset(&first, asset_id) { - Ok(new_asset) => new_asset, - Err(error) => { - log::error!(target: "xcm::DepositReserveAsset", "What happened?"); - first.clone() - }, - }; + let actual_asset_to_use_for_fees = + match Config::AssetConverter::convert_asset(&first, asset_id) { + Ok(new_asset) => new_asset, + Err(error) => { + log::error!(target: "xcm::DepositReserveAsset", "What happened?"); + first.clone() + }, + }; // set aside fee to be charged by XcmSender - let transport_fee = self.holding.saturating_take(actual_asset_to_use_for_fees.into()); + let transport_fee = + self.holding.saturating_take(actual_asset_to_use_for_fees.into()); // now take assets to deposit (excluding transport_fee) let deposited = self.holding.saturating_take(assets); diff --git a/polkadot/xcm/xcm-executor/src/traits/mod.rs b/polkadot/xcm/xcm-executor/src/traits/mod.rs index ac28a2a37028..3057f454bdf9 100644 --- a/polkadot/xcm/xcm-executor/src/traits/mod.rs +++ b/polkadot/xcm/xcm-executor/src/traits/mod.rs @@ -29,7 +29,7 @@ pub use asset_transfer::{Error as AssetTransferError, TransferType, XcmAssetTran mod export; pub use export::{export_xcm, validate_export, ExportXcm}; mod fee_manager; -pub use fee_manager::{FeeManager, FeeReason, AssetConversion}; +pub use fee_manager::{AssetConversion, FeeManager, FeeReason}; mod filter_asset_location; #[allow(deprecated)] pub use filter_asset_location::FilterAssetLocation; From ba024d1eb084f86716a2614b446fa2a9edc758f7 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Mon, 1 Jul 2024 20:16:46 +0200 Subject: [PATCH 14/43] feat(xcm-executor): remove all logic regarding local execution --- polkadot/xcm/pallet-xcm/src/lib.rs | 24 +++--- polkadot/xcm/src/v4/traits.rs | 9 +-- polkadot/xcm/xcm-executor/src/lib.rs | 116 ++++++++------------------- 3 files changed, 46 insertions(+), 103 deletions(-) diff --git a/polkadot/xcm/pallet-xcm/src/lib.rs b/polkadot/xcm/pallet-xcm/src/lib.rs index dedf6fa70fe9..668f07c52ce3 100644 --- a/polkadot/xcm/pallet-xcm/src/lib.rs +++ b/polkadot/xcm/pallet-xcm/src/lib.rs @@ -1593,10 +1593,10 @@ impl Pallet { Either::Left(beneficiary), assets, assets_transfer_type, - FeesHandling::Batched { fees: fees.clone() }, + FeesHandling::Batched { fees }, weight_limit, )?; - Self::execute_xcm_transfer(origin, dest, local_xcm, remote_xcm, fees.id) + Self::execute_xcm_transfer(origin, dest, local_xcm, remote_xcm) } fn do_teleport_assets( @@ -1635,10 +1635,10 @@ impl Pallet { Either::Left(beneficiary), assets, TransferType::Teleport, - FeesHandling::Batched { fees: fees.clone() }, + FeesHandling::Batched { fees }, weight_limit, )?; - Self::execute_xcm_transfer(origin_location, dest, local_xcm, remote_xcm, fees.id) + Self::execute_xcm_transfer(origin_location, dest, local_xcm, remote_xcm) } fn do_transfer_assets( @@ -1651,11 +1651,11 @@ impl Pallet { fees_transfer_type: TransferType, weight_limit: WeightLimit, ) -> DispatchResult { - let asset_for_fees = assets.get(fee_asset_index).ok_or(Error::::Empty)?.clone(); // local and remote XCM programs to potentially handle fees separately let fees = if fees_transfer_type == assets_transfer_type { + let fees = assets.get(fee_asset_index).ok_or(Error::::Empty)?.clone(); // no need for custom fees instructions, fees are batched with assets - FeesHandling::Batched { fees: asset_for_fees.clone() } + FeesHandling::Batched { fees } } else { // Disallow _remote reserves_ unless assets & fees have same remote reserve (covered // by branch above). The reason for this is that we'd need to send XCMs to separate @@ -1704,7 +1704,7 @@ impl Pallet { fees, weight_limit, )?; - Self::execute_xcm_transfer(origin, dest, local_xcm, remote_xcm, asset_for_fees.id) + Self::execute_xcm_transfer(origin, dest, local_xcm, remote_xcm) } fn build_xcm_transfer_type( @@ -1774,7 +1774,6 @@ impl Pallet { dest: Location, mut local_xcm: Xcm<::RuntimeCall>, remote_xcm: Option>, - asset_for_fees: AssetId, ) -> DispatchResult { log::debug!( target: "xcm::pallet_xcm::execute_xcm_transfer", @@ -1805,7 +1804,7 @@ impl Pallet { let (ticket, price) = validate_send::(dest.clone(), remote_xcm.clone()) .map_err(Error::::from)?; if origin != Here.into_location() { - Self::charge_fees(origin.clone(), price, &asset_for_fees).map_err(|error| { + Self::charge_fees(origin.clone(), price).map_err(|error| { log::error!( target: "xcm::pallet_xcm::execute_xcm_transfer", "Unable to charge fee with error {:?}", error @@ -2429,8 +2428,7 @@ impl Pallet { log::debug!(target: "xcm::send_xcm", "dest: {:?}, message: {:?}", &dest, &message); let (ticket, price) = validate_send::(dest, message)?; if let Some(fee_payer) = maybe_fee_payer { - Self::charge_fees(fee_payer, price, &AssetId(Here.into())) - .map_err(|_| SendError::Fees)?; + Self::charge_fees(fee_payer, price).map_err(|_| SendError::Fees)?; } T::XcmRouter::deliver(ticket) } @@ -2669,8 +2667,8 @@ impl Pallet { /// Fails if: /// - the `assets` are not known on this chain; /// - the `assets` cannot be withdrawn with that location as the Origin. - fn charge_fees(location: Location, assets: Assets, asset_for_fees: &AssetId) -> DispatchResult { - T::XcmExecutor::charge_fees(location.clone(), assets.clone(), asset_for_fees) + fn charge_fees(location: Location, assets: Assets) -> DispatchResult { + T::XcmExecutor::charge_fees(location.clone(), assets.clone()) .map_err(|_| Error::::FeesNotMet)?; Self::deposit_event(Event::FeesPaid { paying: location, fees: assets }); Ok(()) diff --git a/polkadot/xcm/src/v4/traits.rs b/polkadot/xcm/src/v4/traits.rs index 0ddaff8facbb..351de92c80ed 100644 --- a/polkadot/xcm/src/v4/traits.rs +++ b/polkadot/xcm/src/v4/traits.rs @@ -102,8 +102,7 @@ pub trait ExecuteXcm { /// Deduct some `fees` to the sovereign account of the given `location` and place them as per /// the convention for fees. - fn charge_fees(location: impl Into, fees: Assets, asset_for_fees: &AssetId) - -> Result; + fn charge_fees(location: impl Into, fees: Assets) -> Result; } pub enum Weightless {} @@ -121,11 +120,7 @@ impl ExecuteXcm for () { fn execute(_: impl Into, _: Self::Prepared, _: &mut XcmHash, _: Weight) -> Outcome { unreachable!() } - fn charge_fees( - _location: impl Into, - _fees: Assets, - _asset_for_fees: &AssetId, - ) -> Result { + fn charge_fees(_location: impl Into, _fees: Assets) -> Result { Err(Error::Unimplemented) } } diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index cbae20fbddf0..00e814026f78 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -255,34 +255,13 @@ impl ExecuteXcm for XcmExecutor, - fees: Assets, - asset_for_fees: &AssetId, - ) -> XcmResult { + fn charge_fees(origin: impl Into, fees: Assets) -> XcmResult { let origin = origin.into(); if !Config::FeeManager::is_waived(Some(&origin), FeeReason::ChargeFees) { - log::trace!(target: "xcm::charge_fees", "Fees: {:?}", fees); - let first = fees.get(0).ok_or(XcmError::AssetNotFound)?; - log::trace!(target: "xcm::charge_fees", "Asset for fees: {:?}", asset_for_fees); - // If no conversion can be made, we use the original asset even if it's not - // the desired one, as best effort. - let asset_to_withdraw = - match Config::AssetConverter::convert_asset(&first, asset_for_fees) { - Ok(new_asset) => new_asset, - Err(error) => { - log::error!( - target: "xcm::charge_fees", - "Could not convert fees to {:?}. Error: {:?}", - asset_for_fees, - error, - ); - first.clone() - }, - }; - log::trace!(target: "xcm::charge_fees", "New asset: {:?}", asset_to_withdraw); - Config::AssetTransactor::withdraw_asset(&asset_to_withdraw, &origin, None)?; - Config::FeeManager::handle_fee(fees, None, FeeReason::ChargeFees); + for asset in fees.inner() { + Config::AssetTransactor::withdraw_asset(&asset, &origin, None)?; + } + Config::FeeManager::handle_fee(fees.into(), None, FeeReason::ChargeFees); } Ok(()) } @@ -485,11 +464,14 @@ impl XcmExecutor { self.fees_mode, reason, ); - let first = fees.get(0).ok_or(XcmError::AssetNotFound)?; + // TODO: Is it okay to just use the first asset? + // Is there a downstream team that has their own router and they use + // more than one asset for delivery fees. + let asset_needed_for_fees = fees.get(0).ok_or(XcmError::AssetNotFound)?; // If `BuyExecution` was called, we know we can try to use that asset for fees. log::trace!(target: "xcm", "Asset for fees: {:?}", self.asset_for_fees); - let actual_asset_to_use = if let Some(asset_for_fees) = &self.asset_for_fees { - match Config::AssetConverter::convert_asset(&first, &asset_for_fees) { + let asset_to_pay_for_fees = if let Some(asset_for_fees) = &self.asset_for_fees { + match Config::AssetConverter::convert_asset(&asset_needed_for_fees, &asset_for_fees) { Ok(new_asset) => new_asset, // If we can't convert, then we return the original asset. // It will error later in any case. @@ -500,67 +482,31 @@ impl XcmExecutor { asset_for_fees, error, ); - first.clone() + asset_needed_for_fees.clone() }, } } else { - // We try each item in holding. - // TODO: We don't need this if we had a way to tell the executor what asset we want to - // use for fees. - // Option 1: Always using a `BuyExecution` instruction, even locally where we would - // normally not use it. This has the downside of being a worse experience. - // Option 2: Having an instruction that sets the asset to be used for fees. - // Can only do it on a new version. - // Option 3: Change the entrypoint `prepare_and_execute` to take the fee item. - // Is a breaking change. - // Option 4: Create a new entrypoint `prepare_and_execute_with_fee_asset`. - // Should work and is not a breaking change. - // TODO: Might increase benchmarks too much. Should optimize for the average case. - let mut asset_for_fees = first.clone(); - // Holding is empty when using jit withdrawal. Could remove it. - // It's not empty because of jit withdrawal, it's empty because it's taken from holding - // before sending. That's the problem jit withdrawal was solving, we need to "park" - // delivery fees in the same way we did for `DepositReserveAsset`. - for asset in self.holding.fungible_assets_iter() { - log::trace!(target: "xcm", "Asset being tested to convert: {:?}", asset); - match Config::AssetConverter::convert_asset(&first, &asset.id) { - Ok(new_asset) => { - asset_for_fees = new_asset; - break; - }, - Err(error) => { - // It's `trace` and not `error` because we expect it to happen - // until we reach the correct implementation in the tuple. - log::trace!( - target: "xcm::take_fee", - "Could not convert fees to {:?}. Error: {:?}", - asset.id, - error, - ); - continue; - }, - } - } - asset_for_fees + asset_needed_for_fees.clone() }; - log::trace!(target: "xcm", "Actual asset for fees: {:?}", actual_asset_to_use); + log::trace!(target: "xcm", "Actual asset for fees: {:?}", asset_to_pay_for_fees); // TODO: Remove `jit_withdrawal`, it makes this much harder. let paid = if self.fees_mode.jit_withdraw { let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; Config::AssetTransactor::withdraw_asset( - &actual_asset_to_use, + &asset_to_pay_for_fees, origin, Some(&self.context), )?; - let swapped_asset = Config::AssetConverter::swap(&actual_asset_to_use, first)?; + let swapped_asset = + Config::AssetConverter::swap(&asset_to_pay_for_fees, asset_needed_for_fees)?; vec![swapped_asset].into() } else { let assets = self .holding - .try_take(actual_asset_to_use.clone().into()) + .try_take(asset_to_pay_for_fees.clone().into()) .map_err(|_| XcmError::NotHoldingFees)?; let taken_asset = assets.into_assets_iter().next().ok_or(XcmError::AssetNotFound)?; - let swapped_asset = Config::AssetConverter::swap(&taken_asset, first)?; + let swapped_asset = Config::AssetConverter::swap(&taken_asset, asset_needed_for_fees)?; vec![swapped_asset].into() }; Config::FeeManager::handle_fee(paid, Some(&self.context), reason); @@ -950,18 +896,22 @@ impl XcmExecutor { message_to_weigh.extend(xcm.0.clone().into_iter()); let (_, fee) = validate_send::(dest.clone(), Xcm(message_to_weigh))?; - let first = fee.get(0).ok_or(XcmError::AssetNotFound)?; - let asset_id = self.asset_for_fees.as_ref().unwrap_or(&first.id); + let asset_needed_for_fees = fee.get(0).ok_or(XcmError::AssetNotFound)?; + log::trace!(target: "xcm::DepositReserveAsset", "Asset wanted to pay for fees: {:?}", self.asset_for_fees); + let asset_id = + self.asset_for_fees.as_ref().unwrap_or(&asset_needed_for_fees.id); // TODO: Deal with this case. // Need to make a test specifically for this. - let actual_asset_to_use_for_fees = - match Config::AssetConverter::convert_asset(&first, asset_id) { - Ok(new_asset) => new_asset, - Err(error) => { - log::error!(target: "xcm::DepositReserveAsset", "What happened?"); - first.clone() - }, - }; + let actual_asset_to_use_for_fees = match Config::AssetConverter::convert_asset( + &asset_needed_for_fees, + asset_id, + ) { + Ok(new_asset) => new_asset, + Err(error) => { + log::warn!(target: "xcm::DepositReserveAsset", "Couldn't convert the fee asset {:?} to the needed {:?}. Error: {:?}", asset_id, asset_needed_for_fees, error); + asset_needed_for_fees.clone() + }, + }; // set aside fee to be charged by XcmSender let transport_fee = self.holding.saturating_take(actual_asset_to_use_for_fees.into()); From 69d1043e2de9f47e2da5e51359467cf77e539ea9 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Tue, 2 Jul 2024 13:18:29 +0200 Subject: [PATCH 15/43] feat: complete tests --- Cargo.lock | 1 + .../src/tests/hybrid_transfers.rs | 32 +-- .../src/tests/reserve_transfer.rs | 268 +++++++++++++++--- .../asset-hub-westend/src/xcm_config.rs | 1 - .../runtimes/assets/common/src/lib.rs | 22 +- .../runtimes/testing/penpal/Cargo.toml | 2 + .../runtimes/testing/penpal/src/lib.rs | 2 +- polkadot/xcm/xcm-executor/src/lib.rs | 8 +- 8 files changed, 263 insertions(+), 73 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 150963b5ac6b..3c3a6b1dc290 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12525,6 +12525,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/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 2481fd79eb97..72b38d38a4ca 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 @@ -282,15 +282,12 @@ fn transfer_foreign_assets_from_asset_hub_to_para() { /// work without using any other token. #[test] fn reserve_transfer_sufficient_foreign_assets_from_system_para_to_para() { - use penpal_runtime::xcm_config::ASSET_HUB_ID; - let destination = AssetHubWestend::sibling_location_of(PenpalA::para_id()); let sov_penpal_on_ahr = AssetHubWestend::sovereign_account_id_of(destination.clone()); let sender = AssetHubWestendSender::get(); let fee_amount_to_send = ASSET_HUB_WESTEND_ED * 100; let asset_amount_to_send = ASSET_HUB_WESTEND_ED * 100; let asset_owner = AssetHubWestendAssetOwner::get(); - let asset_owner_signer = ::RuntimeOrigin::signed(asset_owner.clone()); let receiver = PenpalAReceiver::get(); let roc_at_westend_parachains = Location::new(2, GlobalConsensus(NetworkId::Rococo)); @@ -334,15 +331,15 @@ fn reserve_transfer_sufficient_foreign_assets_from_system_para_to_para() { // This ED isn't reflected in any derivative in a PenpalA account. AssetHubWestend::fund_accounts(vec![(sov_penpal_on_ahr.into(), ASSET_HUB_WESTEND_ED)]); - // We want to make sure the sender doesn't have ANY native asset other than ED, so we can make - // sure we are paying delivery fees with the sufficient asset. - AssetHubWestend::execute_with(|| { - assert_ok!(::Balances::force_set_balance( - ::RuntimeOrigin::root(), - sender.clone().into(), - ASSET_HUB_WESTEND_ED, - )); - }); + // The sender needs some Relay tokens to pay for delivery fees locally. + // TODO: Also allow payment with different assets locally, right now only works remotely. + // AssetHubWestend::execute_with(|| { + // assert_ok!(::Balances::force_set_balance( + // ::RuntimeOrigin::root(), + // sender.clone().into(), + // ASSET_HUB_WESTEND_ED, + // )); + // }); let para_test_args = TestContext { sender: sender.clone(), @@ -394,8 +391,8 @@ fn reserve_transfer_sufficient_foreign_assets_from_system_para_to_para() { >::balance(roc_at_westend_parachains.clone(), &receiver) }); - // The native asset balance is the same. They weren't send or used for fees. - assert_eq!(sender_balance_before, sender_balance_after); + // TODO: When we allow payment with different assets locally, this should be the same, since they aren't used for fees. + assert!(sender_balance_before > sender_balance_after); // The assets have been sent and used for fees. assert!(sender_foreign_assets_after < sender_foreign_assets_before - asset_amount_to_send); assert!(receiver_foreign_assets_after > receiver_foreign_assets_before); @@ -406,7 +403,7 @@ pub fn system_para_to_para_sender_sufficient_foreign_asset_assertions(t: SystemP AssetHubWestend::assert_xcm_pallet_attempted_complete(None); let sov_account_of_dest = AssetHubWestend::sovereign_account_id_of(t.args.dest.clone()); - for (index, 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(); assert_expected_events!( @@ -768,12 +765,9 @@ fn transfer_foreign_assets_from_para_to_para_through_asset_hub() { 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 - let delivery_fees_amount = 31_340_000_000; // TODO: Estimate this. - // Delivery fees stay in the sender chain, so the balance of the sender's - // sovereign account reflects this. assert_eq!( wnds_in_sender_reserve_on_ah_after, - wnds_in_sender_reserve_on_ah_before - wnd_to_send + delivery_fees_amount + wnds_in_sender_reserve_on_ah_before - wnd_to_send ); assert_eq!( rocs_in_sender_reserve_on_ah_after, 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 61a4f3bc2ff3..34ceba617c61 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 @@ -1167,7 +1167,6 @@ fn reserve_transfer_sufficient_assets_from_system_para_to_para() { let fee_amount_to_send = ASSET_HUB_WESTEND_ED * 100; let asset_amount_to_send = ASSET_HUB_WESTEND_ED * 100; let asset_owner = AssetHubWestendAssetOwner::get(); - let asset_owner_signer = ::RuntimeOrigin::signed(asset_owner.clone()); let receiver = PenpalAReceiver::get(); // Create sufficient asset. @@ -1207,15 +1206,15 @@ fn reserve_transfer_sufficient_assets_from_system_para_to_para() { // This ED isn't reflected in any derivative in a PenpalA account. AssetHubWestend::fund_accounts(vec![(sov_penpal_on_ahr.into(), ASSET_HUB_WESTEND_ED)]); - // We want to make sure the sender doesn't have ANY native asset, so we can make - // sure we are paying delivery fees with the sufficient asset. - ::execute_with(|| { - assert_ok!(::Balances::force_set_balance( - ::RuntimeOrigin::root(), - sender.clone().into(), - ASSET_HUB_WESTEND_ED, - )); - }); + // The sender needs some Relay tokens to pay for delivery fees locally. + // TODO: Also allow payment with different assets locally, right now only works remotely. + // ::execute_with(|| { + // assert_ok!(::Balances::force_set_balance( + // ::RuntimeOrigin::root(), + // sender.clone().into(), + // ASSET_HUB_WESTEND_ED, + // )); + // }); let para_test_args = TestContext { sender: sender.clone(), @@ -1266,8 +1265,8 @@ fn reserve_transfer_sufficient_assets_from_system_para_to_para() { ) }); - // The native asset balance is the same. They weren't send or used for fees. - assert_eq!(sender_balance_before, sender_balance_after); + // TODO: When we allow payment with different assets locally, this should be the same, since they aren't used for fees. + assert!(sender_balance_before > sender_balance_after); // The assets have been sent and used for fees. assert!(sender_assets_after < sender_assets_before - asset_amount_to_send); assert!(receiver_foreign_assets_after > receiver_foreign_assets_before); @@ -1306,21 +1305,18 @@ fn reserve_transfer_sufficient_asset_from_para_to_para_through_asset_hub() { [Parachain(ASSET_HUB_ID), PalletInstance(ASSETS_PALLET_ID), GeneralIndex(asset_id.into())], ); - // Need to make sure account has enough for jit withdrawal of delivery fees. - // This number is too big of an estimate. - let delivery_fees_estimate = 1_000_000_000_000; // Create sufficient foreign asset. PenpalA::force_create_foreign_asset( system_para_foreign_asset_location.clone(), PenpalAssetOwner::get(), - true, // Mark it as sufficient. + false, // TODO: Doesn't matter if sufficient since we don't handle local delivery fee payment in other assets. 70_000, - vec![(sender.clone(), asset_amount_to_send + fee_amount_to_send + delivery_fees_estimate)], + vec![(sender.clone(), asset_amount_to_send + fee_amount_to_send)], ); PenpalB::force_create_foreign_asset( system_para_foreign_asset_location.clone(), PenpalAssetOwner::get(), - true, // Mark it as sufficient. + true, // Mark as sufficient. 70_000, vec![], ); @@ -1338,18 +1334,29 @@ fn reserve_transfer_sufficient_asset_from_para_to_para_through_asset_hub() { .into(); // Just to be very specific you don't need any native asset. assert_eq!(assets.len(), 1); - let fee_asset_index = 0; - // fund the Parachain Origin's SA on Relay Chain with the native tokens held in reserve + // fund the Parachain Origin's SA on AssetHub with the native tokens held in reserve AssetHubWestend::fund_accounts(vec![( sov_of_sender_on_asset_hub.into(), asset_amount_to_send * 2, )]); + // 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::execute_with(|| { + assert_ok!(::ForeignAssets::mint( + ::RuntimeOrigin::signed(PenpalAssetOwner::get()), + Location::new(1, []), + sender.clone().into(), + 10_000_000_000_000, // Big estimate to make it work. + )); + }); + // 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(), @@ -1359,7 +1366,7 @@ fn reserve_transfer_sufficient_asset_from_para_to_para_through_asset_hub() { asset_amount_to_send, assets, None, - 0, + fee_asset_index, ), }; let mut test = ParaToParaThroughAHTest::new(test_args); @@ -1553,15 +1560,15 @@ fn reserve_transfer_pool_assets_from_system_para_to_para() { }; let mut test = SystemParaToParaTest::new(test_args); - // We want to make sure the sender doesn't have ANY native asset (other than ED), - // so we can make sure we are paying delivery fees with the pool asset. - ::execute_with(|| { - assert_ok!(::Balances::force_set_balance( - ::RuntimeOrigin::root(), - sender.clone().into(), - ASSET_HUB_WESTEND_ED, - )); - }); + // The sender needs some Relay tokens to pay for delivery fees locally. + // TODO: Also allow payment with different assets locally, right now only works remotely. + // ::execute_with(|| { + // assert_ok!(::Balances::force_set_balance( + // ::RuntimeOrigin::root(), + // sender.clone().into(), + // ASSET_HUB_WESTEND_ED, + // )); + // }); let sender_initial_balance = AssetHubWestend::execute_with(|| { type Assets = ::Assets; @@ -1571,7 +1578,6 @@ fn reserve_transfer_pool_assets_from_system_para_to_para() { type Balances = ::Balances; Balances::free_balance(&sender) }); - assert_eq!(sender_initial_native_balance, ASSET_HUB_WESTEND_ED); let receiver_initial_balance = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; >::balance(custom_asset_penpal_pov.clone(), &receiver) @@ -1595,11 +1601,205 @@ fn reserve_transfer_pool_assets_from_system_para_to_para() { >::balance(custom_asset_penpal_pov, &receiver) }); - // Native amount stays the same. - assert_eq!(sender_after_native_balance, sender_initial_native_balance); + // 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!(sender_after_balance < sender_initial_balance - asset_amount_to_send); + 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 - Pool Asset - Parachain->AssetHub->Parachain ==== +// ========================================================================== +// From Penpal A to Penpal B with AssetHub as the reserve. +#[test] +fn reserve_transfer_pool_assets_from_para_to_para_through_asset_hub() { + use penpal_runtime::xcm_config::ASSET_HUB_ID; + + 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_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_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-AHR 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 asset_id = 1984; + AssetHubWestend::force_create_asset( + asset_id, + AssetHubWestendAssetOwner::get(), + true, // Not sufficient, using a pool. + 70_000, + vec![(sov_of_sender_on_asset_hub.clone(), asset_amount_to_send + fee_amount_to_send)], + ); + + // We create a pool between USDT and the Relay token in AssetHub. + let native_asset = RelayLocationV3::get(); + let asset_id = 1984u32; // USDT. + let pool_asset = xcm::v3::Location::new( + 0, + [ + xcm::v3::Junction::PalletInstance(ASSETS_PALLET_ID), + xcm::v3::Junction::GeneralIndex(asset_id.into()), + ], + ); + + // Setup the pool between `native_asset` and `pool_asset` on AssetHub. + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // set up pool with asset_id <> native pair + assert!(::Assets::asset_exists(asset_id)); + + assert_ok!(::Assets::mint( + ::RuntimeOrigin::signed(AssetHubWestendSender::get()), + asset_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(pool_asset), + )); + + 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(pool_asset), + 1_000_000_000_000, + 2_000_000_000_000, // `pool_asset` is worth half of `native_asset` + 0, + 0, + AssetHubWestendSender::get().into() + )); + + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {lp_token_minted, .. }) => { lp_token_minted: *lp_token_minted == 1414213562273, }, + ] + ); + }); + + let system_para_foreign_asset_location = Location::new( + 1, + [Parachain(ASSET_HUB_ID), PalletInstance(ASSETS_PALLET_ID), GeneralIndex(asset_id.into())], + ); + PenpalA::force_create_foreign_asset( + system_para_foreign_asset_location.clone(), + PenpalAssetOwner::get(), + false, // Asset not sufficient locally. + 70_000, + vec![(sender.clone(), asset_amount_to_send + fee_amount_to_send)], + ); + PenpalB::force_create_foreign_asset( + system_para_foreign_asset_location.clone(), + PenpalAssetOwner::get(), + true, // Mark asset as sufficient. + 70_000, + vec![], + ); + + // Prepare assets to transfer. + let assets: Assets = vec![( + ( + Parent, + Parachain(ASSET_HUB_ID), + PalletInstance(ASSETS_PALLET_ID), + GeneralIndex(asset_id.into()), + ), + asset_amount_to_send + fee_amount_to_send, + ).into()].into(); + // Just to be very specific you don't need any native asset. + assert_eq!(assets.len(), 1); + + // fund the Parachain Origin's SA on AssetHub with the native tokens held in reserve + AssetHubWestend::fund_accounts(vec![( + sov_of_sender_on_asset_hub.into(), + asset_amount_to_send * 2, + )]); + + // 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::execute_with(|| { + assert_ok!(::ForeignAssets::mint( + ::RuntimeOrigin::signed(PenpalAssetOwner::get()), + Location::new(1, []), + sender.clone().into(), + 10_000_000_000_000, // Big estimate to make it work. + )); + }); + + // 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(system_para_foreign_asset_location.clone(), &sender) + }); + let receiver_assets_before = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance( + system_para_foreign_asset_location.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(system_para_foreign_asset_location.clone(), &sender) + }); + let receiver_assets_after = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(system_para_foreign_asset_location, &receiver) + }); + + // Sender's balance is reduced by amount sent plus delivery fees + 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/runtimes/assets/asset-hub-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs index 8c626e8e839a..f1e45709aefa 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 @@ -24,7 +24,6 @@ use assets_common::{ matching::{FromSiblingParachain, IsForeignConcreteAsset}, SufficientAssetConverter, SwapAssetConverter, TrustBackedAssetsAsLocation, }; -use core::marker::PhantomData; use frame_support::{ parameter_types, traits::{ diff --git a/cumulus/parachains/runtimes/assets/common/src/lib.rs b/cumulus/parachains/runtimes/assets/common/src/lib.rs index cc2e328088ea..46212cf16776 100644 --- a/cumulus/parachains/runtimes/assets/common/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/common/src/lib.rs @@ -26,7 +26,6 @@ pub mod runtime_api; use crate::matching::{LocalLocationPattern, ParentLocation}; use core::marker::PhantomData; use frame_support::{ - ensure, traits::{fungibles, tokens::ConversionToAssetBalance, Equals, EverythingBut, Get}, }; use pallet_asset_conversion::SwapCredit as SwapCreditT; @@ -162,24 +161,25 @@ where // TODO: Not the best still. let desired_asset: Asset = (asset_id.clone(), 1u128).into(); // To comply with the interface. let (local_asset_id, _) = Matcher::matches_fungibles(&desired_asset).map_err(|error| { - log::error!( + log::trace!( target: "xcm::SufficientAssetConverter::convert_asset", - "Could not map XCM asset {:?} to FRAME asset", + "Could not map XCM asset {:?} to FRAME asset. Error: {:?}", asset_id, + error, ); XcmError::AssetNotFound })?; log::trace!(target: "xcm::SufficientAssetConverter::convert_asset", "local asset id: {:?}", local_asset_id); let Fungibility::Fungible(old_asset_amount) = asset.fun else { - log::error!( + log::trace!( target: "xcm::SufficientAssetConverter::convert_asset", "Fee asset is not fungible", ); return Err(XcmError::AssetNotFound); }; let new_asset_amount = BalanceConverter::to_asset_balance(old_asset_amount, local_asset_id) - .map_err(|error| { - log::error!( + .map_err(|_| { + log::trace!( target: "xcm::SufficientAssetConverter::convert_asset", "Couldn't convert balance of {:?} to balance of {:?}", asset.id, @@ -187,7 +187,7 @@ where ); XcmError::TooExpensive })?; - let new_asset_amount: u128 = new_asset_amount.try_into().map_err(|error| { + let new_asset_amount: u128 = new_asset_amount.try_into().map_err(|_| { log::error!( target: "xcm::SufficientAssetConverter::convert_asset", "Converting balance of {:?} to u128 would overflow", @@ -200,10 +200,10 @@ where fn swap(give: &Asset, want: &Asset) -> Result { // We need the matcher to select this implementation from the tuple. - let (fungibles_asset, balance) = Matcher::matches_fungibles(give).map_err(|error| { - log::error!( + let _ = Matcher::matches_fungibles(give).map_err(|_| { + log::trace!( target: "xcm::SufficientAssetConverter::swap", - "Could not map XCM asset {:?} to FRAME asset", + "Could not map XCM asset {:?} to FRAME asset.", give, ); XcmError::AssetNotFound @@ -235,7 +235,7 @@ where fn convert_asset(asset: &Asset, asset_id: &AssetId) -> Result { // TODO: Not the best still. let desired_asset: Asset = (asset_id.clone(), 1u128).into(); // To comply with the interface. - let (fungibles_asset, _) = Matcher::matches_fungibles(&desired_asset).map_err(|error| { + let (fungibles_asset, _) = Matcher::matches_fungibles(&desired_asset).map_err(|_| { // Using `trace` instead of `error` since we expect this to happen // when using multiple implementations in a tuple. log::trace!( diff --git a/cumulus/parachains/runtimes/testing/penpal/Cargo.toml b/cumulus/parachains/runtimes/testing/penpal/Cargo.toml index 770bdc9fbda6..edcb350503d0 100644 --- a/cumulus/parachains/runtimes/testing/penpal/Cargo.toml +++ b/cumulus/parachains/runtimes/testing/penpal/Cargo.toml @@ -81,6 +81,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 = [ diff --git a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs index 0e52e6943b6e..f0b1f0fa2016 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs @@ -49,7 +49,7 @@ use frame_support::{ traits::{ tokens::{fungible, fungibles, imbalance::ResolveAssetTo}, AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, Everything, - PalletInfoAccess, TransformOrigin, + TransformOrigin, }, weights::{ constants::WEIGHT_REF_TIME_PER_SECOND, ConstantMultiplier, FeePolynomial, WeightToFee as _, diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index 00e814026f78..f88392718b7a 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -369,12 +369,6 @@ impl XcmExecutor { target: "xcm::send", "Sending msg: {msg:?}, to destination: {dest:?}, (reason: {reason:?})" ); let (ticket, fee) = validate_send::(dest, msg)?; - // `take_fee` takes in the fee in DOT. - // It should use `AssetConverter` to change it to USDC, for example. - // How it knows that is a mystery. - // `self.asset_for_fees` is not populated most of the time. - // `self.holding` can only have enough USDC if I do the same calculation before to park the - // fees. self.take_fee(fee, reason)?; Config::XcmSender::deliver(ticket).map_err(Into::into) } @@ -476,7 +470,7 @@ impl XcmExecutor { // If we can't convert, then we return the original asset. // It will error later in any case. Err(error) => { - log::error!( + log::trace!( target: "xcm::take_fee", "Could not convert fees to {:?}. Error: {:?}", asset_for_fees, From 820160eefcae6aaa52a83711144cd2d697a242bf Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Tue, 2 Jul 2024 13:18:47 +0200 Subject: [PATCH 16/43] fix: fmt --- .../asset-hub-westend/src/tests/hybrid_transfers.rs | 3 ++- .../asset-hub-westend/src/tests/reserve_transfer.rs | 13 +++++++++---- .../parachains/runtimes/assets/common/src/lib.rs | 4 ++-- 3 files changed, 13 insertions(+), 7 deletions(-) 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 72b38d38a4ca..3941b27846cf 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 @@ -391,7 +391,8 @@ fn reserve_transfer_sufficient_foreign_assets_from_system_para_to_para() { >::balance(roc_at_westend_parachains.clone(), &receiver) }); - // TODO: When we allow payment with different assets locally, this should be the same, since they aren't used for fees. + // TODO: When we allow payment with different assets locally, this should be the same, since + // they aren't used for fees. assert!(sender_balance_before > sender_balance_after); // The assets have been sent and used for fees. assert!(sender_foreign_assets_after < sender_foreign_assets_before - asset_amount_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 34ceba617c61..a1d8281b45e4 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 @@ -1265,7 +1265,8 @@ fn reserve_transfer_sufficient_assets_from_system_para_to_para() { ) }); - // TODO: When we allow payment with different assets locally, this should be the same, since they aren't used for fees. + // TODO: When we allow payment with different assets locally, this should be the same, since + // they aren't used for fees. assert!(sender_balance_before > sender_balance_after); // The assets have been sent and used for fees. assert!(sender_assets_after < sender_assets_before - asset_amount_to_send); @@ -1309,7 +1310,8 @@ fn reserve_transfer_sufficient_asset_from_para_to_para_through_asset_hub() { PenpalA::force_create_foreign_asset( system_para_foreign_asset_location.clone(), PenpalAssetOwner::get(), - false, // TODO: Doesn't matter if sufficient since we don't handle local delivery fee payment in other assets. + false, /* TODO: Doesn't matter if sufficient since we don't handle local delivery fee + * payment in other assets. */ 70_000, vec![(sender.clone(), asset_amount_to_send + fee_amount_to_send)], ); @@ -1601,7 +1603,8 @@ fn reserve_transfer_pool_assets_from_system_para_to_para() { >::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. + // 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); @@ -1731,7 +1734,9 @@ fn reserve_transfer_pool_assets_from_para_to_para_through_asset_hub() { GeneralIndex(asset_id.into()), ), asset_amount_to_send + fee_amount_to_send, - ).into()].into(); + ) + .into()] + .into(); // Just to be very specific you don't need any native asset. assert_eq!(assets.len(), 1); diff --git a/cumulus/parachains/runtimes/assets/common/src/lib.rs b/cumulus/parachains/runtimes/assets/common/src/lib.rs index 46212cf16776..e3e3bf1f48a6 100644 --- a/cumulus/parachains/runtimes/assets/common/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/common/src/lib.rs @@ -25,8 +25,8 @@ pub mod runtime_api; use crate::matching::{LocalLocationPattern, ParentLocation}; use core::marker::PhantomData; -use frame_support::{ - traits::{fungibles, tokens::ConversionToAssetBalance, Equals, EverythingBut, Get}, +use frame_support::traits::{ + fungibles, tokens::ConversionToAssetBalance, Equals, EverythingBut, Get, }; use pallet_asset_conversion::SwapCredit as SwapCreditT; use parachains_common::{AssetIdForTrustBackedAssets, CollectionId, ItemId}; From 99c97496a84d0a151975081149adb903ee77ceca Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Tue, 2 Jul 2024 22:37:12 +0200 Subject: [PATCH 17/43] feat: remove support for sufficient asset converters --- .../src/tests/hybrid_transfers.rs | 149 ---------- .../src/tests/reserve_transfer.rs | 278 +----------------- .../asset-hub-westend/src/xcm_config.rs | 28 +- .../runtimes/assets/common/src/lib.rs | 105 +------ .../runtimes/testing/penpal/src/xcm_config.rs | 30 +- polkadot/xcm/xcm-executor/src/lib.rs | 12 +- .../xcm-executor/src/traits/fee_manager.rs | 61 +--- 7 files changed, 36 insertions(+), 627 deletions(-) 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 3941b27846cf..657de504ccb7 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 @@ -275,155 +275,6 @@ fn transfer_foreign_assets_from_asset_hub_to_para() { assert_eq!(receiver_rocs_after, receiver_rocs_before + foreign_amount_to_send); } -// ================================================================================= -// ======= Reserve Transfers - Sufficient Foreign Asset - AssetHub<>Parachain ====== -// ================================================================================= -/// Reserve Transfers of a sufficient foreign asset from System Parachain to Parachain should -/// work without using any other token. -#[test] -fn reserve_transfer_sufficient_foreign_assets_from_system_para_to_para() { - let destination = AssetHubWestend::sibling_location_of(PenpalA::para_id()); - let sov_penpal_on_ahr = AssetHubWestend::sovereign_account_id_of(destination.clone()); - let sender = AssetHubWestendSender::get(); - let fee_amount_to_send = ASSET_HUB_WESTEND_ED * 100; - let asset_amount_to_send = ASSET_HUB_WESTEND_ED * 100; - let asset_owner = AssetHubWestendAssetOwner::get(); - let receiver = PenpalAReceiver::get(); - - let roc_at_westend_parachains = Location::new(2, GlobalConsensus(NetworkId::Rococo)); - // Configure destination chain to trust AH as reserve of ROC - PenpalA::execute_with(|| { - assert_ok!(::System::set_storage( - ::RuntimeOrigin::root(), - vec![( - penpal_runtime::xcm_config::CustomizableAssetFromSystemAssetHub::key().to_vec(), - roc_at_westend_parachains.encode(), - )], - )); - }); - // Create sufficient asset. - PenpalA::force_create_foreign_asset( - roc_at_westend_parachains.clone(), - PenpalAssetOwner::get(), - true, // Mark it as sufficient. - 10_000_000_000, - vec![], - ); - AssetHubWestend::force_create_foreign_asset( - roc_at_westend_parachains.clone().try_into().unwrap(), - asset_owner, - true, // Mark it as sufficient. - 10_000_000_000, - vec![(sender.clone(), ASSET_HUB_WESTEND_ED * 10000)], - ); - - let assets: Assets = vec![( - (Parent, Parent, GlobalConsensus(NetworkId::Rococo)), - asset_amount_to_send + fee_amount_to_send, - ) - .into()] - .into(); - // Just to be very clear we don't need to send any native asset. - assert_eq!(assets.len(), 1); - let fee_asset_index = 0; - - // Create SA-of-Penpal-on-AHR with ED. - // This ED isn't reflected in any derivative in a PenpalA account. - AssetHubWestend::fund_accounts(vec![(sov_penpal_on_ahr.into(), ASSET_HUB_WESTEND_ED)]); - - // The sender needs some Relay tokens to pay for delivery fees locally. - // TODO: Also allow payment with different assets locally, right now only works remotely. - // AssetHubWestend::execute_with(|| { - // assert_ok!(::Balances::force_set_balance( - // ::RuntimeOrigin::root(), - // sender.clone().into(), - // ASSET_HUB_WESTEND_ED, - // )); - // }); - - let para_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 = SystemParaToParaTest::new(para_test_args); - - // Query initial balances. - let sender_balance_before = test.sender.balance; - let sender_foreign_assets_before = AssetHubWestend::execute_with(|| { - type Assets = ::ForeignAssets; - >::balance( - roc_at_westend_parachains.clone().try_into().unwrap(), - &sender, - ) - }); - let receiver_foreign_assets_before = PenpalA::execute_with(|| { - type ForeignAssets = ::ForeignAssets; - >::balance(roc_at_westend_parachains.clone(), &receiver) - }); - - // Set assertions and dispatchables - test.set_assertion::( - system_para_to_para_sender_sufficient_foreign_asset_assertions, - ); - test.set_assertion::(system_para_to_para_receiver_assertions); - test.set_dispatchable::(ah_to_para_transfer_assets); - test.assert(); - - // Query initial balances. - let sender_balance_after = test.sender.balance; - let sender_foreign_assets_after = AssetHubWestend::execute_with(|| { - type Assets = ::ForeignAssets; - >::balance( - roc_at_westend_parachains.clone().try_into().unwrap(), - &sender, - ) - }); - let receiver_foreign_assets_after = PenpalA::execute_with(|| { - type ForeignAssets = ::ForeignAssets; - >::balance(roc_at_westend_parachains.clone(), &receiver) - }); - - // TODO: When we allow payment with different assets locally, this should be the same, since - // they aren't used for fees. - assert!(sender_balance_before > sender_balance_after); - // The assets have been sent and used for fees. - assert!(sender_foreign_assets_after < sender_foreign_assets_before - asset_amount_to_send); - assert!(receiver_foreign_assets_after > receiver_foreign_assets_before); -} - -pub fn system_para_to_para_sender_sufficient_foreign_asset_assertions(t: SystemParaToParaTest) { - type RuntimeEvent = ::RuntimeEvent; - AssetHubWestend::assert_xcm_pallet_attempted_complete(None); - - let sov_account_of_dest = AssetHubWestend::sovereign_account_id_of(t.args.dest.clone()); - 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(); - assert_expected_events!( - AssetHubWestend, - vec![ - // Amount of foreign asset is transferred to Parachain's sovereign account. - RuntimeEvent::ForeignAssets( - pallet_assets::Event::Transferred { asset_id, from, to, amount }, - ) => { - asset_id: *asset_id == expected_id, - from: *from == t.sender.account_id, // Sender is test sender. - to: *to == sov_account_of_dest, // Receiver is parachain's sovereign account. - amount: *amount == asset_amount, // Asset amount is the one specified in the test. - }, - ] - ); - } -} - /// Reserve Transfers of native asset from Parachain to System Parachain should work // =========================================================================== // ======= Transfer - Native + Bridged Assets - Parachain->AssetHub ========== 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 a1d8281b45e4..9cd79ab1254e 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,266 +1152,6 @@ fn reserve_transfer_native_asset_from_para_to_para_through_relay() { assert!(receiver_assets_after < receiver_assets_before + amount_to_send); } -// ========================================================================= -// ======= Reserve Transfers - Sufficient Asset - AssetHub->Parachain ====== -// ========================================================================= -/// Reserve Transfers of a sufficient asset from System Parachain to Parachain should -/// work without using any other token. -#[test] -fn reserve_transfer_sufficient_assets_from_system_para_to_para() { - use penpal_runtime::xcm_config::ASSET_HUB_ID; - - let destination = AssetHubWestend::sibling_location_of(PenpalA::para_id()); - let sov_penpal_on_ahr = AssetHubWestend::sovereign_account_id_of(destination.clone()); - let sender = AssetHubWestendSender::get(); - let fee_amount_to_send = ASSET_HUB_WESTEND_ED * 100; - let asset_amount_to_send = ASSET_HUB_WESTEND_ED * 100; - let asset_owner = AssetHubWestendAssetOwner::get(); - let receiver = PenpalAReceiver::get(); - - // Create sufficient asset. - let asset_id = 1984; - AssetHubWestend::force_create_asset( - asset_id, - asset_owner, - true, // Mark it as sufficient. - 70_000, - vec![(sender.clone(), ASSET_HUB_WESTEND_ED * 10000)], - ); - let system_para_foreign_asset_location = Location::new( - 1, - [Parachain(ASSET_HUB_ID), PalletInstance(ASSETS_PALLET_ID), GeneralIndex(asset_id.into())], - ); - - // Create sufficient foreign asset. - PenpalA::force_create_foreign_asset( - system_para_foreign_asset_location.clone(), - PenpalAssetOwner::get(), - true, // Mark it as sufficient. - 70_000, - vec![], - ); - - let assets: Assets = vec![( - (PalletInstance(ASSETS_PALLET_ID), GeneralIndex(asset_id.into())), - asset_amount_to_send + fee_amount_to_send, - ) - .into()] - .into(); - // Just to be very specific you don't need any native asset. - assert_eq!(assets.len(), 1); - let fee_asset_index = 0; - - // Create SA-of-Penpal-on-AHR with ED. - // This ED isn't reflected in any derivative in a PenpalA account. - AssetHubWestend::fund_accounts(vec![(sov_penpal_on_ahr.into(), ASSET_HUB_WESTEND_ED)]); - - // The sender needs some Relay tokens to pay for delivery fees locally. - // TODO: Also allow payment with different assets locally, right now only works remotely. - // ::execute_with(|| { - // assert_ok!(::Balances::force_set_balance( - // ::RuntimeOrigin::root(), - // sender.clone().into(), - // ASSET_HUB_WESTEND_ED, - // )); - // }); - - let para_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 = SystemParaToParaTest::new(para_test_args); - - // Query initial balances. - let sender_balance_before = test.sender.balance; - let sender_assets_before = AssetHubWestend::execute_with(|| { - type Assets = ::Assets; - >::balance(asset_id, &sender) - }); - let receiver_foreign_assets_before = PenpalA::execute_with(|| { - type ForeignAssets = ::ForeignAssets; - >::balance( - system_para_foreign_asset_location.clone(), - &receiver, - ) - }); - - // Set assertions and dispatchables - // TODO: Bring back assertions. - // test.set_assertion::(system_para_to_para_assets_sender_assertions); - // test.set_assertion::(system_para_to_para_assets_receiver_assertions); - test.set_dispatchable::(system_para_to_para_reserve_transfer_assets); - test.assert(); - - // Query initial balances. - let sender_balance_after = test.sender.balance; - let sender_assets_after = AssetHubWestend::execute_with(|| { - type Assets = ::Assets; - >::balance(asset_id, &sender) - }); - let receiver_foreign_assets_after = PenpalA::execute_with(|| { - type ForeignAssets = ::ForeignAssets; - >::balance( - system_para_foreign_asset_location.clone(), - &receiver, - ) - }); - - // TODO: When we allow payment with different assets locally, this should be the same, since - // they aren't used for fees. - assert!(sender_balance_before > sender_balance_after); - // The assets have been sent and used for fees. - assert!(sender_assets_after < sender_assets_before - asset_amount_to_send); - assert!(receiver_foreign_assets_after > receiver_foreign_assets_before); -} - -// ================================================================================ -// ===== Reserve Transfers - Sufficient Asset - Parachain->AssetHub->Parachain ==== -// ================================================================================ -/// Reserve Transfers of a sufficient trust-backed asset from Parachain to Parachain (through Relay -/// reserve) should work -#[test] -fn reserve_transfer_sufficient_asset_from_para_to_para_through_asset_hub() { - use penpal_runtime::xcm_config::ASSET_HUB_ID; - - // Init values for Parachain Origin - 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 asset_owner = PenpalAssetOwner::get(); - let sender_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_as_seen_by_asset_hub); - - // Create sufficient asset. - let asset_id = 1984; - AssetHubWestend::force_create_asset( - asset_id, - asset_owner, - true, // Mark it as sufficient. - 70_000, - vec![(sov_of_sender_on_asset_hub.clone(), asset_amount_to_send + fee_amount_to_send)], - ); - let system_para_foreign_asset_location = Location::new( - 1, - [Parachain(ASSET_HUB_ID), PalletInstance(ASSETS_PALLET_ID), GeneralIndex(asset_id.into())], - ); - - // Create sufficient foreign asset. - PenpalA::force_create_foreign_asset( - system_para_foreign_asset_location.clone(), - PenpalAssetOwner::get(), - false, /* TODO: Doesn't matter if sufficient since we don't handle local delivery fee - * payment in other assets. */ - 70_000, - vec![(sender.clone(), asset_amount_to_send + fee_amount_to_send)], - ); - PenpalB::force_create_foreign_asset( - system_para_foreign_asset_location.clone(), - PenpalAssetOwner::get(), - true, // Mark as sufficient. - 70_000, - vec![], - ); - - let assets: Assets = vec![( - ( - Parent, - Parachain(ASSET_HUB_ID), - PalletInstance(ASSETS_PALLET_ID), - GeneralIndex(asset_id.into()), - ), - asset_amount_to_send + fee_amount_to_send, - ) - .into()] - .into(); - // Just to be very specific you don't need any native asset. - assert_eq!(assets.len(), 1); - - // fund the Parachain Origin's SA on AssetHub with the native tokens held in reserve - AssetHubWestend::fund_accounts(vec![( - sov_of_sender_on_asset_hub.into(), - asset_amount_to_send * 2, - )]); - - // 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::execute_with(|| { - assert_ok!(::ForeignAssets::mint( - ::RuntimeOrigin::signed(PenpalAssetOwner::get()), - Location::new(1, []), - sender.clone().into(), - 10_000_000_000_000, // Big estimate to make it work. - )); - }); - - // 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(system_para_foreign_asset_location.clone(), &sender) - }); - let receiver_assets_before = PenpalB::execute_with(|| { - type ForeignAssets = ::ForeignAssets; - >::balance( - system_para_foreign_asset_location.clone(), - &receiver, - ) - }); - - // Set assertions and dispatchables - // TODO: Bring back assertions. - // test.set_assertion::(para_to_para_through_hop_sender_assertions); - // test.set_assertion::(para_to_para_relay_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(system_para_foreign_asset_location.clone(), &sender) - }); - let receiver_assets_after = PenpalB::execute_with(|| { - type ForeignAssets = ::ForeignAssets; - >::balance(system_para_foreign_asset_location, &receiver) - }); - - // Sender's balance is reduced by amount sent plus delivery fees - assert!(sender_assets_after < sender_assets_before - asset_amount_to_send); - // Receiver's balance is increased - assert!(receiver_assets_after > receiver_assets_before); -} - // =============================================================== // ===== Reserve Transfers - Pool Asset - AssetHub->Parachain ==== // =============================================================== @@ -1713,7 +1453,7 @@ fn reserve_transfer_pool_assets_from_para_to_para_through_asset_hub() { PenpalA::force_create_foreign_asset( system_para_foreign_asset_location.clone(), PenpalAssetOwner::get(), - false, // Asset not sufficient locally. + true, // Mark asset as sufficient. 70_000, vec![(sender.clone(), asset_amount_to_send + fee_amount_to_send)], ); @@ -1748,14 +1488,12 @@ fn reserve_transfer_pool_assets_from_para_to_para_through_asset_hub() { // 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::execute_with(|| { - assert_ok!(::ForeignAssets::mint( - ::RuntimeOrigin::signed(PenpalAssetOwner::get()), - Location::new(1, []), - sender.clone().into(), - 10_000_000_000_000, // Big estimate to make it work. - )); - }); + 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(); @@ -1803,7 +1541,7 @@ fn reserve_transfer_pool_assets_from_para_to_para_through_asset_hub() { >::balance(system_para_foreign_asset_location, &receiver) }); - // Sender's balance is reduced by amount sent plus delivery fees + // 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/runtimes/assets/asset-hub-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs index f1e45709aefa..71977afc86c9 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 @@ -22,7 +22,7 @@ use super::{ }; use assets_common::{ matching::{FromSiblingParachain, IsForeignConcreteAsset}, - SufficientAssetConverter, SwapAssetConverter, TrustBackedAssetsAsLocation, + SwapAssetConverter, TrustBackedAssetsAsLocation, }; use frame_support::{ parameter_types, @@ -349,26 +349,6 @@ pub type TrustedTeleporters = ( IsForeignConcreteAsset>>, ); -/// Asset converter for trust-backed assets. -/// Used to convert assets marked as `sufficient` into the asset needed for fee payment. -/// This type allows paying fees in `sufficient` trust backed-assets. -pub type TrustBackedSufficientAssetsConverter = SufficientAssetConverter< - Runtime, - TrustBackedAssetsConvertedConcreteId, - pallet_assets::BalanceToAssetBalance, - TrustBackedAssetsInstance, ->; - -/// Asset converter for foreign assets. -/// Used to convert assets marked as `sufficient` into the asset needed for fee payment. -/// This type allows paying fees in `sufficient` foreign assets. -pub type ForeignSufficientAssetsConverter = SufficientAssetConverter< - Runtime, - ForeignAssetsConvertedConcreteId, - pallet_assets::BalanceToAssetBalance, - ForeignAssetsInstance, ->; - /// 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. @@ -455,11 +435,7 @@ impl xcm_executor::Config for XcmConfig { >, >, ); - type AssetConverter = ( - PoolAssetsConverter, - TrustBackedSufficientAssetsConverter, - ForeignSufficientAssetsConverter, - ); + type AssetConverter = PoolAssetsConverter; type ResponseHandler = PolkadotXcm; type AssetTrap = PolkadotXcm; type AssetClaims = PolkadotXcm; diff --git a/cumulus/parachains/runtimes/assets/common/src/lib.rs b/cumulus/parachains/runtimes/assets/common/src/lib.rs index e3e3bf1f48a6..9f71b50abe3f 100644 --- a/cumulus/parachains/runtimes/assets/common/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/common/src/lib.rs @@ -26,7 +26,7 @@ pub mod runtime_api; use crate::matching::{LocalLocationPattern, ParentLocation}; use core::marker::PhantomData; use frame_support::traits::{ - fungibles, tokens::ConversionToAssetBalance, Equals, EverythingBut, Get, + fungibles, Equals, EverythingBut, Get, }; use pallet_asset_conversion::SwapCredit as SwapCreditT; use parachains_common::{AssetIdForTrustBackedAssets, CollectionId, ItemId}; @@ -138,81 +138,6 @@ pub type PoolAssetsConvertedConcreteId = TryConvertInto, >; -pub struct SufficientAssetConverter( - PhantomData<(Runtime, Matcher, BalanceConverter, AssetsInstance)>, -); -impl AssetConversion - for SufficientAssetConverter -where - Runtime: pallet_assets::Config, - Matcher: MatchesFungibles< - >::AssetId, - >::Balance, - >, - BalanceConverter: ConversionToAssetBalance< - u128, - >::AssetId, - >::Balance, - >, - AssetsInstance: 'static, - >::Balance: Into, -{ - fn convert_asset(asset: &Asset, asset_id: &AssetId) -> Result { - // TODO: Not the best still. - let desired_asset: Asset = (asset_id.clone(), 1u128).into(); // To comply with the interface. - let (local_asset_id, _) = Matcher::matches_fungibles(&desired_asset).map_err(|error| { - log::trace!( - target: "xcm::SufficientAssetConverter::convert_asset", - "Could not map XCM asset {:?} to FRAME asset. Error: {:?}", - asset_id, - error, - ); - XcmError::AssetNotFound - })?; - log::trace!(target: "xcm::SufficientAssetConverter::convert_asset", "local asset id: {:?}", local_asset_id); - let Fungibility::Fungible(old_asset_amount) = asset.fun else { - log::trace!( - target: "xcm::SufficientAssetConverter::convert_asset", - "Fee asset is not fungible", - ); - return Err(XcmError::AssetNotFound); - }; - let new_asset_amount = BalanceConverter::to_asset_balance(old_asset_amount, local_asset_id) - .map_err(|_| { - log::trace!( - target: "xcm::SufficientAssetConverter::convert_asset", - "Couldn't convert balance of {:?} to balance of {:?}", - asset.id, - desired_asset.id, - ); - XcmError::TooExpensive - })?; - let new_asset_amount: u128 = new_asset_amount.try_into().map_err(|_| { - log::error!( - target: "xcm::SufficientAssetConverter::convert_asset", - "Converting balance of {:?} to u128 would overflow", - desired_asset.id, - ); - XcmError::Overflow - })?; - Ok((desired_asset.id.clone(), new_asset_amount).into()) - } - - fn swap(give: &Asset, want: &Asset) -> Result { - // We need the matcher to select this implementation from the tuple. - let _ = Matcher::matches_fungibles(give).map_err(|_| { - log::trace!( - target: "xcm::SufficientAssetConverter::swap", - "Could not map XCM asset {:?} to FRAME asset.", - give, - ); - XcmError::AssetNotFound - })?; - // We don't do a swap, we just returned the asset we need for fees. - Ok(want.clone()) - } -} - /// Implementation of `AssetConverter` that pays delivery fees by swapping the given asset to the /// `Target` asset accepted for fees. pub struct SwapAssetConverter( @@ -233,7 +158,10 @@ where >, { fn convert_asset(asset: &Asset, asset_id: &AssetId) -> Result { - // TODO: Not the best still. + // We return `asset` immediately if that's the asset wanted to pay for fees. + if asset.id == *asset_id { + return Ok(asset.clone()); + } let desired_asset: Asset = (asset_id.clone(), 1u128).into(); // To comply with the interface. let (fungibles_asset, _) = Matcher::matches_fungibles(&desired_asset).map_err(|_| { // Using `trace` instead of `error` since we expect this to happen @@ -246,21 +174,13 @@ where XcmError::AssetNotFound })?; let Fungibility::Fungible(old_asset_amount) = asset.fun else { - // Using `trace` instead of `error` since this could happen if an - // implementation was added to pay fees with NFTs. - // It just wouldn't be this one. - log::trace!( + log::error!( target: "xcm::SwapAssetConverter::convert_asset", "Fee asset {:?} is not fungible", asset, ); return Err(XcmError::AssetNotFound); }; - let swap_asset = fungibles_asset.clone().into(); - if Target::get().eq(&swap_asset) { - // Converter not applicable. - return Err(XcmError::FeesNotMet); - } let new_asset_amount = pallet_asset_conversion::Pallet::::quote_price_tokens_for_exact_tokens( @@ -275,9 +195,13 @@ where } fn swap(give: &Asset, want: &Asset) -> Result { + if *give == *want { + // Swap not needed. + return Ok(give.clone()); + } let (fungibles_asset, balance) = Matcher::matches_fungibles(&give).map_err(|error| { log::trace!( - target: "xcm::SwapAssetConverter::convert_asset", + target: "xcm::SwapAssetConverter::swap", "Could not map XCM asset {:?} to FRAME asset. Error: {:?}", give, error, @@ -286,18 +210,13 @@ where })?; let Fungibility::Fungible(fee_amount) = want.fun else { log::error!( - target: "xcm::SwapAssetConverter::convert_asset", + target: "xcm::SwapAssetConverter::swap", "Fee asset is not fungible", ); return Err(XcmError::AssetNotFound); }; let swap_asset = fungibles_asset.clone().into(); - if Target::get().eq(&swap_asset) { - // Converter not applicable. - return Err(XcmError::FeesNotMet); - } - let credit_in = Fungibles::issue(fungibles_asset, balance); // Swap the user's asset for `asset`. diff --git a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs index 4b90f052ebdc..d53073ba225c 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs @@ -26,10 +26,10 @@ use super::{ AccountId, AllPalletsWithSystem, AssetId as AssetIdPalletAssets, Assets, Authorship, Balance, Balances, CollatorSelection, ForeignAssets, ForeignAssetsInstance, NonZeroIssuance, ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, - TrustBackedAssetsInstance, WeightToFee, XcmpQueue, + WeightToFee, XcmpQueue, }; use crate::{BaseDeliveryFee, FeeAssetId, TransactionByteFee}; -use assets_common::{SufficientAssetConverter, SwapAssetConverter, TrustBackedAssetsAsLocation}; +use assets_common::{SwapAssetConverter, TrustBackedAssetsAsLocation}; use core::marker::PhantomData; use frame_support::{ parameter_types, @@ -324,26 +324,6 @@ pub type TrustedTeleporters = pub type TrustBackedAssetsConvertedConcreteId = assets_common::TrustBackedAssetsConvertedConcreteId; -/// Asset converter for trust-backed assets. -/// Used to convert assets marked as `sufficient` into the asset needed for fee payment. -/// This type allows paying fees in `sufficient` trust backed-assets. -pub type TrustBackedSufficientAssetsConverter = SufficientAssetConverter< - Runtime, - TrustBackedAssetsConvertedConcreteId, - pallet_assets::BalanceToAssetBalance, - TrustBackedAssetsInstance, ->; - -/// Asset converter for foreign assets. -/// Used to convert assets marked as `sufficient` into the asset needed for fee payment. -/// This type allows paying fees in `sufficient` foreign assets. -pub type ForeignSufficientAssetsConverter = SufficientAssetConverter< - Runtime, - ForeignAssetsConvertedConcreteId, - pallet_assets::BalanceToAssetBalance, - ForeignAssetsInstance, ->; - /// 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. @@ -430,11 +410,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); - type AssetConverter = ( - PoolAssetsConverter, - TrustBackedSufficientAssetsConverter, - ForeignSufficientAssetsConverter, - ); + type AssetConverter = PoolAssetsConverter; type XcmRecorder = PolkadotXcm; } diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index f88392718b7a..4396565232d1 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -458,9 +458,6 @@ impl XcmExecutor { self.fees_mode, reason, ); - // TODO: Is it okay to just use the first asset? - // Is there a downstream team that has their own router and they use - // more than one asset for delivery fees. let asset_needed_for_fees = fees.get(0).ok_or(XcmError::AssetNotFound)?; // If `BuyExecution` was called, we know we can try to use that asset for fees. log::trace!(target: "xcm", "Asset for fees: {:?}", self.asset_for_fees); @@ -482,8 +479,7 @@ impl XcmExecutor { } else { asset_needed_for_fees.clone() }; - log::trace!(target: "xcm", "Actual asset for fees: {:?}", asset_to_pay_for_fees); - // TODO: Remove `jit_withdrawal`, it makes this much harder. + log::trace!(target: "xcm::fees", "Actual asset for fees: {:?}", asset_to_pay_for_fees); let paid = if self.fees_mode.jit_withdraw { let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; Config::AssetTransactor::withdraw_asset( @@ -491,6 +487,7 @@ impl XcmExecutor { origin, Some(&self.context), )?; + log::trace!(target: "xcm::fees", "Asset needed for fees: {:?}", asset_needed_for_fees); let swapped_asset = Config::AssetConverter::swap(&asset_to_pay_for_fees, asset_needed_for_fees)?; vec![swapped_asset].into() @@ -499,6 +496,7 @@ impl XcmExecutor { .holding .try_take(asset_to_pay_for_fees.clone().into()) .map_err(|_| XcmError::NotHoldingFees)?; + log::trace!(target: "xcm::fees", "Assets taken from holding to pay transport fee: {:?}", assets); let taken_asset = assets.into_assets_iter().next().ok_or(XcmError::AssetNotFound)?; let swapped_asset = Config::AssetConverter::swap(&taken_asset, asset_needed_for_fees)?; vec![swapped_asset].into() @@ -894,8 +892,6 @@ impl XcmExecutor { log::trace!(target: "xcm::DepositReserveAsset", "Asset wanted to pay for fees: {:?}", self.asset_for_fees); let asset_id = self.asset_for_fees.as_ref().unwrap_or(&asset_needed_for_fees.id); - // TODO: Deal with this case. - // Need to make a test specifically for this. let actual_asset_to_use_for_fees = match Config::AssetConverter::convert_asset( &asset_needed_for_fees, asset_id, @@ -909,9 +905,11 @@ impl XcmExecutor { // set aside fee to be charged by XcmSender let transport_fee = self.holding.saturating_take(actual_asset_to_use_for_fees.into()); + log::trace!(target: "xcm::DepositReserveAsset", "Transport fee: {:?}", transport_fee); // now take assets to deposit (excluding transport_fee) let deposited = self.holding.saturating_take(assets); + log::trace!(target: "xcm::DepositReserveAsset", "Assets except transport fee: {:?}", deposited); for asset in deposited.assets_iter() { Config::AssetTransactor::deposit_asset(&asset, &dest, Some(&self.context))?; } diff --git a/polkadot/xcm/xcm-executor/src/traits/fee_manager.rs b/polkadot/xcm/xcm-executor/src/traits/fee_manager.rs index 25fb68fcd8b5..134d7108b0ca 100644 --- a/polkadot/xcm/xcm-executor/src/traits/fee_manager.rs +++ b/polkadot/xcm/xcm-executor/src/traits/fee_manager.rs @@ -69,66 +69,17 @@ pub trait AssetConversion { /// If it can't be converted, an error is returned. fn convert_asset(asset: &Asset, asset_id: &AssetId) -> Result; /// Swaps `give` for `want`. - /// Returns the newly swapped `Asset` or an error. - /// An error might just mean this particular element of the tuple is not applicable. + /// Returns the newly swapped `Asset`, an error, or the same asset if `give == want`. /// The `Asset` returned should be the same as the `asset` passed in to `convert_asset`. fn swap(give: &Asset, want: &Asset) -> Result; } -#[impl_trait_for_tuples::impl_for_tuples(30)] -impl AssetConversion for Tuple { - fn convert_asset(asset: &Asset, asset_id: &AssetId) -> Result { - let mut last_error = None; - - for_tuples!( #( - match Tuple::convert_asset(asset, asset_id) { - Ok(new_asset) => { - log::trace!( - target: "xcm::AssetConversion::convert_asset", - "Found successful implementation in tuple.", - ); - return Ok(new_asset) - }, - Err(error) => { - // This is `trace` and not `error` since it's expected that some - // implementations in the tuple won't match. - log::trace!( - target: "xcm::AssetConversion::convert_asset", - "Implementation in tuple errored: {:?}, trying with next one, unless this was the last one.", - error, - ); - last_error = Some(error); - } - } - )* ); - - Err(last_error.unwrap_or(XcmError::TooExpensive)) +impl AssetConversion for () { + fn convert_asset(asset: &Asset, _: &AssetId) -> Result { + Ok(asset.clone()) } - fn swap(give: &Asset, want: &Asset) -> Result { - for_tuples!( #( - match Tuple::swap(give, want) { - Ok(asset) => { - log::trace!( - target: "xcm::AssetConversion::swap", - "Found successful implementation in tuple.", - ); - - return Ok(asset) - }, - Err(error) => { - // This is `trace` and not `error` since it's expected that some - // implementations in the tuple won't match. - log::trace!( - target: "xcm::AssetConversion::swap", - "Implementation in tuple errored: {:?}, trying with next one, unless this was the last one.", - error, - ); - }, - } - )* ); - - // If all tuple implementations fail, we want to return the fees. - Ok(want.clone()) + fn swap(give: &Asset, _: &Asset) -> Result { + Ok(give.clone()) } } From 05b620d33edd90ce3a4427b22fcac9d459ce4ce5 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Tue, 2 Jul 2024 22:45:48 +0200 Subject: [PATCH 18/43] feat(asset-hub-rococo): add a PoolAssetsConverter --- .../assets/asset-hub-rococo/src/xcm_config.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) 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 a1956404137c..ea642db7dc45 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 @@ -23,6 +23,7 @@ use super::{ use assets_common::{ matching::{FromNetwork, FromSiblingParachain, IsForeignConcreteAsset}, TrustBackedAssetsAsLocation, + SwapAssetConverter, }; use frame_support::{ parameter_types, @@ -329,6 +330,22 @@ 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 PoolAssetsConverter = SwapAssetConverter< + TokenLocationV3, + Runtime, + crate::NativeAndAssets, + ( + TrustBackedAssetsAsLocation, + ForeignAssetsConvertedConcreteId, + ), + crate::AssetConversion, + AccountId, +>; + pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; @@ -426,7 +443,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); type XcmRecorder = PolkadotXcm; - type AssetConverter = (); + type AssetConverter = PoolAssetsConverter; } /// Converts a local signed origin into an XCM location. From 522e7c761e422d8165d76e6029842ec8cf7cdde2 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Wed, 3 Jul 2024 11:17:46 +0200 Subject: [PATCH 19/43] feat(emulated-tests): add USDT to genesis and create WND<>USDT pool in penpal --- .../assets/asset-hub-westend/src/genesis.rs | 8 +- .../parachains/testing/penpal/src/genesis.rs | 5 +- .../parachains/testing/penpal/src/lib.rs | 1 + .../src/tests/fellowship_treasury.rs | 12 +- .../src/tests/reserve_transfer.rs | 116 ++++++++++++------ .../asset-hub-westend/src/tests/treasury.rs | 12 +- .../assets/asset-hub-rococo/src/xcm_config.rs | 3 +- .../runtimes/assets/common/src/lib.rs | 4 +- .../runtimes/testing/penpal/src/xcm_config.rs | 19 +-- 9 files changed, 103 insertions(+), 77 deletions(-) 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 6662069d05f2..27094e2ecc40 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 @@ -78,6 +78,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/tests/fellowship_treasury.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/fellowship_treasury.rs index 2d02e90f47fb..a8e2776d6ab4 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 = @@ -47,13 +47,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/reserve_transfer.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs index 9cd79ab1254e..262bc5171410 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,17 +1381,18 @@ fn reserve_transfer_pool_assets_from_para_to_para_through_asset_hub() { // Give USDT to sov account of sender. let asset_id = 1984; - AssetHubWestend::force_create_asset( - asset_id, - AssetHubWestendAssetOwner::get(), - true, // Not sufficient, using a pool. - 70_000, - vec![(sov_of_sender_on_asset_hub.clone(), asset_amount_to_send + fee_amount_to_send)], - ); + AssetHubWestend::execute_with(|| { + use frame_support::traits::tokens::fungibles::Mutate; + type Assets = ::Assets; + assert_ok!(>::mint_into( + asset_id.into(), + &sov_of_sender_on_asset_hub.clone().into(), + asset_amount_to_send + fee_amount_to_send, + )); + }); - // We create a pool between USDT and the Relay token in AssetHub. + // We create a pool between USDT and WND in AssetHub. let native_asset = RelayLocationV3::get(); - let asset_id = 1984u32; // USDT. let pool_asset = xcm::v3::Location::new( 0, [ @@ -1399,14 +1400,10 @@ fn reserve_transfer_pool_assets_from_para_to_para_through_asset_hub() { xcm::v3::Junction::GeneralIndex(asset_id.into()), ], ); - - // Setup the pool between `native_asset` and `pool_asset` on AssetHub. AssetHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; // set up pool with asset_id <> native pair - assert!(::Assets::asset_exists(asset_id)); - assert_ok!(::Assets::mint( ::RuntimeOrigin::signed(AssetHubWestendSender::get()), asset_id.into(), @@ -1446,24 +1443,70 @@ fn reserve_transfer_pool_assets_from_para_to_para_through_asset_hub() { ); }); - let system_para_foreign_asset_location = Location::new( - 1, - [Parachain(ASSET_HUB_ID), PalletInstance(ASSETS_PALLET_ID), GeneralIndex(asset_id.into())], - ); - PenpalA::force_create_foreign_asset( - system_para_foreign_asset_location.clone(), - PenpalAssetOwner::get(), - true, // Mark asset as sufficient. - 70_000, - vec![(sender.clone(), asset_amount_to_send + fee_amount_to_send)], - ); - PenpalB::force_create_foreign_asset( - system_para_foreign_asset_location.clone(), - PenpalAssetOwner::get(), - true, // Mark asset as sufficient. - 70_000, - vec![], - ); + // We also need a pool between WND and USDT on PenpalB. + PenpalB::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + let relay_asset = Location::parent(); + let foreign_asset_id = Location::new( + 1, + [ + Parachain(ASSET_HUB_ID), + PalletInstance(ASSETS_PALLET_ID), + GeneralIndex(asset_id.into()), + ], + ); + + assert_ok!(::ForeignAssets::mint( + ::RuntimeOrigin::signed(PenpalAssetOwner::get()), + foreign_asset_id.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(foreign_asset_id.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(foreign_asset_id), + 1_000_000_000_000, + 2_000_000_000_000, // `foreign_asset_id` is worth half of `relay_asset` + 0, + 0, + PenpalBReceiver::get().into() + )); + + assert_expected_events!( + PenpalB, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {lp_token_minted, .. }) => { lp_token_minted: *lp_token_minted == 1414213562273, }, + ] + ); + }); + + let usdt_from_asset_hub = penpal_runtime::xcm_config::UsdtFromAssetHub::get(); + + 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 = vec![( @@ -1517,14 +1560,11 @@ fn reserve_transfer_pool_assets_from_para_to_para_through_asset_hub() { // Query initial balances let sender_assets_before = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(system_para_foreign_asset_location.clone(), &sender) + >::balance(usdt_from_asset_hub.clone(), &sender) }); let receiver_assets_before = PenpalB::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance( - system_para_foreign_asset_location.clone(), - &receiver, - ) + >::balance(usdt_from_asset_hub.clone(), &receiver) }); test.set_dispatchable::( para_to_para_through_asset_hub_limited_reserve_transfer_assets, @@ -1534,11 +1574,11 @@ fn reserve_transfer_pool_assets_from_para_to_para_through_asset_hub() { // Query final balances let sender_assets_after = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(system_para_foreign_asset_location.clone(), &sender) + >::balance(usdt_from_asset_hub.clone(), &sender) }); let receiver_assets_after = PenpalB::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(system_para_foreign_asset_location, &receiver) + >::balance(usdt_from_asset_hub, &receiver) }); // Sender's balance is reduced by amount 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 6d8c0f5e5de6..687672694ba5 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)); @@ -46,13 +46,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 ea642db7dc45..15bc91436695 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 @@ -22,8 +22,7 @@ use super::{ }; use assets_common::{ matching::{FromNetwork, FromSiblingParachain, IsForeignConcreteAsset}, - TrustBackedAssetsAsLocation, - SwapAssetConverter, + SwapAssetConverter, TrustBackedAssetsAsLocation, }; use frame_support::{ parameter_types, diff --git a/cumulus/parachains/runtimes/assets/common/src/lib.rs b/cumulus/parachains/runtimes/assets/common/src/lib.rs index 9f71b50abe3f..8b7112320fed 100644 --- a/cumulus/parachains/runtimes/assets/common/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/common/src/lib.rs @@ -25,9 +25,7 @@ pub mod runtime_api; use crate::matching::{LocalLocationPattern, ParentLocation}; use core::marker::PhantomData; -use frame_support::traits::{ - fungibles, Equals, EverythingBut, Get, -}; +use frame_support::traits::{fungibles, Equals, EverythingBut, Get}; use pallet_asset_conversion::SwapCredit as SwapCreditT; use parachains_common::{AssetIdForTrustBackedAssets, CollectionId, ItemId}; use sp_runtime::traits::{TryConvertInto, Zero}; diff --git a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs index d53073ba225c..635efaaf2597 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs @@ -273,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)]); @@ -290,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 @@ -359,8 +365,6 @@ 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::SwapFirstAssetTrader< RelayLocation, crate::AssetConversion, @@ -377,17 +381,6 @@ impl xcm_executor::Config for XcmConfig { ResolveAssetTo, AccountId, >, - cumulus_primitives_utility::TakeFirstAssetTrader< - AccountId, - ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger, - ForeignAssetsConvertedConcreteId, - ForeignAssets, - cumulus_primitives_utility::XcmFeesTo32ByteAccount< - ForeignFungiblesTransactor, - AccountId, - XcmAssetFeesReceiver, - >, - >, ); type ResponseHandler = PolkadotXcm; type AssetTrap = PolkadotXcm; From 629dccec1b245622dc8cdf454e7571eb0d493a6b Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Wed, 3 Jul 2024 11:31:02 +0200 Subject: [PATCH 20/43] doc: prdoc --- prdoc/pr_4375.prdoc | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 prdoc/pr_4375.prdoc diff --git a/prdoc/pr_4375.prdoc b/prdoc/pr_4375.prdoc new file mode 100644 index 000000000000..ff7710cfebac --- /dev/null +++ b/prdoc/pr_4375.prdoc @@ -0,0 +1,39 @@ +# 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: Support different assets for delivery fees + +doc: + - audience: Runtime User + description: | + We now support different assets for delivery fees. + This means reserve asset transfers from one parachain to another going through AssetHub + no longer need DOT/KSM to pay for delivery fees on AssetHub. + They work as long as there is any asset which has a pool with DOT/KSM on AssetHub. + Right now paying delivery fees with a different asset locally is not supported, but it will + be in the future. + This PR supports different assets for remote delivery fees. + - audience: Runtime Dev + description: | + In order to support different assets for delivery fees, a new config item was added: AssetConverter. + This was needed to support this use case with the least amount of breaking changes. + It can be set to `()` if you don't want to support paying delivery fees with different assets. + Otherwise, it can be set to something like the `SwapAssetConverter` in this PR. + +crates: + - name: staging-xcm-executor + bump: major + - name: asset-hub-westend-runtime + bump: minor + - name: asset-hub-rococo-runtime + bump: minor + - name: bridge-hub-rococo-runtime + bump: minor + - name: people-rococo-runtime + bump: minor + - name: penpal-runtime + bump: minor + - name: cumulus-primitives-utility + bump: minor + - name: assets-common + bump: minor From b6f3b6e27a9dda97dc3fbaf1daffeedbf3435ec7 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Wed, 3 Jul 2024 11:31:40 +0200 Subject: [PATCH 21/43] doc(asset-hub-westend-integration-tests): change the docs for a test --- .../assets/asset-hub-westend/src/tests/reserve_transfer.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 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 262bc5171410..b170e3eb49e8 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 @@ -1354,11 +1354,12 @@ fn reserve_transfer_pool_assets_from_system_para_to_para() { } // ========================================================================== -// ===== Reserve Transfers - Pool Asset - Parachain->AssetHub->Parachain ==== +// == Reserve Transfers USDT - Parachain->AssetHub->Parachain - pay fees with USDT (asset conversion) == // ========================================================================== -// From Penpal A to Penpal B with AssetHub as the reserve. +// 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_pool_assets_from_para_to_para_through_asset_hub() { +fn reserve_transfer_usdt_from_para_to_para_through_asset_hub() { use penpal_runtime::xcm_config::ASSET_HUB_ID; let destination = PenpalA::sibling_location_of(PenpalB::para_id()); From 7ffd02a0439e6d4624ed3ca8d33af7495fce1b86 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Wed, 3 Jul 2024 11:31:57 +0200 Subject: [PATCH 22/43] chore(xcm-executor): change order of associated types in config --- polkadot/xcm/xcm-executor/src/config.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/polkadot/xcm/xcm-executor/src/config.rs b/polkadot/xcm/xcm-executor/src/config.rs index a6f76fb5d4fc..19a041d85080 100644 --- a/polkadot/xcm/xcm-executor/src/config.rs +++ b/polkadot/xcm/xcm-executor/src/config.rs @@ -93,6 +93,8 @@ pub trait Config { /// Configure the fees. type FeeManager: FeeManager; + /// Convert one asset to the one used to pay for fees. + type AssetConverter: AssetConversion; /// The method of exporting a message. type MessageExporter: ExportXcm; @@ -123,8 +125,6 @@ pub trait Config { /// Allows optional logic execution for the `HrmpChannelClosing` XCM notification. type HrmpChannelClosingHandler: HandleHrmpChannelClosing; - /// Allows converting a balance of one asset into another. - type AssetConverter: AssetConversion; /// Allows recording the last executed XCM (used by dry-run runtime APIs). type XcmRecorder: RecordXcm; } From 69c22cedf6e3e8c79d93779796d174a187c77dca Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Wed, 3 Jul 2024 11:32:33 +0200 Subject: [PATCH 23/43] fix: fmt --- .../assets/asset-hub-westend/src/tests/reserve_transfer.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 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 b170e3eb49e8..38dc528b28ee 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 @@ -1354,8 +1354,8 @@ fn reserve_transfer_pool_assets_from_system_para_to_para() { } // ========================================================================== -// == Reserve Transfers USDT - Parachain->AssetHub->Parachain - pay fees with USDT (asset conversion) == -// ========================================================================== +// == Reserve Transfers USDT - Parachain->AssetHub->Parachain - pay fees with USDT (asset +// conversion) == ========================================================================== // 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] From df196fe1468a5f137322f31274a7c64e7ae56de3 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Thu, 4 Jul 2024 16:36:59 +0200 Subject: [PATCH 24/43] feat: remove AssetConverter and use AssetExchanger instead --- Cargo.lock | 2 + .../src/tests/reserve_transfer.rs | 8 +- .../assets/asset-hub-rococo/src/xcm_config.rs | 35 +++-- .../asset-hub-westend/src/xcm_config.rs | 37 +++-- .../runtimes/assets/common/src/lib.rs | 106 +------------ .../bridge-hub-rococo/src/xcm_config.rs | 1 - .../bridge-hub-westend/src/xcm_config.rs | 1 - .../collectives-westend/src/xcm_config.rs | 1 - .../people/people-rococo/src/xcm_config.rs | 1 - .../people/people-westend/src/xcm_config.rs | 1 - .../runtimes/testing/penpal/src/xcm_config.rs | 15 +- polkadot/runtime/rococo/src/xcm_config.rs | 1 - polkadot/runtime/westend/src/xcm_config.rs | 1 - polkadot/xcm/xcm-builder/Cargo.toml | 4 + .../xcm/xcm-builder/src/asset_exchange.rs | 147 ++++++++++++++++++ polkadot/xcm/xcm-builder/src/lib.rs | 3 + polkadot/xcm/xcm-executor/src/config.rs | 6 +- polkadot/xcm/xcm-executor/src/lib.rs | 129 ++++++++++----- .../xcm-executor/src/traits/asset_exchange.rs | 9 ++ .../xcm-executor/src/traits/fee_manager.rs | 25 --- polkadot/xcm/xcm-executor/src/traits/mod.rs | 2 +- substrate/frame/asset-conversion/src/swap.rs | 47 ++++++ 22 files changed, 360 insertions(+), 222 deletions(-) create mode 100644 polkadot/xcm/xcm-builder/src/asset_exchange.rs diff --git a/Cargo.lock b/Cargo.lock index 9e934c1dc7d1..75704669753e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21086,10 +21086,12 @@ name = "staging-xcm-builder" version = "7.0.0" dependencies = [ "assert_matches", + "env_logger 0.11.3", "frame-support", "frame-system", "impl-trait-for-tuples", "log", + "pallet-asset-conversion", "pallet-assets", "pallet-balances", "pallet-salary", 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 38dc528b28ee..8a1de56848cd 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 @@ -1157,7 +1157,7 @@ fn reserve_transfer_native_asset_from_para_to_para_through_relay() { // =============================================================== #[test] fn reserve_transfer_pool_assets_from_system_para_to_para() { - let native_asset = RelayLocationV3::get(); + let native_asset = westend_system_emulated_network::asset_hub_westend_emulated_chain::asset_hub_westend_runtime::xcm_config::WestendLocationV3::get(); let asset_id = 9999u32; let pool_asset = xcm::v3::Location::new( 0, @@ -1360,7 +1360,7 @@ fn reserve_transfer_pool_assets_from_system_para_to_para() { // USDT by making use of existing USDT pools on AssetHub and destination. #[test] fn reserve_transfer_usdt_from_para_to_para_through_asset_hub() { - use penpal_runtime::xcm_config::ASSET_HUB_ID; + use westend_system_emulated_network::penpal_emulated_chain::penpal_runtime::xcm_config::ASSET_HUB_ID; let destination = PenpalA::sibling_location_of(PenpalB::para_id()); let sender = PenpalASender::get(); @@ -1393,7 +1393,7 @@ fn reserve_transfer_usdt_from_para_to_para_through_asset_hub() { }); // We create a pool between USDT and WND in AssetHub. - let native_asset = RelayLocationV3::get(); + let native_asset = westend_system_emulated_network::asset_hub_westend_emulated_chain::asset_hub_westend_runtime::xcm_config::WestendLocationV3::get(); let pool_asset = xcm::v3::Location::new( 0, [ @@ -1497,7 +1497,7 @@ fn reserve_transfer_usdt_from_para_to_para_through_asset_hub() { ); }); - let usdt_from_asset_hub = penpal_runtime::xcm_config::UsdtFromAssetHub::get(); + let usdt_from_asset_hub = westend_system_emulated_network::penpal_emulated_chain::penpal_runtime::xcm_config::UsdtFromAssetHub::get(); PenpalA::execute_with(|| { use frame_support::traits::tokens::fungibles::Mutate; 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 15bc91436695..8aaa2392a832 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,8 +21,8 @@ use super::{ XcmpQueue, }; use assets_common::{ - matching::{FromNetwork, FromSiblingParachain, IsForeignConcreteAsset}, - SwapAssetConverter, TrustBackedAssetsAsLocation, + matching::{FromNetwork, FromSiblingParachain, IsForeignConcreteAsset, ParentLocation}, + TrustBackedAssetsAsLocation, }; use frame_support::{ parameter_types, @@ -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, }; @@ -53,12 +53,13 @@ use xcm_builder::{ AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, DenyThenTry, DescribeAllTerminal, DescribeFamily, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, - GlobalConsensusParachainConvertsFor, HashedDescription, IsConcrete, LocalMint, - NetworkExportTableItem, NoChecking, NonFungiblesAdapter, ParentAsSuperuser, ParentIsPreset, - RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignPaidRemoteExporter, - SovereignSignedViaLocation, StartsWith, StartsWithExplicitGlobalConsensus, TakeWeightCredit, - TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + FungiblesPoolAdapter, GlobalConsensusParachainConvertsFor, HashedDescription, IsConcrete, + LocalMint, MatchedConvertedConcreteId, NetworkExportTableItem, NoChecking, NonFungiblesAdapter, + ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, + SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, + SovereignPaidRemoteExporter, SovereignSignedViaLocation, StartsWith, + StartsWithExplicitGlobalConsensus, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, + WeightInfoBounds, WithComputedOrigin, WithLatestLocationConverter, WithUniqueTopic, XcmFeeManagerFromComponents, XcmFeeToAccount, }; use xcm_executor::XcmExecutor; @@ -333,15 +334,20 @@ pub type TrustedTeleporters = ( /// 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 PoolAssetsConverter = SwapAssetConverter< - TokenLocationV3, - Runtime, +pub type PoolAssetsExchanger = FungiblesPoolAdapter< + crate::AssetConversion, crate::NativeAndAssets, ( TrustBackedAssetsAsLocation, ForeignAssetsConvertedConcreteId, + MatchedConvertedConcreteId< + xcm::v3::Location, + Balance, + Equals, + WithLatestLocationConverter, + TryConvertInto, + >, // Adding this back in to match on relay token. ), - crate::AssetConversion, AccountId, >; @@ -426,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, XcmFeeToAccount, @@ -442,7 +448,6 @@ impl xcm_executor::Config for XcmConfig { type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); type XcmRecorder = PolkadotXcm; - type AssetConverter = PoolAssetsConverter; } /// Converts a local signed origin into an XCM location. 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 71977afc86c9..c66a8d448c26 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,8 +21,8 @@ use super::{ XcmpQueue, }; use assets_common::{ - matching::{FromSiblingParachain, IsForeignConcreteAsset}, - SwapAssetConverter, TrustBackedAssetsAsLocation, + matching::{FromSiblingParachain, IsForeignConcreteAsset, ParentLocation}, + TrustBackedAssetsAsLocation, }; use frame_support::{ parameter_types, @@ -42,20 +42,20 @@ 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, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, DenyThenTry, DescribeFamily, DescribePalletTerminal, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, - GlobalConsensusParachainConvertsFor, HashedDescription, IsConcrete, LocalMint, - NetworkExportTableItem, NoChecking, NonFungiblesAdapter, ParentAsSuperuser, ParentIsPreset, - RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, StartsWith, - StartsWithExplicitGlobalConsensus, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, - WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, - XcmFeeToAccount, + FungiblesPoolAdapter, GlobalConsensusParachainConvertsFor, HashedDescription, IsConcrete, + LocalMint, MatchedConvertedConcreteId, NetworkExportTableItem, NoChecking, NonFungiblesAdapter, + ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, + SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, StartsWith, StartsWithExplicitGlobalConsensus, TakeWeightCredit, + TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, + WithLatestLocationConverter, WithUniqueTopic, XcmFeeManagerFromComponents, XcmFeeToAccount, }; use xcm_executor::XcmExecutor; @@ -353,15 +353,20 @@ pub type TrustedTeleporters = ( /// 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 PoolAssetsConverter = SwapAssetConverter< - WestendLocationV3, - Runtime, +pub type PoolAssetsExchanger = FungiblesPoolAdapter< + crate::AssetConversion, crate::NativeAndAssets, ( TrustBackedAssetsAsLocation, ForeignAssetsConvertedConcreteId, + MatchedConvertedConcreteId< + xcm::v3::Location, + Balance, + Equals, + WithLatestLocationConverter, + TryConvertInto, + >, // Adding this back in to match on relay token. ), - crate::AssetConversion, AccountId, >; @@ -435,7 +440,6 @@ impl xcm_executor::Config for XcmConfig { >, >, ); - type AssetConverter = PoolAssetsConverter; type ResponseHandler = PolkadotXcm; type AssetTrap = PolkadotXcm; type AssetClaims = PolkadotXcm; @@ -443,8 +447,7 @@ impl xcm_executor::Config for XcmConfig { type PalletInstancesInfo = AllPalletsWithSystem; type MaxAssetsIntoHolding = MaxAssetsIntoHolding; type AssetLocker = (); - // TODO: Might also use `AssetExchanger` instead of `AssetConverter` if API fits. - type AssetExchanger = (); + type AssetExchanger = PoolAssetsExchanger; type FeeManager = XcmFeeManagerFromComponents< WaivedLocations, XcmFeeToAccount, diff --git a/cumulus/parachains/runtimes/assets/common/src/lib.rs b/cumulus/parachains/runtimes/assets/common/src/lib.rs index 8b7112320fed..63ae84f05298 100644 --- a/cumulus/parachains/runtimes/assets/common/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/common/src/lib.rs @@ -24,17 +24,13 @@ pub mod matching; pub mod runtime_api; use crate::matching::{LocalLocationPattern, ParentLocation}; -use core::marker::PhantomData; -use frame_support::traits::{fungibles, Equals, EverythingBut, Get}; -use pallet_asset_conversion::SwapCredit as SwapCreditT; +use frame_support::traits::{Equals, EverythingBut}; use parachains_common::{AssetIdForTrustBackedAssets, CollectionId, ItemId}; -use sp_runtime::traits::{TryConvertInto, Zero}; -use sp_std::vec; +use sp_runtime::traits::TryConvertInto; use xcm::prelude::*; use xcm_builder::{ AsPrefixedGeneralIndex, MatchedConvertedConcreteId, StartsWith, WithLatestLocationConverter, }; -use xcm_executor::traits::{AssetConversion, MatchesFungibles}; /// `Location` vs `AssetIdForTrustBackedAssets` converter for `TrustBackedAssets` pub type AssetIdForTrustBackedAssetsConvert = @@ -136,104 +132,6 @@ pub type PoolAssetsConvertedConcreteId = TryConvertInto, >; -/// Implementation of `AssetConverter` that pays delivery fees by swapping the given asset to the -/// `Target` asset accepted for fees. -pub struct SwapAssetConverter( - PhantomData<(Target, Runtime, Fungibles, Matcher, SwapCredit, AccountId)>, -); -impl AssetConversion - for SwapAssetConverter -where - Target: Get, - Runtime: pallet_asset_conversion::Config, - Fungibles: fungibles::Balanced, - Matcher: MatchesFungibles, - SwapCredit: SwapCreditT< - AccountId, - Balance = u128, - AssetKind = Fungibles::AssetId, - Credit = fungibles::Credit, - >, -{ - fn convert_asset(asset: &Asset, asset_id: &AssetId) -> Result { - // We return `asset` immediately if that's the asset wanted to pay for fees. - if asset.id == *asset_id { - return Ok(asset.clone()); - } - let desired_asset: Asset = (asset_id.clone(), 1u128).into(); // To comply with the interface. - let (fungibles_asset, _) = Matcher::matches_fungibles(&desired_asset).map_err(|_| { - // Using `trace` instead of `error` since we expect this to happen - // when using multiple implementations in a tuple. - log::trace!( - target: "xcm::SwapAssetConverter::convert_asset", - "Could not map XCM asset {:?} to FRAME asset", - asset_id, - ); - XcmError::AssetNotFound - })?; - let Fungibility::Fungible(old_asset_amount) = asset.fun else { - log::error!( - target: "xcm::SwapAssetConverter::convert_asset", - "Fee asset {:?} is not fungible", - asset, - ); - return Err(XcmError::AssetNotFound); - }; - - let new_asset_amount = - pallet_asset_conversion::Pallet::::quote_price_tokens_for_exact_tokens( - fungibles_asset, - Target::get(), - old_asset_amount, - true, - ) - .ok_or(XcmError::FeesNotMet)?; - - Ok((asset_id.clone(), new_asset_amount).into()) - } - - fn swap(give: &Asset, want: &Asset) -> Result { - if *give == *want { - // Swap not needed. - return Ok(give.clone()); - } - let (fungibles_asset, balance) = Matcher::matches_fungibles(&give).map_err(|error| { - log::trace!( - target: "xcm::SwapAssetConverter::swap", - "Could not map XCM asset {:?} to FRAME asset. Error: {:?}", - give, - error, - ); - XcmError::AssetNotFound - })?; - let Fungibility::Fungible(fee_amount) = want.fun else { - log::error!( - target: "xcm::SwapAssetConverter::swap", - "Fee asset is not fungible", - ); - return Err(XcmError::AssetNotFound); - }; - - let swap_asset = fungibles_asset.clone().into(); - let credit_in = Fungibles::issue(fungibles_asset, balance); - - // Swap the user's asset for `asset`. - let (credit_out, credit_change) = SwapCredit::swap_tokens_for_exact_tokens( - vec![swap_asset, Target::get()], - credit_in, - fee_amount, - ) - .map_err(|(credit_in, _)| { - drop(credit_in); - XcmError::FeesNotMet - })?; - - assert!(credit_change.peek() == Zero::zero()); - - Ok((want.id.clone(), credit_out.peek()).into()) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs index 7e5328df861b..a0d2e91dffd2 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs @@ -240,7 +240,6 @@ impl xcm_executor::Config for XcmConfig { type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); type XcmRecorder = PolkadotXcm; - type AssetConverter = (); } pub type PriceForParentDelivery = diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs index f65f58436b04..c2ca8e47f2a6 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs @@ -206,7 +206,6 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); - type AssetConverter = (); type XcmRecorder = PolkadotXcm; } diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs index 5f3a7e65f1d0..c68f230a16dc 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs @@ -220,7 +220,6 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); - type AssetConverter = (); type XcmRecorder = PolkadotXcm; } diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/people/people-rococo/src/xcm_config.rs index c08bc9af4ccf..cca964fb2441 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/xcm_config.rs @@ -231,7 +231,6 @@ impl xcm_executor::Config for XcmConfig { type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); type XcmRecorder = PolkadotXcm; - type AssetConverter = (); } /// Converts a local signed origin into an XCM location. Forms the basis for local origins diff --git a/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs index 635eba1e49fc..3926ddcf21ef 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs @@ -238,7 +238,6 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); - type AssetConverter = (); type XcmRecorder = PolkadotXcm; } diff --git a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs index 635efaaf2597..8f9a95767bad 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs @@ -29,7 +29,7 @@ use super::{ WeightToFee, XcmpQueue, }; use crate::{BaseDeliveryFee, FeeAssetId, TransactionByteFee}; -use assets_common::{SwapAssetConverter, TrustBackedAssetsAsLocation}; +use assets_common::TrustBackedAssetsAsLocation; use core::marker::PhantomData; use frame_support::{ parameter_types, @@ -50,8 +50,8 @@ use xcm_builder::{ AccountId32Aliases, AllowHrmpNotificationsFromRelayChain, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AsPrefixedGeneralIndex, ConvertedConcreteId, EnsureXcmOrigin, FixedWeightBounds, FrameTransactionalProcessor, - FungibleAdapter, FungiblesAdapter, IsConcrete, LocalMint, NativeAsset, NoChecking, - ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, + FungibleAdapter, FungiblesAdapter, FungiblesPoolAdapter, IsConcrete, LocalMint, NativeAsset, + NoChecking, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, StartsWith, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, @@ -334,9 +334,8 @@ pub type TrustBackedAssetsConvertedConcreteId = /// 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 PoolAssetsConverter = SwapAssetConverter< - RelayLocation, - Runtime, +pub type PoolAssetsExchanger = FungiblesPoolAdapter< + crate::AssetConversion, crate::NativeAndAssets, ( TrustBackedAssetsAsLocation< @@ -346,7 +345,6 @@ pub type PoolAssetsConverter = SwapAssetConverter< >, ForeignAssetsConvertedConcreteId, ), - crate::AssetConversion, AccountId, >; @@ -389,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< (), XcmFeeToAccount, @@ -403,7 +401,6 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); - type AssetConverter = PoolAssetsConverter; type XcmRecorder = PolkadotXcm; } diff --git a/polkadot/runtime/rococo/src/xcm_config.rs b/polkadot/runtime/rococo/src/xcm_config.rs index 2865f3204836..96416821e4c8 100644 --- a/polkadot/runtime/rococo/src/xcm_config.rs +++ b/polkadot/runtime/rococo/src/xcm_config.rs @@ -224,7 +224,6 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); - type AssetConverter = (); type XcmRecorder = XcmPallet; } diff --git a/polkadot/runtime/westend/src/xcm_config.rs b/polkadot/runtime/westend/src/xcm_config.rs index a87f87cec4a0..9d7143c96bb5 100644 --- a/polkadot/runtime/westend/src/xcm_config.rs +++ b/polkadot/runtime/westend/src/xcm_config.rs @@ -222,7 +222,6 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); - type AssetConverter = (); type XcmRecorder = XcmPallet; } diff --git a/polkadot/xcm/xcm-builder/Cargo.toml b/polkadot/xcm/xcm-builder/Cargo.toml index d43506aa651b..5ab02359ba92 100644 --- a/polkadot/xcm/xcm-builder/Cargo.toml +++ b/polkadot/xcm/xcm-builder/Cargo.toml @@ -23,12 +23,14 @@ 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] +env_logger = { workspace = true } primitive-types = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } pallet-xcm = { workspace = true, default-features = true } @@ -54,6 +56,7 @@ runtime-benchmarks = [ "polkadot-test-runtime/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "pallet-asset-conversion/runtime-benchmarks", ] std = [ "codec/std", @@ -70,4 +73,5 @@ std = [ "sp-weights/std", "xcm-executor/std", "xcm/std", + "pallet-asset-conversion/std", ] diff --git a/polkadot/xcm/xcm-builder/src/asset_exchange.rs b/polkadot/xcm/xcm-builder/src/asset_exchange.rs new file mode 100644 index 000000000000..28ae3c073759 --- /dev/null +++ b/polkadot/xcm/xcm-builder/src/asset_exchange.rs @@ -0,0 +1,147 @@ +// 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 that implement the [`xcm_executor::traits::AssetExchange`] trait. + +use core::marker::PhantomData; +use frame_support::traits::tokens::fungibles; +use pallet_asset_conversion::{QuoteExchangePrice, SwapCredit}; +use sp_runtime::traits::Zero; +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::QuoteExchangePrice`] to [`xcm_executor::traits::AssetExchange`]. +/// +/// Takes just one fungible asset in `give` and allows only one fungible asset in `want`. +pub struct FungiblesPoolAdapter( + PhantomData<(AssetConversion, Fungibles, Matcher, AccountId)>, +); +impl AssetExchange + for FungiblesPoolAdapter +where + AssetConversion: SwapCredit< + AccountId, + Balance = u128, + AssetKind = Fungibles::AssetId, + Credit = fungibles::Credit, + > + QuoteExchangePrice, + Fungibles: fungibles::Balanced, + Matcher: MatchesFungibles, +{ + fn exchange_asset( + _: Option<&Location>, + give: AssetsInHolding, + want: &Assets, + _: bool, + ) -> Result { + let give_asset = give.fungible_assets_iter().next().ok_or_else(|| { + log::trace!( + target: "xcm::FungiblesPoolAdapter::exchange_asset", + "No fungible asset was in `give`.", + ); + give.clone() + })?; + let want_asset = want.get(0).ok_or_else(|| { + log::trace!( + target: "xcm::FungiblesPoolAdapter::exchange_asset", + "No asset was in `want`.", + ); + give.clone() + })?; + let (give_asset_id, balance) = + Matcher::matches_fungibles(&give_asset).map_err(|error| { + log::trace!( + target: "xcm::FungiblesPoolAdapter::exchange_asset", + "Could not map XCM asset {:?} to FRAME asset. Error: {:?}", + give, + error, + ); + give.clone() + })?; + let (want_asset_id, want_amount) = + Matcher::matches_fungibles(&want_asset).map_err(|error| { + log::trace!( + target: "xcm::FungiblesPoolAdapter", + "Could not map XCM asset {:?} to FRAME asset. Error: {:?}", + want, + 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, balance); + log::trace!(target: "xcm", "Credit in: {:?}", credit_in.peek()); + + // Do the swap. + let (credit_out, credit_change) = + >::swap_tokens_for_exact_tokens( + vec![swap_asset, want_asset_id], + credit_in, + want_amount, + ) + .map_err(|(credit_in, _)| { + drop(credit_in); + give.clone() + })?; + + log::trace!(target: "xcm", "Credit out: {:?}", credit_out.peek()); + log::trace!(target: "xcm", "Credit change: {:?}", credit_change.peek()); + assert!(credit_change.peek() == Zero::zero()); + + let resulting_asset: Asset = (want_asset.id.clone(), credit_out.peek()).into(); + Ok(resulting_asset.into()) + } + + fn quote_exchange_price(asset1: &Asset, asset2: &Asset, _: bool) -> Option { + let (asset1_id, _) = Matcher::matches_fungibles(asset1) + .map_err(|error| { + log::trace!( + target: "xcm::FungiblesPoolAdapter::quote_exchange_price", + "Could not map XCM asset {:?} to FRAME asset. Error: {:?}.", + asset1, + error, + ); + () + }) + .ok()?; + let (asset2_id, desired_asset2_amount) = Matcher::matches_fungibles(asset2) + .map_err(|error| { + log::trace!( + target: "xcm::FungiblesPoolAdapter::quote_exchange_price", + "Could not map XCM asset {:?} to FRAME asset. Error: {:?}.", + asset2, + error, + ); + () + }) + .ok()?; + let necessary_asset1_amount = + ::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/lib.rs b/polkadot/xcm/xcm-builder/src/lib.rs index cc06c298a418..a33c20068eff 100644 --- a/polkadot/xcm/xcm-builder/src/lib.rs +++ b/polkadot/xcm/xcm-builder/src/lib.rs @@ -33,6 +33,9 @@ pub use asset_conversion::{ AsPrefixedGeneralIndex, ConvertedConcreteId, MatchedConvertedConcreteId, }; +mod asset_exchange; +pub use asset_exchange::FungiblesPoolAdapter; + mod barriers; pub use barriers::{ AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, diff --git a/polkadot/xcm/xcm-executor/src/config.rs b/polkadot/xcm/xcm-executor/src/config.rs index 19a041d85080..e74474011b5e 100644 --- a/polkadot/xcm/xcm-executor/src/config.rs +++ b/polkadot/xcm/xcm-executor/src/config.rs @@ -15,8 +15,8 @@ // along with Polkadot. If not, see . use crate::traits::{ - AssetConversion, AssetExchange, AssetLock, CallDispatcher, ClaimAssets, ConvertOrigin, - DropAssets, ExportXcm, FeeManager, HandleHrmpChannelAccepted, HandleHrmpChannelClosing, + AssetExchange, AssetLock, CallDispatcher, ClaimAssets, ConvertOrigin, DropAssets, ExportXcm, + FeeManager, HandleHrmpChannelAccepted, HandleHrmpChannelClosing, HandleHrmpNewChannelOpenRequest, OnResponse, ProcessTransaction, RecordXcm, ShouldExecute, TransactAsset, VersionChangeNotifier, WeightBounds, WeightTrader, }; @@ -93,8 +93,6 @@ pub trait Config { /// Configure the fees. type FeeManager: FeeManager; - /// Convert one asset to the one used to pay for fees. - type AssetConverter: AssetConversion; /// The method of exporting a message. type MessageExporter: ExportXcm; diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index 4396565232d1..15c471e214b0 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -30,8 +30,8 @@ use xcm::latest::prelude::*; pub mod traits; use traits::{ - validate_export, AssetConversion, AssetExchange, AssetLock, CallDispatcher, ClaimAssets, - ConvertOrigin, DropAssets, Enact, ExportXcm, FeeManager, FeeReason, HandleHrmpChannelAccepted, + validate_export, AssetExchange, AssetLock, CallDispatcher, ClaimAssets, ConvertOrigin, + DropAssets, Enact, ExportXcm, FeeManager, FeeReason, HandleHrmpChannelAccepted, HandleHrmpChannelClosing, HandleHrmpNewChannelOpenRequest, OnResponse, ProcessTransaction, Properties, ShouldExecute, TransactAsset, VersionChangeNotifier, WeightBounds, WeightTrader, XcmAssetTransfers, @@ -80,7 +80,7 @@ pub struct XcmExecutor { appendix_weight: Weight, transact_status: MaybeErrorCode, fees_mode: FeesMode, - asset_for_fees: Option, + asset_for_fees: Option, _config: PhantomData, } @@ -460,21 +460,27 @@ impl XcmExecutor { ); let asset_needed_for_fees = fees.get(0).ok_or(XcmError::AssetNotFound)?; // If `BuyExecution` was called, we know we can try to use that asset for fees. - log::trace!(target: "xcm", "Asset for fees: {:?}", self.asset_for_fees); let asset_to_pay_for_fees = if let Some(asset_for_fees) = &self.asset_for_fees { - match Config::AssetConverter::convert_asset(&asset_needed_for_fees, &asset_for_fees) { - Ok(new_asset) => new_asset, - // If we can't convert, then we return the original asset. - // It will error later in any case. - Err(error) => { - log::trace!( - target: "xcm::take_fee", - "Could not convert fees to {:?}. Error: {:?}", - asset_for_fees, - error, - ); - asset_needed_for_fees.clone() - }, + if asset_for_fees.id != asset_needed_for_fees.id { + match Config::AssetExchanger::quote_exchange_price( + &asset_for_fees, + &asset_needed_for_fees, + false, + ) { + Some(necessary_amount) => (asset_for_fees.id.clone(), necessary_amount).into(), + // If we can't convert, then we return the original asset. + // It will error later in any case. + None => { + log::trace!( + target: "xcm::take_fee", + "Could not convert fees to {:?}", + asset_for_fees, + ); + asset_needed_for_fees.clone() + }, + } + } else { + asset_needed_for_fees.clone() } } else { asset_needed_for_fees.clone() @@ -488,19 +494,56 @@ impl XcmExecutor { Some(&self.context), )?; log::trace!(target: "xcm::fees", "Asset needed for fees: {:?}", asset_needed_for_fees); - let swapped_asset = - Config::AssetConverter::swap(&asset_to_pay_for_fees, asset_needed_for_fees)?; - vec![swapped_asset].into() + if asset_to_pay_for_fees.id != asset_needed_for_fees.id { + // TODO: This should be only one asset. + let swapped_asset: Assets = Config::AssetExchanger::exchange_asset( + self.origin_ref(), + asset_to_pay_for_fees.into(), + &asset_needed_for_fees.clone().into(), + false, + ) + .map_err(|given_assets| { + log::error!( + target: "xcm::fees", + "Swap was deemed necessary but couldn't be done. Given assets: {:?}.", + given_assets, + ); + XcmError::FeesNotMet + })? + .into(); + swapped_asset + } else { + vec![asset_to_pay_for_fees].into() + } } else { let assets = self .holding .try_take(asset_to_pay_for_fees.clone().into()) .map_err(|_| XcmError::NotHoldingFees)?; log::trace!(target: "xcm::fees", "Assets taken from holding to pay transport fee: {:?}", assets); - let taken_asset = assets.into_assets_iter().next().ok_or(XcmError::AssetNotFound)?; - let swapped_asset = Config::AssetConverter::swap(&taken_asset, asset_needed_for_fees)?; - vec![swapped_asset].into() + // TODO: This should be only one asset. + if asset_to_pay_for_fees.id != asset_needed_for_fees.id { + let swapped_asset: Assets = Config::AssetExchanger::exchange_asset( + self.origin_ref(), + assets, + &asset_needed_for_fees.clone().into(), + false, + ) + .map_err(|given_assets| { + log::error!( + target: "xcm::fees", + "Swap was deemed necessary but couldn't be done. Given assets: {:?}.", + given_assets, + ); + XcmError::FeesNotMet + })? + .into(); + swapped_asset + } else { + vec![asset_to_pay_for_fees].into() + } }; + // TODO: There should be a way to remove the swap duplication. Config::FeeManager::handle_fee(paid, Some(&self.context), reason); Ok(()) } @@ -889,19 +932,32 @@ impl XcmExecutor { let (_, fee) = validate_send::(dest.clone(), Xcm(message_to_weigh))?; let asset_needed_for_fees = fee.get(0).ok_or(XcmError::AssetNotFound)?; + log::trace!(target: "xcm::DepositReserveAsset", "Asset needed to pay for fees: {:?}", asset_needed_for_fees); log::trace!(target: "xcm::DepositReserveAsset", "Asset wanted to pay for fees: {:?}", self.asset_for_fees); - let asset_id = - self.asset_for_fees.as_ref().unwrap_or(&asset_needed_for_fees.id); - let actual_asset_to_use_for_fees = match Config::AssetConverter::convert_asset( - &asset_needed_for_fees, - asset_id, - ) { - Ok(new_asset) => new_asset, - Err(error) => { - log::warn!(target: "xcm::DepositReserveAsset", "Couldn't convert the fee asset {:?} to the needed {:?}. Error: {:?}", asset_id, asset_needed_for_fees, error); + let asset_to_pay_for_fees = + self.asset_for_fees.as_ref().unwrap_or(&asset_needed_for_fees); + let actual_asset_to_use_for_fees = + if asset_to_pay_for_fees.id != 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, + &asset_needed_for_fees, + false, + ) { + Some(necessary_amount) => + (asset_to_pay_for_fees.id.clone(), necessary_amount).into(), + None => { + log::trace!( + target: "xcm::take_fee", + "Could not convert fees to {:?}", + asset_to_pay_for_fees, + ); + asset_needed_for_fees.clone() + }, + } + } else { asset_needed_for_fees.clone() - }, - }; + }; // set aside fee to be charged by XcmSender let transport_fee = self.holding.saturating_take(actual_asset_to_use_for_fees.into()); @@ -998,8 +1054,9 @@ impl XcmExecutor { // should be executed. let Some(weight) = Option::::from(weight_limit) else { return Ok(()) }; let old_holding = self.holding.clone(); - // Store the asset being used for fees, so delivery fees can access it. - self.asset_for_fees = Some(fees.id.clone()); + // Save the asset being used for execution fees, so we later know what should be + // used for delivery fees. + self.asset_for_fees = Some(fees.clone()); log::trace!(target: "xcm::executor::BuyExecution", "Asset for fees: {:?}", self.asset_for_fees); // pay for `weight` using up to `fees` of the holding register. let max_fee = diff --git a/polkadot/xcm/xcm-executor/src/traits/asset_exchange.rs b/polkadot/xcm/xcm-executor/src/traits/asset_exchange.rs index 432a7498ed4c..c52d98101991 100644 --- a/polkadot/xcm/xcm-executor/src/traits/asset_exchange.rs +++ b/polkadot/xcm/xcm-executor/src/traits/asset_exchange.rs @@ -37,6 +37,15 @@ 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 { + None + } } #[impl_trait_for_tuples::impl_for_tuples(30)] diff --git a/polkadot/xcm/xcm-executor/src/traits/fee_manager.rs b/polkadot/xcm/xcm-executor/src/traits/fee_manager.rs index 134d7108b0ca..b6e303daaad8 100644 --- a/polkadot/xcm/xcm-executor/src/traits/fee_manager.rs +++ b/polkadot/xcm/xcm-executor/src/traits/fee_manager.rs @@ -58,28 +58,3 @@ impl FeeManager for () { fn handle_fee(_: Assets, _: Option<&XcmContext>, _: FeeReason) {} } - -/// Not about exchanging assets, just converting an amount of one -/// into one of another. -/// Used for paying fees in different assets. -pub trait AssetConversion { - /// Convert `asset` to the specified `asset_id`. - /// If the conversion can be done, the returned Asset - /// has the specified `asset_id` and a new balance. - /// If it can't be converted, an error is returned. - fn convert_asset(asset: &Asset, asset_id: &AssetId) -> Result; - /// Swaps `give` for `want`. - /// Returns the newly swapped `Asset`, an error, or the same asset if `give == want`. - /// The `Asset` returned should be the same as the `asset` passed in to `convert_asset`. - fn swap(give: &Asset, want: &Asset) -> Result; -} - -impl AssetConversion for () { - fn convert_asset(asset: &Asset, _: &AssetId) -> Result { - Ok(asset.clone()) - } - - fn swap(give: &Asset, _: &Asset) -> Result { - Ok(give.clone()) - } -} diff --git a/polkadot/xcm/xcm-executor/src/traits/mod.rs b/polkadot/xcm/xcm-executor/src/traits/mod.rs index 3057f454bdf9..feb2922bcdff 100644 --- a/polkadot/xcm/xcm-executor/src/traits/mod.rs +++ b/polkadot/xcm/xcm-executor/src/traits/mod.rs @@ -29,7 +29,7 @@ pub use asset_transfer::{Error as AssetTransferError, TransferType, XcmAssetTran mod export; pub use export::{export_xcm, validate_export, ExportXcm}; mod fee_manager; -pub use fee_manager::{AssetConversion, FeeManager, FeeReason}; +pub use fee_manager::{FeeManager, FeeReason}; mod filter_asset_location; #[allow(deprecated)] pub use filter_asset_location::FilterAssetLocation; diff --git a/substrate/frame/asset-conversion/src/swap.rs b/substrate/frame/asset-conversion/src/swap.rs index a6154e294147..de4b2b8e9676 100644 --- a/substrate/frame/asset-conversion/src/swap.rs +++ b/substrate/frame/asset-conversion/src/swap.rs @@ -112,6 +112,30 @@ pub trait SwapCredit { ) -> Result<(Self::Credit, Self::Credit), (Self::Credit, DispatchError)>; } +/// Trait providing methods for quoting the exchange price between different asset classes. +pub trait QuoteExchangePrice { + /// Measure units of the asset classes for quoting. + type Balance: Balance; + /// Kind of assets that are going to be quoted. + type AssetKind; + + /// Provides the amount of `asset2` exchangeable for an exact amount of `asset1`. + fn quote_price_exact_tokens_for_tokens( + asset1: Self::AssetKind, + asset2: Self::AssetKind, + amount: Self::Balance, + include_fee: bool, + ) -> Option; + + /// Provides the amount of `asset1` exchangeable for an exact amount of `asset2`. + fn quote_price_tokens_for_exact_tokens( + asset1: Self::AssetKind, + asset2: Self::AssetKind, + amount: Self::Balance, + include_fee: bool, + ) -> Option; +} + impl Swap for Pallet { type Balance = T::Balance; type AssetKind = T::AssetKind; @@ -210,3 +234,26 @@ impl SwapCredit for Pallet { .map_err(|_| (Self::Credit::zero(credit_asset), DispatchError::Corruption))? } } + +impl QuoteExchangePrice for Pallet { + type Balance = T::Balance; + type AssetKind = T::AssetKind; + + fn quote_price_exact_tokens_for_tokens( + asset1: T::AssetKind, + asset2: T::AssetKind, + amount: T::Balance, + include_fee: bool, + ) -> Option { + Self::quote_price_exact_tokens_for_tokens(asset1, asset2, amount, include_fee) + } + + fn quote_price_tokens_for_exact_tokens( + asset1: T::AssetKind, + asset2: T::AssetKind, + amount: T::Balance, + include_fee: bool, + ) -> Option { + Self::quote_price_tokens_for_exact_tokens(asset1, asset2, amount, include_fee) + } +} From 1639489e9cb3817546e0a155e813ecc4bd020a4e Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Thu, 4 Jul 2024 16:39:22 +0200 Subject: [PATCH 25/43] doc: prdoc --- prdoc/pr_4375.prdoc | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/prdoc/pr_4375.prdoc b/prdoc/pr_4375.prdoc index ff7710cfebac..ed81a082c86f 100644 --- a/prdoc/pr_4375.prdoc +++ b/prdoc/pr_4375.prdoc @@ -15,10 +15,11 @@ doc: This PR supports different assets for remote delivery fees. - audience: Runtime Dev description: | - In order to support different assets for delivery fees, a new config item was added: AssetConverter. - This was needed to support this use case with the least amount of breaking changes. - It can be set to `()` if you don't want to support paying delivery fees with different assets. - Otherwise, it can be set to something like the `SwapAssetConverter` in this PR. + In order to support different assets for delivery fees, the existing AssetExchanger config item + was leveraged. + This config item allows exchanging assets and quoting exchange prices. + An adapter was added to `xcm-builder` to make it compatible with `pallet-asset-conversion`: + `FungiblesPoolAdapter`. crates: - name: staging-xcm-executor From 9875ee28b5f40d573d772cbdfe1369c01e8a29d7 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Thu, 4 Jul 2024 16:41:47 +0200 Subject: [PATCH 26/43] doc: prdoc --- prdoc/pr_4375.prdoc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/prdoc/pr_4375.prdoc b/prdoc/pr_4375.prdoc index ed81a082c86f..9ae1c4d11299 100644 --- a/prdoc/pr_4375.prdoc +++ b/prdoc/pr_4375.prdoc @@ -20,6 +20,11 @@ doc: This config item allows exchanging assets and quoting exchange prices. An adapter was added to `xcm-builder` to make it compatible with `pallet-asset-conversion`: `FungiblesPoolAdapter`. + The new functionality in the executor pays delivery fees using only one asset, which might be + swapped to the correct one. + If you have a custom XcmRouter that returns more than one asset needed for delivery fees, then know + that only the first one will be used. + No XcmRouter provided by the SDK returns more than one asset. crates: - name: staging-xcm-executor From c3d15547fe5f9a9a85da0fc5b1c39d92b0f52f19 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Thu, 4 Jul 2024 17:04:02 +0200 Subject: [PATCH 27/43] fix: clippy and feedback --- .../runtimes/assets/common/src/lib.rs | 1 - polkadot/xcm/xcm-executor/src/lib.rs | 25 +++++++++++-------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/cumulus/parachains/runtimes/assets/common/src/lib.rs b/cumulus/parachains/runtimes/assets/common/src/lib.rs index 63ae84f05298..f1387741c885 100644 --- a/cumulus/parachains/runtimes/assets/common/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/common/src/lib.rs @@ -136,7 +136,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/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index 15c471e214b0..fe347d3964f7 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -80,7 +80,9 @@ pub struct XcmExecutor { appendix_weight: Weight, transact_status: MaybeErrorCode, fees_mode: FeesMode, - asset_for_fees: Option, + /// Asset provided in `BuyExecution` instruction (if any) in current XCM program. Same asset + /// will be used for paying any potential delivery fees incurred by current XCM program. + asset_used_for_fees: Option, _config: PhantomData, } @@ -311,7 +313,7 @@ impl XcmExecutor { appendix_weight: Weight::zero(), transact_status: Default::default(), fees_mode: FeesMode { jit_withdraw: false }, - asset_for_fees: None, + asset_used_for_fees: None, _config: PhantomData, } } @@ -460,21 +462,22 @@ impl XcmExecutor { ); let asset_needed_for_fees = fees.get(0).ok_or(XcmError::AssetNotFound)?; // If `BuyExecution` was called, we know we can try to use that asset for fees. - let asset_to_pay_for_fees = if let Some(asset_for_fees) = &self.asset_for_fees { - if asset_for_fees.id != asset_needed_for_fees.id { + let asset_to_pay_for_fees = if let Some(asset_used_for_fees) = &self.asset_used_for_fees { + if asset_used_for_fees.id != asset_needed_for_fees.id { match Config::AssetExchanger::quote_exchange_price( - &asset_for_fees, + &asset_used_for_fees, &asset_needed_for_fees, false, ) { - Some(necessary_amount) => (asset_for_fees.id.clone(), necessary_amount).into(), + Some(necessary_amount) => + (asset_used_for_fees.id.clone(), necessary_amount).into(), // If we can't convert, then we return the original asset. // It will error later in any case. None => { log::trace!( target: "xcm::take_fee", "Could not convert fees to {:?}", - asset_for_fees, + asset_used_for_fees, ); asset_needed_for_fees.clone() }, @@ -933,9 +936,9 @@ impl XcmExecutor { validate_send::(dest.clone(), Xcm(message_to_weigh))?; let asset_needed_for_fees = fee.get(0).ok_or(XcmError::AssetNotFound)?; log::trace!(target: "xcm::DepositReserveAsset", "Asset needed to pay for fees: {:?}", asset_needed_for_fees); - log::trace!(target: "xcm::DepositReserveAsset", "Asset wanted to pay for fees: {:?}", self.asset_for_fees); + log::trace!(target: "xcm::DepositReserveAsset", "Asset wanted to pay for fees: {:?}", self.asset_used_for_fees); let asset_to_pay_for_fees = - self.asset_for_fees.as_ref().unwrap_or(&asset_needed_for_fees); + self.asset_used_for_fees.as_ref().unwrap_or(&asset_needed_for_fees); let actual_asset_to_use_for_fees = if asset_to_pay_for_fees.id != asset_needed_for_fees.id { // Get the correct amount of asset_to_pay_for_fees. @@ -1056,8 +1059,8 @@ impl XcmExecutor { 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_for_fees = Some(fees.clone()); - log::trace!(target: "xcm::executor::BuyExecution", "Asset for fees: {:?}", self.asset_for_fees); + self.asset_used_for_fees = Some(fees.clone()); + log::trace!(target: "xcm::executor::BuyExecution", "Asset 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 e2107f893bc1ce8d00e9abd5e5b354fa06b42cde Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Thu, 4 Jul 2024 17:28:02 +0200 Subject: [PATCH 28/43] fix: zepter and taplo --- cumulus/parachains/runtimes/assets/common/Cargo.toml | 8 ++++---- cumulus/parachains/runtimes/testing/penpal/Cargo.toml | 7 ++++--- polkadot/xcm/xcm-builder/Cargo.toml | 4 ++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/cumulus/parachains/runtimes/assets/common/Cargo.toml b/cumulus/parachains/runtimes/assets/common/Cargo.toml index 56e3fa95968a..ff6fecc289d9 100644 --- a/cumulus/parachains/runtimes/assets/common/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/common/Cargo.toml @@ -43,8 +43,9 @@ std = [ "cumulus-primitives-core/std", "frame-support/std", "log/std", - "pallet-assets/std", "pallet-asset-conversion/std", + "pallet-assets/std", + "pallet-assets/std", "pallet-xcm/std", "parachains-common/std", "scale-info/std", @@ -54,18 +55,17 @@ std = [ "xcm-builder/std", "xcm-executor/std", "xcm/std", - "pallet-assets/std", ] runtime-benchmarks = [ "cumulus-primitives-core/runtime-benchmarks", "frame-support/runtime-benchmarks", - "pallet-assets/runtime-benchmarks", "pallet-asset-conversion/runtime-benchmarks", + "pallet-assets/runtime-benchmarks", + "pallet-assets/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", "parachains-common/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", - "pallet-assets/runtime-benchmarks", ] diff --git a/cumulus/parachains/runtimes/testing/penpal/Cargo.toml b/cumulus/parachains/runtimes/testing/penpal/Cargo.toml index edcb350503d0..898a1df2eebd 100644 --- a/cumulus/parachains/runtimes/testing/penpal/Cargo.toml +++ b/cumulus/parachains/runtimes/testing/penpal/Cargo.toml @@ -103,9 +103,9 @@ std = [ "frame-system/std", "frame-try-runtime?/std", "log/std", + "pallet-asset-conversion/std", "pallet-asset-tx-payment/std", "pallet-assets/std", - "pallet-asset-conversion/std", "pallet-aura/std", "pallet-authorship/std", "pallet-balances/std", @@ -122,6 +122,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", @@ -155,9 +156,9 @@ 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-asset-conversion/runtime-benchmarks", "pallet-balances/runtime-benchmarks", "pallet-collator-selection/runtime-benchmarks", "pallet-message-queue/runtime-benchmarks", @@ -183,9 +184,9 @@ 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-asset-conversion/try-runtime", "pallet-aura/try-runtime", "pallet-authorship/try-runtime", "pallet-balances/try-runtime", diff --git a/polkadot/xcm/xcm-builder/Cargo.toml b/polkadot/xcm/xcm-builder/Cargo.toml index 5ab02359ba92..24cd3fa848f0 100644 --- a/polkadot/xcm/xcm-builder/Cargo.toml +++ b/polkadot/xcm/xcm-builder/Cargo.toml @@ -46,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", @@ -56,13 +57,13 @@ runtime-benchmarks = [ "polkadot-test-runtime/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-executor/runtime-benchmarks", - "pallet-asset-conversion/runtime-benchmarks", ] std = [ "codec/std", "frame-support/std", "frame-system/std", "log/std", + "pallet-asset-conversion/std", "pallet-transaction-payment/std", "polkadot-parachain-primitives/std", "scale-info/std", @@ -73,5 +74,4 @@ std = [ "sp-weights/std", "xcm-executor/std", "xcm/std", - "pallet-asset-conversion/std", ] From 77b0cc860bc5abb4e72b8ba9f91cd16ad0aa6790 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Fri, 5 Jul 2024 11:59:36 +0200 Subject: [PATCH 29/43] feat(xcm-executor): not error in case there are no delivery fees --- polkadot/xcm/xcm-executor/src/lib.rs | 79 ++++++++++++++++------------ 1 file changed, 44 insertions(+), 35 deletions(-) diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index fe347d3964f7..2f2ae7527934 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -460,7 +460,10 @@ impl XcmExecutor { self.fees_mode, reason, ); - let asset_needed_for_fees = fees.get(0).ok_or(XcmError::AssetNotFound)?; + 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. let asset_to_pay_for_fees = if let Some(asset_used_for_fees) = &self.asset_used_for_fees { if asset_used_for_fees.id != asset_needed_for_fees.id { @@ -934,39 +937,43 @@ impl XcmExecutor { message_to_weigh.extend(xcm.0.clone().into_iter()); let (_, fee) = validate_send::(dest.clone(), Xcm(message_to_weigh))?; - let asset_needed_for_fees = fee.get(0).ok_or(XcmError::AssetNotFound)?; - log::trace!(target: "xcm::DepositReserveAsset", "Asset needed to pay for fees: {:?}", asset_needed_for_fees); - log::trace!(target: "xcm::DepositReserveAsset", "Asset wanted to pay for fees: {:?}", self.asset_used_for_fees); - let asset_to_pay_for_fees = - self.asset_used_for_fees.as_ref().unwrap_or(&asset_needed_for_fees); - let actual_asset_to_use_for_fees = - if asset_to_pay_for_fees.id != 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, - &asset_needed_for_fees, - false, - ) { - Some(necessary_amount) => - (asset_to_pay_for_fees.id.clone(), necessary_amount).into(), - None => { - log::trace!( - target: "xcm::take_fee", - "Could not convert fees to {:?}", - asset_to_pay_for_fees, - ); - asset_needed_for_fees.clone() - }, - } - } else { - asset_needed_for_fees.clone() - }; - // set aside fee to be charged by XcmSender - let transport_fee = - self.holding.saturating_take(actual_asset_to_use_for_fees.into()); - log::trace!(target: "xcm::DepositReserveAsset", "Transport fee: {:?}", transport_fee); - + let maybe_delivery_fee = if let Some(asset_needed_for_fees) = fee.get(0) { + log::trace!(target: "xcm::DepositReserveAsset", "Asset needed to pay for fees: {:?}", asset_needed_for_fees); + log::trace!(target: "xcm::DepositReserveAsset", "Asset wanted to pay for fees: {:?}", self.asset_used_for_fees); + let asset_to_pay_for_fees = + self.asset_used_for_fees.as_ref().unwrap_or(&asset_needed_for_fees); + let actual_asset_to_use_for_fees = + if asset_to_pay_for_fees.id != 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, + &asset_needed_for_fees, + false, + ) { + Some(necessary_amount) => + (asset_to_pay_for_fees.id.clone(), necessary_amount).into(), + None => { + log::trace!( + target: "xcm::take_fee", + "Could not convert fees to {:?}", + asset_to_pay_for_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()); + log::trace!(target: "xcm::DepositReserveAsset", "Transport fee: {:?}", 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); log::trace!(target: "xcm::DepositReserveAsset", "Assets except transport fee: {:?}", deposited); for asset in deposited.assets_iter() { @@ -978,8 +985,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(()) }); From 288882632222fe92ad367d58aeb7f97fac683ced Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Fri, 5 Jul 2024 12:27:52 +0200 Subject: [PATCH 30/43] chore(xcm-executor): deduplicate swapping logic in take_fees --- polkadot/xcm/xcm-executor/src/lib.rs | 70 +++++++++++----------------- 1 file changed, 26 insertions(+), 44 deletions(-) diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index 2f2ae7527934..bc6fe8373a3e 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -460,6 +460,7 @@ impl XcmExecutor { self.fees_mode, reason, ); + // 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. @@ -492,7 +493,7 @@ impl XcmExecutor { asset_needed_for_fees.clone() }; log::trace!(target: "xcm::fees", "Actual asset for fees: {:?}", asset_to_pay_for_fees); - let paid = if self.fees_mode.jit_withdraw { + let assets_to_swap = if self.fees_mode.jit_withdraw { let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; Config::AssetTransactor::withdraw_asset( &asset_to_pay_for_fees, @@ -500,56 +501,37 @@ impl XcmExecutor { Some(&self.context), )?; log::trace!(target: "xcm::fees", "Asset needed for fees: {:?}", asset_needed_for_fees); - if asset_to_pay_for_fees.id != asset_needed_for_fees.id { - // TODO: This should be only one asset. - let swapped_asset: Assets = Config::AssetExchanger::exchange_asset( - self.origin_ref(), - asset_to_pay_for_fees.into(), - &asset_needed_for_fees.clone().into(), - false, - ) - .map_err(|given_assets| { - log::error!( - target: "xcm::fees", - "Swap was deemed necessary but couldn't be done. Given assets: {:?}.", - given_assets, - ); - XcmError::FeesNotMet - })? - .into(); - swapped_asset - } else { - vec![asset_to_pay_for_fees].into() - } + asset_to_pay_for_fees.clone().into() } else { let assets = self .holding .try_take(asset_to_pay_for_fees.clone().into()) .map_err(|_| XcmError::NotHoldingFees)?; log::trace!(target: "xcm::fees", "Assets taken from holding to pay transport fee: {:?}", assets); - // TODO: This should be only one asset. - if asset_to_pay_for_fees.id != asset_needed_for_fees.id { - let swapped_asset: Assets = Config::AssetExchanger::exchange_asset( - self.origin_ref(), - assets, - &asset_needed_for_fees.clone().into(), - false, - ) - .map_err(|given_assets| { - log::error!( - target: "xcm::fees", - "Swap was deemed necessary but couldn't be done. Given assets: {:?}.", - given_assets, - ); - XcmError::FeesNotMet - })? - .into(); - swapped_asset - } else { - vec![asset_to_pay_for_fees].into() - } + let mut iter = assets.fungible_assets_iter(); + let asset = iter.next().ok_or(XcmError::NotHoldingFees)?; + asset.into() + }; + 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(), + assets_to_swap, + &asset_needed_for_fees.clone().into(), + false, + ) + .map_err(|given_assets| { + log::error!( + target: "xcm::fees", + "Swap was deemed necessary but couldn't be done. Given assets: {:?}.", + given_assets, + ); + XcmError::FeesNotMet + })? + .into(); + swapped_asset + } else { + vec![asset_to_pay_for_fees].into() }; - // TODO: There should be a way to remove the swap duplication. Config::FeeManager::handle_fee(paid, Some(&self.context), reason); Ok(()) } From 90807aab9ada50139ea17eae6783b9965e4ba93e Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Fri, 5 Jul 2024 22:42:45 +0200 Subject: [PATCH 31/43] fix(assets-common): remove duplicated feature flags --- cumulus/parachains/runtimes/assets/common/Cargo.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/cumulus/parachains/runtimes/assets/common/Cargo.toml b/cumulus/parachains/runtimes/assets/common/Cargo.toml index ff6fecc289d9..51157e884936 100644 --- a/cumulus/parachains/runtimes/assets/common/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/common/Cargo.toml @@ -45,7 +45,6 @@ std = [ "log/std", "pallet-asset-conversion/std", "pallet-assets/std", - "pallet-assets/std", "pallet-xcm/std", "parachains-common/std", "scale-info/std", @@ -62,7 +61,6 @@ runtime-benchmarks = [ "frame-support/runtime-benchmarks", "pallet-asset-conversion/runtime-benchmarks", "pallet-assets/runtime-benchmarks", - "pallet-assets/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", "parachains-common/runtime-benchmarks", "sp-runtime/runtime-benchmarks", From 10dd60fc0c6b0f675cac903a84ecbc3e67a56398 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Fri, 5 Jul 2024 22:43:17 +0200 Subject: [PATCH 32/43] chore(xcm-executor): remove default implementation for AssetExchange --- polkadot/xcm/xcm-builder/src/test_utils.rs | 4 ++++ polkadot/xcm/xcm-builder/src/tests/mock.rs | 4 ++++ .../xcm/xcm-executor/src/traits/asset_exchange.rs | 14 +++++++++++--- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/polkadot/xcm/xcm-builder/src/test_utils.rs b/polkadot/xcm/xcm-builder/src/test_utils.rs index 3131dece3757..aae1ed8aff6c 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 f35c73bdb685..304a6ed5b242 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 c52d98101991..49bac348bed4 100644 --- a/polkadot/xcm/xcm-executor/src/traits/asset_exchange.rs +++ b/polkadot/xcm/xcm-executor/src/traits/asset_exchange.rs @@ -43,9 +43,7 @@ pub trait AssetExchange { /// - `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 { - None - } + fn quote_exchange_price(asset1: &Asset, asset2: &Asset, maximal: bool) -> Option; } #[impl_trait_for_tuples::impl_for_tuples(30)] @@ -64,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 + } } From 49d76b9a61962ff4e14b8accd4fa3a1fbed4fdc0 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Fri, 5 Jul 2024 22:43:35 +0200 Subject: [PATCH 33/43] doc: update prdoc crate bumps --- prdoc/pr_4375.prdoc | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/prdoc/pr_4375.prdoc b/prdoc/pr_4375.prdoc index 9ae1c4d11299..a88bd932a054 100644 --- a/prdoc/pr_4375.prdoc +++ b/prdoc/pr_4375.prdoc @@ -28,18 +28,18 @@ doc: crates: - name: staging-xcm-executor - bump: major + bump: minor - name: asset-hub-westend-runtime bump: minor - name: asset-hub-rococo-runtime bump: minor - - name: bridge-hub-rococo-runtime - bump: minor - - name: people-rococo-runtime - bump: minor - name: penpal-runtime - bump: minor + bump: major - name: cumulus-primitives-utility - bump: minor + bump: patch - name: assets-common + bump: patch + - name: staging-xcm-builder + bump: minor + - name: pallet-asset-conversion bump: minor From a2d3b6806fac38b8ce8ffcb3b641cc41d88c8358 Mon Sep 17 00:00:00 2001 From: Adrian Catangiu Date: Tue, 9 Jul 2024 18:49:26 +0300 Subject: [PATCH 34/43] Apply suggestions from code review --- polkadot/xcm/xcm-builder/src/asset_exchange.rs | 5 ++--- polkadot/xcm/xcm-executor/src/lib.rs | 9 ++++----- substrate/frame/asset-conversion/src/swap.rs | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/polkadot/xcm/xcm-builder/src/asset_exchange.rs b/polkadot/xcm/xcm-builder/src/asset_exchange.rs index 28ae3c073759..6576d4dc4b0f 100644 --- a/polkadot/xcm/xcm-builder/src/asset_exchange.rs +++ b/polkadot/xcm/xcm-builder/src/asset_exchange.rs @@ -104,9 +104,8 @@ where give.clone() })?; - log::trace!(target: "xcm", "Credit out: {:?}", credit_out.peek()); - log::trace!(target: "xcm", "Credit change: {:?}", credit_change.peek()); - assert!(credit_change.peek() == Zero::zero()); + log::trace!(target: "xcm", "Credit out: {:?}, Credit change: {:?}", credit_out.peek(), credit_change.peek()); + debug_assert!(credit_change.peek() == Zero::zero()); let resulting_asset: Asset = (want_asset.id.clone(), credit_out.peek()).into(); Ok(resulting_asset.into()) diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index bc6fe8373a3e..d016be2f59a2 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -80,9 +80,9 @@ pub struct XcmExecutor { appendix_weight: Weight, transact_status: MaybeErrorCode, fees_mode: FeesMode, - /// Asset provided in `BuyExecution` instruction (if any) in current XCM program. Same asset - /// will be used for paying any potential delivery fees incurred by current XCM program. - asset_used_for_fees: Option, + /// Id of asset provided in `BuyExecution` instruction (if any) in current XCM program. Same asset + /// type will be used for paying any potential delivery fees incurred by current XCM program. + asset_used_for_fees: Option, _config: PhantomData, } @@ -920,8 +920,7 @@ impl XcmExecutor { let (_, fee) = validate_send::(dest.clone(), Xcm(message_to_weigh))?; let maybe_delivery_fee = if let Some(asset_needed_for_fees) = fee.get(0) { - log::trace!(target: "xcm::DepositReserveAsset", "Asset needed to pay for fees: {:?}", asset_needed_for_fees); - log::trace!(target: "xcm::DepositReserveAsset", "Asset wanted to pay for fees: {:?}", self.asset_used_for_fees); + log::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); let actual_asset_to_use_for_fees = diff --git a/substrate/frame/asset-conversion/src/swap.rs b/substrate/frame/asset-conversion/src/swap.rs index de4b2b8e9676..95e28835eb39 100644 --- a/substrate/frame/asset-conversion/src/swap.rs +++ b/substrate/frame/asset-conversion/src/swap.rs @@ -119,7 +119,7 @@ pub trait QuoteExchangePrice { /// Kind of assets that are going to be quoted. type AssetKind; - /// Provides the amount of `asset2` exchangeable for an exact amount of `asset1`. + /// Provides the amount of `asset2` exchangeable for an exact `amount` of `asset1`. fn quote_price_exact_tokens_for_tokens( asset1: Self::AssetKind, asset2: Self::AssetKind, From d4d6055b1fa30557afa2dfa7d0e748de072fe2ee Mon Sep 17 00:00:00 2001 From: Adrian Catangiu Date: Tue, 9 Jul 2024 18:59:48 +0300 Subject: [PATCH 35/43] fix executor --- polkadot/xcm/xcm-executor/src/lib.rs | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index d016be2f59a2..b5db56511936 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -80,8 +80,9 @@ pub struct XcmExecutor { appendix_weight: Weight, transact_status: MaybeErrorCode, fees_mode: FeesMode, - /// Id of asset provided in `BuyExecution` instruction (if any) in current XCM program. Same asset - /// type will be used for paying any potential delivery fees incurred by current XCM program. + /// Id of asset provided in `BuyExecution` instruction (if any) in current XCM program. Same + /// asset type will be used for paying any potential delivery fees incurred by current XCM + /// program. asset_used_for_fees: Option, _config: PhantomData, } @@ -467,14 +468,14 @@ impl XcmExecutor { }; // If `BuyExecution` was called, we know we can try to use that asset 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.id != asset_needed_for_fees.id { + if asset_used_for_fees != &asset_needed_for_fees.id { match Config::AssetExchanger::quote_exchange_price( - &asset_used_for_fees, + &(asset_used_for_fees.clone(), 1u128).into(), &asset_needed_for_fees, false, ) { Some(necessary_amount) => - (asset_used_for_fees.id.clone(), necessary_amount).into(), + (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 => { @@ -920,19 +921,23 @@ impl XcmExecutor { let (_, fee) = validate_send::(dest.clone(), Xcm(message_to_weigh))?; let maybe_delivery_fee = if let Some(asset_needed_for_fees) = fee.get(0) { - log::trace!(target: "xcm::DepositReserveAsset", "Asset provided to pay for fees {:?}, asset required for transport fees: {:?}", self.asset_used_for_fees, asset_needed_for_fees); + log::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); + 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.id != asset_needed_for_fees.id { + 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, + &(asset_to_pay_for_fees.clone(), 1u128).into(), &asset_needed_for_fees, false, ) { Some(necessary_amount) => - (asset_to_pay_for_fees.id.clone(), necessary_amount).into(), + (asset_to_pay_for_fees.clone(), necessary_amount).into(), None => { log::trace!( target: "xcm::take_fee", @@ -1049,7 +1054,7 @@ impl XcmExecutor { 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.clone()); + self.asset_used_for_fees = Some(fees.id.clone()); log::trace!(target: "xcm::executor::BuyExecution", "Asset for fees: {:?}", self.asset_used_for_fees); // pay for `weight` using up to `fees` of the holding register. let max_fee = From 8f266c437e0ad8d049657815813c9cf281e4b654 Mon Sep 17 00:00:00 2001 From: Adrian Catangiu Date: Tue, 9 Jul 2024 19:34:55 +0300 Subject: [PATCH 36/43] clean up tests --- .../tests/assets/asset-hub-westend/src/lib.rs | 1 + .../src/tests/reserve_transfer.rs | 189 +++++------------- 2 files changed, 52 insertions(+), 138 deletions(-) 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 db8ada3f4ea2..ce5cce9dc098 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 @@ -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, 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 8a1de56848cd..f5537f044e7e 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,80 +1152,28 @@ fn reserve_transfer_native_asset_from_para_to_para_through_relay() { assert!(receiver_assets_after < receiver_assets_before + amount_to_send); } -// =============================================================== -// ===== Reserve Transfers - Pool Asset - AssetHub->Parachain ==== -// =============================================================== +// ========================================================================================= +// ==== Reserve Transfers - TrustBacked Asset pay fees using pool - AssetHub->Parachain ==== +// ========================================================================================= #[test] fn reserve_transfer_pool_assets_from_system_para_to_para() { - let native_asset = westend_system_emulated_network::asset_hub_westend_emulated_chain::asset_hub_westend_runtime::xcm_config::WestendLocationV3::get(); let asset_id = 9999u32; - let pool_asset = xcm::v3::Location::new( - 0, - [ - xcm::v3::Junction::PalletInstance(ASSETS_PALLET_ID), - xcm::v3::Junction::GeneralIndex(asset_id.into()), - ], - ); 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-AHR with ED. + // 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)]); - // Setup the pool between `native_asset` and `pool_asset` on AssetHub. - AssetHubWestend::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - - // set up pool with asset_id <> native pair - assert_ok!(::Assets::create( - ::RuntimeOrigin::signed(AssetHubWestendSender::get()), - asset_id.into(), - AssetHubWestendSender::get().into(), - ASSET_MIN_BALANCE, - )); - assert!(::Assets::asset_exists(asset_id)); - - assert_ok!(::Assets::mint( - ::RuntimeOrigin::signed(AssetHubWestendSender::get()), - asset_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(pool_asset), - )); - - 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(pool_asset), - 1_000_000_000_000, - 2_000_000_000_000, // `pool_asset` is worth half of `native_asset` - 0, - 0, - AssetHubWestendSender::get().into() - )); - - assert_expected_events!( - AssetHubWestend, - vec![ - RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {lp_token_minted, .. }) => { lp_token_minted: *lp_token_minted == 1414213562273, }, - ] - ); - }); + 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 = Location::parent(); + 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())], @@ -1233,15 +1181,15 @@ fn reserve_transfer_pool_assets_from_system_para_to_para() { PenpalA::force_create_foreign_asset( custom_asset_penpal_pov.clone(), PenpalAssetOwner::get(), - false, // Asset not sufficient, has a pool to pay for fees. + false, 1_000_000, - vec![(PenpalASender::get(), 10_000_000_000_000)], /* We give it some funds to be able to - * add liquidity later. */ + // We give it some funds to be able to add liquidity later. + vec![(PenpalASender::get(), 10_000_000_000_000)], ); - // Setup the pool between `native_penpal_asset` and `pool_asset` on PenpalA. - // So we can swap the custom asset that comes from AssetHubWestend for native - // penpal asset to pay for fees. + // 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; @@ -1262,9 +1210,9 @@ fn reserve_transfer_pool_assets_from_system_para_to_para() { ::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, /* `custom_asset_penpal_pov` is worth a third of - * `relay_asset_penpal_pov` */ + 3_000_000_000_000, 0, 0, PenpalASender::get().into() @@ -1273,7 +1221,7 @@ fn reserve_transfer_pool_assets_from_system_para_to_para() { assert_expected_events!( PenpalA, vec![ - RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {lp_token_minted, .. }) => { lp_token_minted: *lp_token_minted == 1732050807468, }, + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded { .. }) => {}, ] ); }); @@ -1302,16 +1250,6 @@ fn reserve_transfer_pool_assets_from_system_para_to_para() { }; let mut test = SystemParaToParaTest::new(test_args); - // The sender needs some Relay tokens to pay for delivery fees locally. - // TODO: Also allow payment with different assets locally, right now only works remotely. - // ::execute_with(|| { - // assert_ok!(::Balances::force_set_balance( - // ::RuntimeOrigin::root(), - // sender.clone().into(), - // ASSET_HUB_WESTEND_ED, - // )); - // }); - let sender_initial_balance = AssetHubWestend::execute_with(|| { type Assets = ::Assets; >::balance(asset_id, &sender) @@ -1325,8 +1263,7 @@ fn reserve_transfer_pool_assets_from_system_para_to_para() { >::balance(custom_asset_penpal_pov.clone(), &receiver) }); - // test.set_assertion::(system_para_to_para_sender_assertions); - // test.set_assertion::(system_para_to_para_receiver_assertions); + test.set_assertion::(system_para_to_para_receiver_assertions); test.set_dispatchable::(system_para_to_para_reserve_transfer_assets); test.assert(); @@ -1353,15 +1290,14 @@ 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 (asset -// conversion) == ========================================================================== +// =============================================================================================== +// == 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() { - use westend_system_emulated_network::penpal_emulated_chain::penpal_runtime::xcm_config::ASSET_HUB_ID; - let destination = PenpalA::sibling_location_of(PenpalB::para_id()); let sender = PenpalASender::get(); let asset_amount_to_send: Balance = WESTEND_ED * 10000; @@ -1381,33 +1317,34 @@ fn reserve_transfer_usdt_from_para_to_para_through_asset_hub() { ]); // Give USDT to sov account of sender. - let asset_id = 1984; + let usdt_id = 1984; AssetHubWestend::execute_with(|| { use frame_support::traits::tokens::fungibles::Mutate; type Assets = ::Assets; assert_ok!(>::mint_into( - asset_id.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 USDT and WND in AssetHub. - let native_asset = westend_system_emulated_network::asset_hub_westend_emulated_chain::asset_hub_westend_runtime::xcm_config::WestendLocationV3::get(); - let pool_asset = xcm::v3::Location::new( + // We create a pool between WND and USDT in AssetHub. + let native_asset = v3::Parent.into(); + let usdt = v3::Location::new( 0, [ - xcm::v3::Junction::PalletInstance(ASSETS_PALLET_ID), - xcm::v3::Junction::GeneralIndex(asset_id.into()), + 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; - // set up pool with asset_id <> native pair assert_ok!(::Assets::mint( ::RuntimeOrigin::signed(AssetHubWestendSender::get()), - asset_id.into(), + usdt_id.into(), AssetHubWestendSender::get().into(), 10_000_000_000_000, // For it to have more than enough. )); @@ -1415,7 +1352,7 @@ 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(pool_asset), + Box::new(usdt), )); assert_expected_events!( @@ -1428,9 +1365,9 @@ fn reserve_transfer_usdt_from_para_to_para_through_asset_hub() { assert_ok!(::AssetConversion::add_liquidity( ::RuntimeOrigin::signed(AssetHubWestendSender::get()), Box::new(native_asset), - Box::new(pool_asset), + Box::new(usdt), 1_000_000_000_000, - 2_000_000_000_000, // `pool_asset` is worth half of `native_asset` + 2_000_000_000_000, // usdt is worth half of `native_asset` 0, 0, AssetHubWestendSender::get().into() @@ -1439,28 +1376,21 @@ fn reserve_transfer_usdt_from_para_to_para_through_asset_hub() { assert_expected_events!( AssetHubWestend, vec![ - RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {lp_token_minted, .. }) => { lp_token_minted: *lp_token_minted == 1414213562273, }, + 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 = Location::parent(); - let foreign_asset_id = Location::new( - 1, - [ - Parachain(ASSET_HUB_ID), - PalletInstance(ASSETS_PALLET_ID), - GeneralIndex(asset_id.into()), - ], - ); + let relay_asset = RelayLocation::get(); assert_ok!(::ForeignAssets::mint( ::RuntimeOrigin::signed(PenpalAssetOwner::get()), - foreign_asset_id.clone().into(), + usdt_from_asset_hub.clone().into(), PenpalBReceiver::get().into(), 10_000_000_000_000, // For it to have more than enough. )); @@ -1468,7 +1398,7 @@ fn reserve_transfer_usdt_from_para_to_para_through_asset_hub() { assert_ok!(::AssetConversion::create_pool( ::RuntimeOrigin::signed(PenpalBReceiver::get()), Box::new(relay_asset.clone()), - Box::new(foreign_asset_id.clone()), + Box::new(usdt_from_asset_hub.clone()), )); assert_expected_events!( @@ -1481,9 +1411,9 @@ fn reserve_transfer_usdt_from_para_to_para_through_asset_hub() { assert_ok!(::AssetConversion::add_liquidity( ::RuntimeOrigin::signed(PenpalBReceiver::get()), Box::new(relay_asset), - Box::new(foreign_asset_id), + Box::new(usdt_from_asset_hub.clone()), 1_000_000_000_000, - 2_000_000_000_000, // `foreign_asset_id` is worth half of `relay_asset` + 2_000_000_000_000, // `usdt_from_asset_hub` is worth half of `relay_asset` 0, 0, PenpalBReceiver::get().into() @@ -1492,13 +1422,11 @@ fn reserve_transfer_usdt_from_para_to_para_through_asset_hub() { assert_expected_events!( PenpalB, vec![ - RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {lp_token_minted, .. }) => { lp_token_minted: *lp_token_minted == 1414213562273, }, + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded { .. }) => {}, ] ); }); - let usdt_from_asset_hub = westend_system_emulated_network::penpal_emulated_chain::penpal_runtime::xcm_config::UsdtFromAssetHub::get(); - PenpalA::execute_with(|| { use frame_support::traits::tokens::fungibles::Mutate; type ForeignAssets = ::ForeignAssets; @@ -1510,26 +1438,11 @@ fn reserve_transfer_usdt_from_para_to_para_through_asset_hub() { }); // Prepare assets to transfer. - let assets: Assets = vec![( - ( - Parent, - Parachain(ASSET_HUB_ID), - PalletInstance(ASSETS_PALLET_ID), - GeneralIndex(asset_id.into()), - ), - asset_amount_to_send + fee_amount_to_send, - ) - .into()] - .into(); - // Just to be very specific you don't need any native asset. + 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); - // fund the Parachain Origin's SA on AssetHub with the native tokens held in reserve - AssetHubWestend::fund_accounts(vec![( - sov_of_sender_on_asset_hub.into(), - asset_amount_to_send * 2, - )]); - // 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( From b0e1c60f5fa944a548bb7e26567ad2b5906aa5d9 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Wed, 17 Jul 2024 18:06:29 +0200 Subject: [PATCH 37/43] chore: address some docs and naming feedback --- .../assets/asset-hub-westend/src/tests/reserve_transfer.rs | 2 +- polkadot/xcm/xcm-builder/src/asset_exchange.rs | 4 +--- polkadot/xcm/xcm-executor/src/lib.rs | 5 ++--- 3 files changed, 4 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 f5537f044e7e..74d47b392bb0 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 @@ -1181,7 +1181,7 @@ fn reserve_transfer_pool_assets_from_system_para_to_para() { PenpalA::force_create_foreign_asset( custom_asset_penpal_pov.clone(), PenpalAssetOwner::get(), - false, + true, 1_000_000, // We give it some funds to be able to add liquidity later. vec![(PenpalASender::get(), 10_000_000_000_000)], diff --git a/polkadot/xcm/xcm-builder/src/asset_exchange.rs b/polkadot/xcm/xcm-builder/src/asset_exchange.rs index 6576d4dc4b0f..d17cfdade70b 100644 --- a/polkadot/xcm/xcm-builder/src/asset_exchange.rs +++ b/polkadot/xcm/xcm-builder/src/asset_exchange.rs @@ -79,7 +79,7 @@ where let (want_asset_id, want_amount) = Matcher::matches_fungibles(&want_asset).map_err(|error| { log::trace!( - target: "xcm::FungiblesPoolAdapter", + target: "xcm::FungiblesPoolAdapter::exchange_asset", "Could not map XCM asset {:?} to FRAME asset. Error: {:?}", want, error, @@ -90,7 +90,6 @@ where // 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, balance); - log::trace!(target: "xcm", "Credit in: {:?}", credit_in.peek()); // Do the swap. let (credit_out, credit_change) = @@ -104,7 +103,6 @@ where give.clone() })?; - log::trace!(target: "xcm", "Credit out: {:?}, Credit change: {:?}", credit_out.peek(), credit_change.peek()); debug_assert!(credit_change.peek() == Zero::zero()); let resulting_asset: Asset = (want_asset.id.clone(), credit_out.peek()).into(); diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index 714d744e40ec..8602e7b51f08 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -83,9 +83,8 @@ pub struct XcmExecutor { appendix_weight: Weight, transact_status: MaybeErrorCode, fees_mode: FeesMode, - /// Id of asset provided in `BuyExecution` instruction (if any) in current XCM program. Same - /// asset type will be used for paying any potential delivery fees incurred by current XCM - /// program. + /// 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, } From 18e8b7c26c885a9f115e1ecdccbb94a9b22317cf Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Thu, 18 Jul 2024 13:18:22 +0200 Subject: [PATCH 38/43] fix(staging-xcm-executor): move log to tracing --- .../src/tests/reserve_transfer.rs | 6 ++-- polkadot/xcm/xcm-executor/src/config.rs | 1 - polkadot/xcm/xcm-executor/src/lib.rs | 36 +++++++++---------- prdoc/pr_4375.prdoc | 2 +- 4 files changed, 22 insertions(+), 23 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 74d47b392bb0..2727a143f4e1 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 @@ -1302,14 +1302,14 @@ fn reserve_transfer_usdt_from_para_to_para_through_asset_hub() { let sender = PenpalASender::get(); let asset_amount_to_send: Balance = WESTEND_ED * 10000; let fee_amount_to_send: Balance = WESTEND_ED * 10000; - let sender_as_seen_by_asset_hub = AssetHubWestend::sibling_location_of(PenpalA::para_id()); + 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_as_seen_by_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-AHR with ED. + // 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), diff --git a/polkadot/xcm/xcm-executor/src/config.rs b/polkadot/xcm/xcm-executor/src/config.rs index e74474011b5e..63b113bc250f 100644 --- a/polkadot/xcm/xcm-executor/src/config.rs +++ b/polkadot/xcm/xcm-executor/src/config.rs @@ -122,7 +122,6 @@ pub trait Config { type HrmpChannelAcceptedHandler: HandleHrmpChannelAccepted; /// Allows optional logic execution for the `HrmpChannelClosing` XCM notification. type HrmpChannelClosingHandler: HandleHrmpChannelClosing; - /// Allows recording the last executed XCM (used by dry-run runtime APIs). type XcmRecorder: RecordXcm; } diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index 8602e7b51f08..137c51cf8003 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -503,10 +503,10 @@ impl XcmExecutor { // If we can't convert, then we return the original asset. // It will error later in any case. None => { - log::trace!( + tracing::trace!( target: "xcm::take_fee", - "Could not convert fees to {:?}", - asset_used_for_fees, + ?asset_used_for_fees, + "Could not convert fees", ); asset_needed_for_fees.clone() }, @@ -517,7 +517,7 @@ impl XcmExecutor { } else { asset_needed_for_fees.clone() }; - log::trace!(target: "xcm::fees", "Actual asset for fees: {:?}", asset_to_pay_for_fees); + tracing::trace!(target: "xcm::fees", ?asset_to_pay_for_fees); let assets_to_swap = if self.fees_mode.jit_withdraw { let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; Config::AssetTransactor::withdraw_asset( @@ -525,15 +525,15 @@ impl XcmExecutor { origin, Some(&self.context), )?; - log::trace!(target: "xcm::fees", "Asset needed for fees: {:?}", asset_needed_for_fees); + tracing::trace!(target: "xcm::fees", ?asset_needed_for_fees); asset_to_pay_for_fees.clone().into() } else { - let assets = self + let assets_taken_from_holding_to_pay_delivery_fees = self .holding .try_take(asset_to_pay_for_fees.clone().into()) .map_err(|_| XcmError::NotHoldingFees)?; - log::trace!(target: "xcm::fees", "Assets taken from holding to pay transport fee: {:?}", assets); - let mut iter = assets.fungible_assets_iter(); + 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() }; @@ -545,10 +545,10 @@ impl XcmExecutor { false, ) .map_err(|given_assets| { - log::error!( + tracing::error!( target: "xcm::fees", - "Swap was deemed necessary but couldn't be done. Given assets: {:?}.", - given_assets, + ?given_assets, + "Swap was deemed necessary but couldn't be done", ); XcmError::FeesNotMet })? @@ -953,7 +953,7 @@ impl XcmExecutor { let (_, fee) = validate_send::(dest.clone(), Xcm(message_to_weigh))?; let maybe_delivery_fee = if let Some(asset_needed_for_fees) = fee.get(0) { - log::trace!( + 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, @@ -971,10 +971,10 @@ impl XcmExecutor { Some(necessary_amount) => (asset_to_pay_for_fees.clone(), necessary_amount).into(), None => { - log::trace!( + tracing::trace!( target: "xcm::take_fee", - "Could not convert fees to {:?}", - asset_to_pay_for_fees, + ?asset_to_pay_for_fees, + "Could not convert fees", ); asset_needed_for_fees.clone() }, @@ -985,7 +985,7 @@ impl XcmExecutor { // set aside fee to be charged by XcmSender let delivery_fee = self.holding.saturating_take(actual_asset_to_use_for_fees.into()); - log::trace!(target: "xcm::DepositReserveAsset", "Transport fee: {:?}", delivery_fee); + tracing::trace!(target: "xcm::DepositReserveAsset", ?delivery_fee); Some(delivery_fee) } else { None @@ -993,7 +993,7 @@ impl XcmExecutor { // now take assets to deposit (excluding transport_fee) // TODO: We should be taking `assets - transport_fee` let deposited = self.holding.saturating_take(assets); - log::trace!(target: "xcm::DepositReserveAsset", "Assets except transport fee: {:?}", deposited); + 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))?; } @@ -1087,7 +1087,7 @@ impl XcmExecutor { // 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()); - log::trace!(target: "xcm::executor::BuyExecution", "Asset for fees: {:?}", self.asset_used_for_fees); + 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)?; diff --git a/prdoc/pr_4375.prdoc b/prdoc/pr_4375.prdoc index a88bd932a054..9e2205f57c90 100644 --- a/prdoc/pr_4375.prdoc +++ b/prdoc/pr_4375.prdoc @@ -28,7 +28,7 @@ doc: crates: - name: staging-xcm-executor - bump: minor + bump: major - name: asset-hub-westend-runtime bump: minor - name: asset-hub-rococo-runtime From d1baffc28d05b93f6b0289d3788dddd7826931ff Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Thu, 18 Jul 2024 19:35:25 +0200 Subject: [PATCH 39/43] chore: address more feedback --- Cargo.lock | 1 + polkadot/xcm/xcm-builder/Cargo.toml | 2 + .../xcm/xcm-builder/src/asset_exchange.rs | 52 +++++++++++++++---- polkadot/xcm/xcm-executor/src/lib.rs | 12 +++-- substrate/frame/asset-conversion/src/swap.rs | 15 ++++-- 5 files changed, 65 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0d683e6c5fc9..d65bbca531a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20941,6 +20941,7 @@ dependencies = [ "sp-arithmetic", "sp-io", "sp-runtime", + "sp-std 14.0.0", "sp-weights", "staging-xcm", "staging-xcm-executor", diff --git a/polkadot/xcm/xcm-builder/Cargo.toml b/polkadot/xcm/xcm-builder/Cargo.toml index a3c92f228ae4..58509f848da9 100644 --- a/polkadot/xcm/xcm-builder/Cargo.toml +++ b/polkadot/xcm/xcm-builder/Cargo.toml @@ -17,6 +17,7 @@ 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 } @@ -66,6 +67,7 @@ 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.rs b/polkadot/xcm/xcm-builder/src/asset_exchange.rs index d17cfdade70b..cc214b03b069 100644 --- a/polkadot/xcm/xcm-builder/src/asset_exchange.rs +++ b/polkadot/xcm/xcm-builder/src/asset_exchange.rs @@ -30,7 +30,9 @@ use xcm_executor::{ /// An adapter from [`pallet_asset_conversion::SwapCredit`] and /// [`pallet_asset_conversion::QuoteExchangePrice`] to [`xcm_executor::traits::AssetExchange`]. /// -/// Takes just one fungible asset in `give` and allows only one fungible asset in `want`. +/// 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. pub struct FungiblesPoolAdapter( PhantomData<(AssetConversion, Fungibles, Matcher, AccountId)>, ); @@ -50,7 +52,7 @@ where _: Option<&Location>, give: AssetsInHolding, want: &Assets, - _: bool, + maximal: bool, ) -> Result { let give_asset = give.fungible_assets_iter().next().ok_or_else(|| { log::trace!( @@ -66,7 +68,7 @@ where ); give.clone() })?; - let (give_asset_id, balance) = + let (give_asset_id, give_amount) = Matcher::matches_fungibles(&give_asset).map_err(|error| { log::trace!( target: "xcm::FungiblesPoolAdapter::exchange_asset", @@ -89,11 +91,25 @@ where // 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, balance); + let credit_in = Fungibles::issue(give_asset_id, give_amount); // Do the swap. - let (credit_out, credit_change) = - >::swap_tokens_for_exact_tokens( + 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)| { + // TODO: Log 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, @@ -103,13 +119,19 @@ where give.clone() })?; - debug_assert!(credit_change.peek() == Zero::zero()); + // 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, _: bool) -> Option { + 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!( @@ -121,6 +143,7 @@ where () }) .ok()?; + // For `asset2`, we also want the desired amount. let (asset2_id, desired_asset2_amount) = Matcher::matches_fungibles(asset2) .map_err(|error| { log::trace!( @@ -132,13 +155,22 @@ where () }) .ok()?; - let necessary_asset1_amount = + // 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-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index 137c51cf8003..5576544cf421 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -491,6 +491,7 @@ impl XcmExecutor { 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( @@ -518,7 +519,8 @@ impl XcmExecutor { asset_needed_for_fees.clone() }; tracing::trace!(target: "xcm::fees", ?asset_to_pay_for_fees); - let assets_to_swap = if self.fees_mode.jit_withdraw { + // 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, @@ -537,10 +539,11 @@ 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. 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(), - assets_to_swap, + withdrawn_fee_asset, &asset_needed_for_fees.clone().into(), false, ) @@ -555,7 +558,10 @@ impl XcmExecutor { .into(); swapped_asset } else { - vec![asset_to_pay_for_fees].into() + // 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(()) diff --git a/substrate/frame/asset-conversion/src/swap.rs b/substrate/frame/asset-conversion/src/swap.rs index 95e28835eb39..3d18fd9e30ff 100644 --- a/substrate/frame/asset-conversion/src/swap.rs +++ b/substrate/frame/asset-conversion/src/swap.rs @@ -112,6 +112,7 @@ pub trait SwapCredit { ) -> Result<(Self::Credit, Self::Credit), (Self::Credit, DispatchError)>; } +// TODO: Remove and use `QuotePrice` once https://github.com/paritytech/polkadot-sdk/pull/4488 gets merged. /// Trait providing methods for quoting the exchange price between different asset classes. pub trait QuoteExchangePrice { /// Measure units of the asset classes for quoting. @@ -119,16 +120,22 @@ pub trait QuoteExchangePrice { /// Kind of assets that are going to be quoted. type AssetKind; - /// Provides the amount of `asset2` exchangeable for an exact `amount` of `asset1`. - fn quote_price_exact_tokens_for_tokens( + /// Quotes the amount of `asset1` required to obtain the exact `amount` of `asset2`. + /// + /// If `include_fee` is set to `true`, the price will include the pool's fee. + /// If the pool does not exist or the swap cannot be made, `None` is returned. + fn quote_price_tokens_for_exact_tokens( asset1: Self::AssetKind, asset2: Self::AssetKind, amount: Self::Balance, include_fee: bool, ) -> Option; - /// Provides the amount of `asset1` exchangeable for an exact amount of `asset2`. - fn quote_price_tokens_for_exact_tokens( + /// Quotes the amount of `asset2` resulting from swapping the exact `amount` of `asset1`. + /// + /// If `include_fee` is set to `true`, the price will include the pool's fee. + /// If the pool does not exist or the swap cannot be made, `None` is returned. + fn quote_price_exact_tokens_for_tokens( asset1: Self::AssetKind, asset2: Self::AssetKind, amount: Self::Balance, From d576a762d0a9f11053bfc8ce78bf27576e58d9dd Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Thu, 18 Jul 2024 19:36:34 +0200 Subject: [PATCH 40/43] fix: fmt --- .../src/tests/reserve_transfer.rs | 3 +- .../xcm/xcm-builder/src/asset_exchange.rs | 36 ++++++++++--------- 2 files changed, 21 insertions(+), 18 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 2727a143f4e1..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 @@ -1302,7 +1302,8 @@ fn reserve_transfer_usdt_from_para_to_para_through_asset_hub() { 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 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()); diff --git a/polkadot/xcm/xcm-builder/src/asset_exchange.rs b/polkadot/xcm/xcm-builder/src/asset_exchange.rs index cc214b03b069..7e34b9ae50c0 100644 --- a/polkadot/xcm/xcm-builder/src/asset_exchange.rs +++ b/polkadot/xcm/xcm-builder/src/asset_exchange.rs @@ -30,9 +30,9 @@ use xcm_executor::{ /// An adapter from [`pallet_asset_conversion::SwapCredit`] and /// [`pallet_asset_conversion::QuoteExchangePrice`] 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. +/// 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. pub struct FungiblesPoolAdapter( PhantomData<(AssetConversion, Fungibles, Matcher, AccountId)>, ); @@ -95,29 +95,31 @@ where // 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`. + // 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)| { + ) + .map_err(|(credit_in, _error)| { // TODO: Log 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, _)| { - drop(credit_in); - give.clone() - })?; + // 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, _)| { + 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. From 5e1c9997045b73990d60acf4dea009217254b4c3 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Mon, 22 Jul 2024 10:18:52 +0200 Subject: [PATCH 41/43] fix: fmt --- .../assets/asset-hub-rococo/src/xcm_config.rs | 6 +- .../asset-hub-westend/src/xcm_config.rs | 8 +- .../runtimes/testing/penpal/src/xcm_config.rs | 10 +- .../xcm/xcm-builder/src/asset_exchange.rs | 178 ------------------ 4 files changed, 12 insertions(+), 190 deletions(-) delete mode 100644 polkadot/xcm/xcm-builder/src/asset_exchange.rs 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 45ed8ed6cd2b..9537f6fd6a2f 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 @@ -55,9 +55,9 @@ use xcm_builder::{ EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, FungiblesPoolAdapter, GlobalConsensusParachainConvertsFor, HashedDescription, IsConcrete, LocalMint, MatchedConvertedConcreteId, NetworkExportTableItem, NoChecking, NonFungiblesAdapter, - ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SendXcmFeeToAccount, SiblingParachainAsNative, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignPaidRemoteExporter, SovereignSignedViaLocation, StartsWith, + ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SendXcmFeeToAccount, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignPaidRemoteExporter, SovereignSignedViaLocation, StartsWith, StartsWithExplicitGlobalConsensus, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithLatestLocationConverter, WithUniqueTopic, XcmFeeManagerFromComponents, XcmFeeToAccount, 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 e75e1b9a0c15..4d182b5ec682 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,10 +51,10 @@ use xcm_builder::{ EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, FungiblesPoolAdapter, GlobalConsensusParachainConvertsFor, HashedDescription, IsConcrete, LocalMint, MatchedConvertedConcreteId, NetworkExportTableItem, NoChecking, NonFungiblesAdapter, - ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SendXcmFeeToAccount, SiblingParachainAsNative, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, StartsWith, StartsWithExplicitGlobalConsensus, TakeWeightCredit, - TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, + ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SendXcmFeeToAccount, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, StartsWith, StartsWithExplicitGlobalConsensus, + TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithLatestLocationConverter, WithUniqueTopic, XcmFeeManagerFromComponents, XcmFeeToAccount, }; use xcm_executor::XcmExecutor; diff --git a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs index ec712d40e901..de6b4dd63aaa 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs @@ -51,11 +51,11 @@ use xcm_builder::{ AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AsPrefixedGeneralIndex, ConvertedConcreteId, EnsureXcmOrigin, FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, FungiblesPoolAdapter, IsConcrete, LocalMint, NativeAsset, - NoChecking, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SendXcmFeeToAccount, SiblingParachainAsNative, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, StartsWith, TakeWeightCredit, TrailingSetTopicAsId, - UsingComponents, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, - XcmFeeToAccount, + NoChecking, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SendXcmFeeToAccount, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, StartsWith, TakeWeightCredit, + TrailingSetTopicAsId, UsingComponents, WithComputedOrigin, WithUniqueTopic, + XcmFeeManagerFromComponents, XcmFeeToAccount, }; use xcm_executor::{traits::JustTry, XcmExecutor}; diff --git a/polkadot/xcm/xcm-builder/src/asset_exchange.rs b/polkadot/xcm/xcm-builder/src/asset_exchange.rs deleted file mode 100644 index 7e34b9ae50c0..000000000000 --- a/polkadot/xcm/xcm-builder/src/asset_exchange.rs +++ /dev/null @@ -1,178 +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 . - -//! Adapters that implement the [`xcm_executor::traits::AssetExchange`] trait. - -use core::marker::PhantomData; -use frame_support::traits::tokens::fungibles; -use pallet_asset_conversion::{QuoteExchangePrice, SwapCredit}; -use sp_runtime::traits::Zero; -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::QuoteExchangePrice`] 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. -pub struct FungiblesPoolAdapter( - PhantomData<(AssetConversion, Fungibles, Matcher, AccountId)>, -); -impl AssetExchange - for FungiblesPoolAdapter -where - AssetConversion: SwapCredit< - AccountId, - Balance = u128, - AssetKind = Fungibles::AssetId, - Credit = fungibles::Credit, - > + QuoteExchangePrice, - Fungibles: fungibles::Balanced, - Matcher: MatchesFungibles, -{ - fn exchange_asset( - _: Option<&Location>, - give: AssetsInHolding, - want: &Assets, - maximal: bool, - ) -> Result { - let give_asset = give.fungible_assets_iter().next().ok_or_else(|| { - log::trace!( - target: "xcm::FungiblesPoolAdapter::exchange_asset", - "No fungible asset was in `give`.", - ); - give.clone() - })?; - let want_asset = want.get(0).ok_or_else(|| { - log::trace!( - target: "xcm::FungiblesPoolAdapter::exchange_asset", - "No asset was in `want`.", - ); - give.clone() - })?; - let (give_asset_id, give_amount) = - Matcher::matches_fungibles(&give_asset).map_err(|error| { - log::trace!( - target: "xcm::FungiblesPoolAdapter::exchange_asset", - "Could not map XCM asset {:?} to FRAME asset. Error: {:?}", - give, - error, - ); - give.clone() - })?; - let (want_asset_id, want_amount) = - Matcher::matches_fungibles(&want_asset).map_err(|error| { - log::trace!( - target: "xcm::FungiblesPoolAdapter::exchange_asset", - "Could not map XCM asset {:?} to FRAME asset. Error: {:?}", - want, - 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)| { - // TODO: Log 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, _)| { - 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::FungiblesPoolAdapter::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::FungiblesPoolAdapter::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 1fe6c08dc4880428aa0001b87fbb56708ec9f9af Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Mon, 22 Jul 2024 11:00:58 +0200 Subject: [PATCH 42/43] feat(xcm-builder): rename FungiblesPoolAdapter to SingleAssetExchangeAdapter --- Cargo.lock | 1 + .../assets/asset-hub-rococo/src/xcm_config.rs | 4 +- .../asset-hub-westend/src/xcm_config.rs | 4 +- .../runtimes/testing/penpal/src/xcm_config.rs | 2 +- polkadot/xcm/xcm-builder/Cargo.toml | 4 +- .../xcm-builder/src/asset_exchange/mock.rs | 294 ++++++++++++++++++ .../xcm/xcm-builder/src/asset_exchange/mod.rs | 190 +++++++++++ .../xcm-builder/src/asset_exchange/tests.rs | 55 ++++ polkadot/xcm/xcm-builder/src/lib.rs | 2 +- prdoc/pr_4375.prdoc | 2 +- 10 files changed, 550 insertions(+), 8 deletions(-) create mode 100644 polkadot/xcm/xcm-builder/src/asset_exchange/mock.rs create mode 100644 polkadot/xcm/xcm-builder/src/asset_exchange/mod.rs create mode 100644 polkadot/xcm/xcm-builder/src/asset_exchange/tests.rs diff --git a/Cargo.lock b/Cargo.lock index a5cd22ebca36..8b039e28348e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21016,6 +21016,7 @@ dependencies = [ "primitive-types", "scale-info", "sp-arithmetic", + "sp-core", "sp-io", "sp-runtime", "sp-std 14.0.0", 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 9537f6fd6a2f..5e7ff098a3b7 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 @@ -53,7 +53,7 @@ use xcm_builder::{ AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, DenyThenTry, DescribeAllTerminal, DescribeFamily, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, - FungiblesPoolAdapter, GlobalConsensusParachainConvertsFor, HashedDescription, IsConcrete, + SingleAssetExchangeAdapter, GlobalConsensusParachainConvertsFor, HashedDescription, IsConcrete, LocalMint, MatchedConvertedConcreteId, NetworkExportTableItem, NoChecking, NonFungiblesAdapter, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SendXcmFeeToAccount, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, @@ -334,7 +334,7 @@ pub type TrustedTeleporters = ( /// 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 = FungiblesPoolAdapter< +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 4d182b5ec682..da5ef165dd06 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 @@ -49,7 +49,7 @@ use xcm_builder::{ AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, DenyThenTry, DescribeFamily, DescribePalletTerminal, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, - FungiblesPoolAdapter, GlobalConsensusParachainConvertsFor, HashedDescription, IsConcrete, + SingleAssetExchangeAdapter, GlobalConsensusParachainConvertsFor, HashedDescription, IsConcrete, LocalMint, MatchedConvertedConcreteId, NetworkExportTableItem, NoChecking, NonFungiblesAdapter, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SendXcmFeeToAccount, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, @@ -353,7 +353,7 @@ pub type TrustedTeleporters = ( /// 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 = FungiblesPoolAdapter< +pub type PoolAssetsExchanger = SingleAssetExchangeAdapter< crate::AssetConversion, crate::NativeAndAssets, ( diff --git a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs index de6b4dd63aaa..0320d5d71653 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs @@ -334,7 +334,7 @@ pub type TrustBackedAssetsConvertedConcreteId = /// 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 = FungiblesPoolAdapter< +pub type PoolAssetsExchanger = SingleAssetExchangeAdapter< crate::AssetConversion, crate::NativeAndAssets, ( diff --git a/polkadot/xcm/xcm-builder/Cargo.toml b/polkadot/xcm/xcm-builder/Cargo.toml index 58509f848da9..c7f293d67bdc 100644 --- a/polkadot/xcm/xcm-builder/Cargo.toml +++ b/polkadot/xcm/xcm-builder/Cargo.toml @@ -31,11 +31,13 @@ polkadot-parachain-primitives = { workspace = true } [dev-dependencies] env_logger = { workspace = true } -primitive-types = { workspace = true, default-features = true } +primitive-types = { features = ["codec", "num-traits", "scale-info"], workspace = true } +sp-core = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } pallet-xcm = { workspace = true, default-features = true } pallet-salary = { workspace = true, default-features = true } pallet-assets = { workspace = true, default-features = true } +pallet-asset-conversion = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } polkadot-runtime-parachains = { workspace = true, default-features = true } assert_matches = { workspace = true } diff --git a/polkadot/xcm/xcm-builder/src/asset_exchange/mock.rs b/polkadot/xcm/xcm-builder/src/asset_exchange/mock.rs new file mode 100644 index 000000000000..a0457cd4abd8 --- /dev/null +++ b/polkadot/xcm/xcm-builder/src/asset_exchange/mock.rs @@ -0,0 +1,294 @@ +// 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::{ + construct_runtime, derive_impl, ord_parameter_types, parameter_types, + traits::{ + fungible, tokens::imbalance::ResolveAssetTo, AsEnsureOriginWithArg, Everything, Nothing, + OriginTrait, + }, + PalletId, +}; +use sp_core::{ConstU32, ConstU64, Get}; +use sp_runtime::{ + traits::{AccountIdConversion, IdentityLookup, TryConvert}, + Permill, +}; +use xcm::prelude::*; +use xcm_executor::{traits::ConvertLocation, XcmExecutor}; + +pub type Block = frame_system::mocking::MockBlock; +pub type AccountId = u64; +pub type Balance = u64; + +construct_runtime! { + pub struct Runtime { + System: frame_system, + Balances: pallet_balances, + Assets: 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 AccountStore = System; +} + +const UNITS: u64 = 1_000_000_000_000; + +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 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 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: fungible::NativeOrWithId = fungible::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, fungible::NativeOrWithId)>; + +impl pallet_asset_conversion::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type HigherPrecisionBalance = sp_core::U256; + type AssetKind = fungible::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 = ConstU64<0>; // 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 = ConstU64<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(); +} + +/// Adapter for the native token. +pub type FungibleTransactor = crate::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. + crate::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 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 = (); + 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 = (); +} + +// TODO: Move to its own PR. +/// Simple converter to turn a u64 into a [`Location`] using the [`AccountIndex64`] junction +/// and no parents. +pub struct AliasesIntoAccountIndex64; +impl<'a> sp_runtime::traits::Convert<&'a AccountId, Location> for AliasesIntoAccountIndex64 { + fn convert(who: &AccountId) -> Location { + AccountIndex64 { network: None, index: *who }.into() + } +} + +// TODO: Move to its own PR. +/// 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()) + } +} + +// TODO: Move to its own PR. +/// `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; +} 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..c1fdbdb186c5 --- /dev/null +++ b/polkadot/xcm/xcm-builder/src/asset_exchange/mod.rs @@ -0,0 +1,190 @@ +// 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 that implement the [`xcm_executor::traits::AssetExchange`] trait. + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +use core::marker::PhantomData; +use frame_support::{ensure, traits::tokens::fungibles}; +use pallet_asset_conversion::{QuoteExchangePrice, SwapCredit}; +use sp_runtime::traits::Zero; +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::QuoteExchangePrice`] 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, + > + QuoteExchangePrice, + 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 {:?} to FRAME asset. Error: {:?}", + give, + 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 {:?} to FRAME asset. Error: {:?}", + want, + 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)| { + // TODO: Log 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, _)| { + 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/tests.rs b/polkadot/xcm/xcm-builder/src/asset_exchange/tests.rs new file mode 100644 index 000000000000..288c94fdee5d --- /dev/null +++ b/polkadot/xcm/xcm-builder/src/asset_exchange/tests.rs @@ -0,0 +1,55 @@ +// 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::*; + +/// Scenario: +/// Account #3 wants to use the local liquidity pool between two custom assets, +/// 1 and 2. +#[test] +fn maximal_exchange() {} + +#[test] +fn not_maximal_exchange() {} + +#[test] +fn maximal_quote() {} + +#[test] +fn not_maximal_quote() {} + +#[test] +fn no_asset_in_give() {} + +#[test] +fn more_than_one_asset_in_give() {} + +#[test] +fn no_asset_in_want() {} + +#[test] +fn more_than_one_asset_in_want() {} + +#[test] +fn give_asset_does_not_match() {} + +#[test] +fn want_asset_does_not_match() {} + +#[test] +fn exchange_fails() {} diff --git a/polkadot/xcm/xcm-builder/src/lib.rs b/polkadot/xcm/xcm-builder/src/lib.rs index 7519d00cfcf3..bec3bdcb05a0 100644 --- a/polkadot/xcm/xcm-builder/src/lib.rs +++ b/polkadot/xcm/xcm-builder/src/lib.rs @@ -36,7 +36,7 @@ pub use asset_conversion::{ }; mod asset_exchange; -pub use asset_exchange::FungiblesPoolAdapter; +pub use asset_exchange::SingleAssetExchangeAdapter; mod barriers; pub use barriers::{ diff --git a/prdoc/pr_4375.prdoc b/prdoc/pr_4375.prdoc index 9e2205f57c90..abf734faf233 100644 --- a/prdoc/pr_4375.prdoc +++ b/prdoc/pr_4375.prdoc @@ -19,7 +19,7 @@ doc: was leveraged. This config item allows exchanging assets and quoting exchange prices. An adapter was added to `xcm-builder` to make it compatible with `pallet-asset-conversion`: - `FungiblesPoolAdapter`. + `SingleAssetExchangeAdapter`. The new functionality in the executor pays delivery fees using only one asset, which might be swapped to the correct one. If you have a custom XcmRouter that returns more than one asset needed for delivery fees, then know From 184b2eb29f3a894176f190da43518caf70f88c03 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Wed, 24 Jul 2024 18:22:07 +0200 Subject: [PATCH 43/43] feat(xcm-builder): tests for SingleAssetExchangeAdapter --- .../assets/asset-hub-rococo/src/xcm_config.rs | 12 +- .../asset-hub-westend/src/xcm_config.rs | 11 +- .../xcm-builder/src/asset_exchange/mock.rs | 140 +++++++++++++--- .../xcm/xcm-builder/src/asset_exchange/mod.rs | 28 ++-- .../xcm-builder/src/asset_exchange/tests.rs | 154 ++++++++++++++++-- 5 files changed, 290 insertions(+), 55 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 5e7ff098a3b7..48531a2aba6f 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 @@ -53,14 +53,14 @@ use xcm_builder::{ AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, DenyThenTry, DescribeAllTerminal, DescribeFamily, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, - SingleAssetExchangeAdapter, GlobalConsensusParachainConvertsFor, HashedDescription, IsConcrete, - LocalMint, MatchedConvertedConcreteId, NetworkExportTableItem, NoChecking, NonFungiblesAdapter, + GlobalConsensusParachainConvertsFor, HashedDescription, IsConcrete, LocalMint, + MatchedConvertedConcreteId, NetworkExportTableItem, NoChecking, NonFungiblesAdapter, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SendXcmFeeToAccount, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, - SignedToAccountId32, SovereignPaidRemoteExporter, SovereignSignedViaLocation, StartsWith, - StartsWithExplicitGlobalConsensus, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, - WeightInfoBounds, WithComputedOrigin, WithLatestLocationConverter, WithUniqueTopic, - XcmFeeManagerFromComponents, XcmFeeToAccount, + SignedToAccountId32, SingleAssetExchangeAdapter, SovereignPaidRemoteExporter, + SovereignSignedViaLocation, StartsWith, StartsWithExplicitGlobalConsensus, TakeWeightCredit, + TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, + WithLatestLocationConverter, WithUniqueTopic, XcmFeeManagerFromComponents, XcmFeeToAccount, }; use xcm_executor::XcmExecutor; 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 da5ef165dd06..f65a2a0c1e4e 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 @@ -49,13 +49,14 @@ use xcm_builder::{ AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, DenyThenTry, DescribeFamily, DescribePalletTerminal, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, - SingleAssetExchangeAdapter, GlobalConsensusParachainConvertsFor, HashedDescription, IsConcrete, - LocalMint, MatchedConvertedConcreteId, NetworkExportTableItem, NoChecking, NonFungiblesAdapter, + GlobalConsensusParachainConvertsFor, HashedDescription, IsConcrete, LocalMint, + MatchedConvertedConcreteId, NetworkExportTableItem, NoChecking, NonFungiblesAdapter, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SendXcmFeeToAccount, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, StartsWith, StartsWithExplicitGlobalConsensus, - TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, - WithLatestLocationConverter, WithUniqueTopic, XcmFeeManagerFromComponents, XcmFeeToAccount, + SignedToAccountId32, SingleAssetExchangeAdapter, SovereignSignedViaLocation, StartsWith, + StartsWithExplicitGlobalConsensus, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, + WeightInfoBounds, WithComputedOrigin, WithLatestLocationConverter, WithUniqueTopic, + XcmFeeManagerFromComponents, XcmFeeToAccount, }; use xcm_executor::XcmExecutor; diff --git a/polkadot/xcm/xcm-builder/src/asset_exchange/mock.rs b/polkadot/xcm/xcm-builder/src/asset_exchange/mock.rs index a0457cd4abd8..37c19d7e046d 100644 --- a/polkadot/xcm/xcm-builder/src/asset_exchange/mock.rs +++ b/polkadot/xcm/xcm-builder/src/asset_exchange/mock.rs @@ -18,30 +18,34 @@ use core::marker::PhantomData; use frame_support::{ - construct_runtime, derive_impl, ord_parameter_types, parameter_types, + assert_ok, construct_runtime, derive_impl, ord_parameter_types, parameter_types, traits::{ - fungible, tokens::imbalance::ResolveAssetTo, AsEnsureOriginWithArg, Everything, Nothing, - OriginTrait, + fungible::{self, NativeFromLeft, NativeOrWithId}, + fungibles::Mutate, + tokens::imbalance::ResolveAssetTo, + AsEnsureOriginWithArg, Equals, Everything, Nothing, OriginTrait, PalletInfoAccess, }, PalletId, }; -use sp_core::{ConstU32, ConstU64, Get}; +use sp_core::{ConstU128, ConstU32, Get}; use sp_runtime::{ - traits::{AccountIdConversion, IdentityLookup, TryConvert}, - Permill, + 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 = u64; +pub type Balance = u128; construct_runtime! { pub struct Runtime { System: frame_system, Balances: pallet_balances, - Assets: pallet_assets::, + AssetsPallet: pallet_assets::, PoolAssets: pallet_assets::, XcmPallet: pallet_xcm, AssetConversion: pallet_asset_conversion, @@ -58,19 +62,25 @@ impl frame_system::Config for Runtime { #[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] impl pallet_balances::Config for Runtime { + type Balance = Balance; type AccountStore = System; + type ExistentialDeposit = ConstU128<1>; } -const UNITS: u64 = 1_000_000_000_000; - 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 CreateOrigin = AsEnsureOriginWithArg>; - type ForceOrigin = frame_system::EnsureRoot; + 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 = (); } @@ -78,19 +88,25 @@ impl pallet_assets::Config for Runtime { #[derive_impl(pallet_assets::config_preludes::TestDefaultConfig)] impl pallet_assets::Config for Runtime { type Currency = Balances; - type CreateOrigin = AsEnsureOriginWithArg>; - type ForceOrigin = frame_system::EnsureRoot; + 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>; + fungible::UnionOf, AccountId>; parameter_types! { pub const AssetConversionPalletId: PalletId = PalletId(*b"py/ascon"); - pub const Native: fungible::NativeOrWithId = fungible::NativeOrWithId::Native; + pub const Native: NativeOrWithId = NativeOrWithId::Native; pub const LiquidityWithdrawalFee: Permill = Permill::from_percent(0); } @@ -99,14 +115,16 @@ ord_parameter_types! { AccountIdConversion::::into_account_truncating(&AssetConversionPalletId::get()); } -pub type PoolIdToAccountId = - pallet_asset_conversion::AccountIdConverter, fungible::NativeOrWithId)>; +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 = fungible::NativeOrWithId; + type AssetKind = NativeOrWithId; type Assets = NativeAndAssets; type PoolId = (Self::AssetKind, Self::AssetKind); type PoolLocator = pallet_asset_conversion::WithFirstAsset< @@ -117,14 +135,14 @@ impl pallet_asset_conversion::Config for Runtime { >; type PoolAssetId = u32; type PoolAssets = PoolAssets; - type PoolSetupFee = ConstU64<0>; // Asset class deposit fees are sufficient to prevent spam + 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 = ConstU64<100>; + type MintMinLiquidity = ConstU128<100>; type WeightInfo = (); #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = assets_common::benchmarks::AssetPairFactory< @@ -143,15 +161,17 @@ parameter_types! { 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 = crate::FungibleAdapter< +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. - crate::IsConcrete, + IsConcrete, // How to convert an XCM Location into a local account id. // This is also something that's configured in the XCM executor. LocationToAccountId, @@ -163,6 +183,41 @@ pub type FungibleTransactor = crate::FungibleAdapter< 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(pallet_instance), GeneralIndex(index)]) => + 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; @@ -180,7 +235,7 @@ impl xcm_executor::Config for XcmConfig { type ResponseHandler = (); type AssetTrap = (); type AssetLocker = (); - type AssetExchanger = (); + type AssetExchanger = PoolAssetsExchanger; type AssetClaims = (); type SubscriptionService = (); type PalletInstancesInfo = (); @@ -292,3 +347,40 @@ impl pallet_xcm::Config for Runtime { 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/mod.rs b/polkadot/xcm/xcm-builder/src/asset_exchange/mod.rs index c1fdbdb186c5..a389a96e6a98 100644 --- a/polkadot/xcm/xcm-builder/src/asset_exchange/mod.rs +++ b/polkadot/xcm/xcm-builder/src/asset_exchange/mod.rs @@ -24,7 +24,6 @@ mod tests; use core::marker::PhantomData; use frame_support::{ensure, traits::tokens::fungibles}; use pallet_asset_conversion::{QuoteExchangePrice, SwapCredit}; -use sp_runtime::traits::Zero; use sp_std::vec; use xcm::prelude::*; use xcm_executor::{ @@ -84,8 +83,8 @@ where Matcher::matches_fungibles(&give_asset).map_err(|error| { log::trace!( target: "xcm::SingleAssetExchangeAdapter::exchange_asset", - "Could not map XCM asset {:?} to FRAME asset. Error: {:?}", - give, + "Could not map XCM asset give {:?} to FRAME asset. Error: {:?}", + give_asset, error, ); give.clone() @@ -94,8 +93,8 @@ where Matcher::matches_fungibles(&want_asset).map_err(|error| { log::trace!( target: "xcm::SingleAssetExchangeAdapter::exchange_asset", - "Could not map XCM asset {:?} to FRAME asset. Error: {:?}", - want, + "Could not map XCM asset want {:?} to FRAME asset. Error: {:?}", + want_asset, error, ); give.clone() @@ -114,28 +113,37 @@ where credit_in, Some(want_amount), ) - .map_err(|(credit_in, _error)| { - // TODO: Log error. + .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) = + let (credit_out, _credit_change) = >::swap_tokens_for_exact_tokens( vec![swap_asset, want_asset_id], credit_in, want_amount, ) - .map_err(|(credit_in, _)| { + .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()); + // debug_assert!(credit_change.peek() == Zero::zero()); credit_out }; diff --git a/polkadot/xcm/xcm-builder/src/asset_exchange/tests.rs b/polkadot/xcm/xcm-builder/src/asset_exchange/tests.rs index 288c94fdee5d..b76dae74cd84 100644 --- a/polkadot/xcm/xcm-builder/src/asset_exchange/tests.rs +++ b/polkadot/xcm/xcm-builder/src/asset_exchange/tests.rs @@ -17,39 +17,173 @@ //! Tests for the [`SingleAssetExchangeAdapter`] type. use super::mock::*; +use frame_support::{ + assert_ok, + traits::{ + fungible::NativeOrWithId, + fungibles::{Inspect, Mutate}, + }, +}; +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() {} +fn maximal_exchange() { + let _ = env_logger::builder().is_test(true).try_init().unwrap(); + 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 not_maximal_exchange() {} +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() {} +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 not_maximal_quote() {} +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() {} +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() {} +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() {} +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() {} +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() {} +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() {} +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) +}