From 98c7ded0c33386fde02d2d303e415ca5a6938a1b Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Fri, 3 May 2024 18:05:53 +0200 Subject: [PATCH] WIP --- .../src/tests/reserve_transfer.rs | 122 ++++++++++++++++++ .../asset-hub-westend/src/xcm_config.rs | 42 ++++++ .../bridge-hub-westend/src/xcm_config.rs | 1 + .../collectives-westend/src/xcm_config.rs | 1 + .../people/people-westend/src/xcm_config.rs | 1 + .../runtimes/testing/penpal/src/xcm_config.rs | 1 + polkadot/runtime/rococo/src/xcm_config.rs | 1 + polkadot/runtime/westend/src/xcm_config.rs | 1 + polkadot/xcm/pallet-xcm/src/lib.rs | 23 ++-- polkadot/xcm/src/v4/traits.rs | 4 +- polkadot/xcm/xcm-executor/src/config.rs | 5 +- polkadot/xcm/xcm-executor/src/lib.rs | 36 ++++-- .../xcm-executor/src/traits/fee_manager.rs | 20 +++ polkadot/xcm/xcm-executor/src/traits/mod.rs | 2 +- 14 files changed, 234 insertions(+), 26 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 65d013a0eec40..bbd3c8cb1929b 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 @@ -1137,3 +1137,125 @@ 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 local asset and native asset from System Parachain to Parachain should +/// work +#[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(); + + let asset_id = 1984; + // Create sufficient asset. + 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(); + 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; + dbg!(&sender_balance_before); + let sender_assets_before = AssetHubWestend::execute_with(|| { + type Assets = ::Assets; + >::balance(asset_id, &sender) + }); + dbg!(&sender_assets_before); + let receiver_foreign_assets_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance( + system_para_foreign_asset_location.clone(), + &receiver, + ) + }); + dbg!(&receiver_foreign_assets_before); + + // 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; + dbg!(&sender_balance_after); + let sender_assets_after = AssetHubWestend::execute_with(|| { + type Assets = ::Assets; + >::balance(asset_id, &sender) + }); + dbg!(&sender_assets_after); + let receiver_foreign_assets_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance( + system_para_foreign_asset_location.clone(), + &receiver, + ) + }); + dbg!(&receiver_foreign_assets_after); +} 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 d610bfd768cdf..1e34413482464 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, }; +use core::marker::PhantomData; use frame_support::{ parameter_types, traits::{ @@ -349,6 +350,46 @@ pub type TrustedTeleporters = ( IsForeignConcreteAsset>>, ); +use xcm_executor::traits::{MatchesFungibles, AssetConversion}; +use frame_support::traits::fungibles; +use frame_support::traits::tokens::ConversionToAssetBalance; + +pub struct IsSufficientAssetConverter(PhantomData<(Runtime, Matcher, BalanceConverter, ConcreteAssets, AssetsInstance)>); +impl AssetConversion for IsSufficientAssetConverter +where + Runtime: pallet_assets::Config, + Matcher: MatchesFungibles< + >::AssetId, + >::Balance + >, + BalanceConverter: ConversionToAssetBalance< + u128, + >::AssetId, + >::Balance + >, + AssetsInstance: 'static, +{ + fn convert_asset(asset: &Asset, asset_id: &AssetId) -> Asset { + let desired_asset: Asset = (asset_id.clone(), 0u128).into(); // To comply with the interface. + let local_asset_id = match Matcher::matches_fungibles(&desired_asset) { + Ok((local_asset_id, _)) => local_asset_id, + Err(_) => return asset.clone(), // We return the same asset that was passed in in case of an error. + }; + log::trace!(target: "xcm::convert_asset", "local asset id: {:?}", local_asset_id); + let Fungibility::Fungible(old_asset_amount) = asset.fun else { todo!() }; + let new_asset_amount = BalanceConverter::to_asset_balance(old_asset_amount, local_asset_id).map_err(|_| ()).unwrap(); // TODO + (asset_id.clone(), new_asset_amount).into() + } +} + +pub type IsSufficientFeeAssetConverter = IsSufficientAssetConverter< + Runtime, + TrustBackedAssetsConvertedConcreteId, + pallet_assets::BalanceToAssetBalance, + Assets, + TrustBackedAssetsInstance, +>; + pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; @@ -419,6 +460,7 @@ impl xcm_executor::Config for XcmConfig { >, >, ); + type AssetConverter = IsSufficientFeeAssetConverter; type ResponseHandler = PolkadotXcm; type AssetTrap = PolkadotXcm; type AssetClaims = PolkadotXcm; 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 f147cd9653fe7..286a27c82b756 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 84697c3e36341..47b95a945b2e5 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 0a903f9150563..1a5b34a4cbea8 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/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs index 711041f6d6e28..a89cebafaeea6 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs @@ -362,6 +362,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type AssetConverter = (); } /// Multiplier used for dedicated `TakeFirstAssetTrader` with `ForeignAssets` instance. diff --git a/polkadot/runtime/rococo/src/xcm_config.rs b/polkadot/runtime/rococo/src/xcm_config.rs index c7063bd7ad616..80d514f7280dd 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 f661c4b0e4f43..5f75334c82192 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! { diff --git a/polkadot/xcm/pallet-xcm/src/lib.rs b/polkadot/xcm/pallet-xcm/src/lib.rs index af3b66121ea13..d3940836bafec 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 f6136c76d808f..930ae864a763f 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/config.rs b/polkadot/xcm/xcm-executor/src/config.rs index b296d32ca2adb..1564eff227a4f 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/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index a7052328da001..f192e74f8be9d 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,19 @@ 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); + let new_asset = Config::AssetConverter::convert_asset(&first, &asset_for_fees); + log::trace!(target: "xcm::charge_fees", "New asset: {:?}", new_asset); + Config::AssetTransactor::withdraw_asset(&new_asset, &origin, None)?; Config::FeeManager::handle_fee(fees, None, FeeReason::ChargeFees); } Ok(()) @@ -301,6 +309,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 +444,26 @@ 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 paid = if self.fees_mode.jit_withdraw { let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; - for asset in fee.inner() { + for asset in fees.inner() { Config::AssetTransactor::withdraw_asset(&asset, origin, Some(&self.context))?; } - fee + fees } else { - self.holding.try_take(fee.into()).map_err(|_| XcmError::NotHoldingFees)?.into() + self.holding.try_take(fees.into()).map_err(|_| XcmError::NotHoldingFees)?.into() }; Config::FeeManager::handle_fee(paid, Some(&self.context), reason); Ok(()) @@ -843,6 +852,9 @@ 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); + Config::AssetConverter::convert_asset(&first, asset_id); // set aside fee to be charged by XcmSender let transport_fee = self.holding.saturating_take(fee.into()); @@ -937,6 +949,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> { diff --git a/polkadot/xcm/xcm-executor/src/traits/fee_manager.rs b/polkadot/xcm/xcm-executor/src/traits/fee_manager.rs index b6e303daaad89..0dab28a2dc601 100644 --- a/polkadot/xcm/xcm-executor/src/traits/fee_manager.rs +++ b/polkadot/xcm/xcm-executor/src/traits/fee_manager.rs @@ -58,3 +58,23 @@ 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, the returned Asset + /// is the same `asset` passed in. + fn convert_asset(asset: &Asset, asset_id: &AssetId) -> Asset; +} + +/// Dummy implementation that just returns the same asset that was passed in, +/// ignoring the `asset_id`. +impl AssetConversion for () { + fn convert_asset(asset: &Asset, _: &AssetId) -> Asset { + asset.clone() + } +} diff --git a/polkadot/xcm/xcm-executor/src/traits/mod.rs b/polkadot/xcm/xcm-executor/src/traits/mod.rs index aa3f0d26e3025..0f742c298827b 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;