#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
use alloc::{vec, vec::Vec};
use codec::{Decode, Encode};
use core::{fmt::Debug, marker::PhantomData};
use frame_support::{
dispatch::GetDispatchInfo,
ensure,
traits::{Contains, ContainsPair, Defensive, Get, PalletsInfoAccess},
};
use sp_core::defer;
use sp_io::hashing::blake2_128;
use sp_weights::Weight;
use xcm::latest::{prelude::*, AssetTransferFilter};
pub mod traits;
use traits::{
validate_export, AssetExchange, AssetLock, CallDispatcher, ClaimAssets, ConvertOrigin,
DropAssets, Enact, ExportXcm, FeeManager, FeeReason, HandleHrmpChannelAccepted,
HandleHrmpChannelClosing, HandleHrmpNewChannelOpenRequest, OnResponse, ProcessTransaction,
Properties, ShouldExecute, TransactAsset, VersionChangeNotifier, WeightBounds, WeightTrader,
XcmAssetTransfers,
};
pub use traits::RecordXcm;
mod assets;
pub use assets::AssetsInHolding;
mod config;
pub use config::Config;
#[cfg(test)]
mod tests;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct FeesMode {
pub jit_withdraw: bool,
}
const RECURSION_LIMIT: u8 = 10;
environmental::environmental!(recursion_count: u8);
pub struct XcmExecutor<Config: config::Config> {
holding: AssetsInHolding,
holding_limit: usize,
context: XcmContext,
original_origin: Location,
trader: Config::Trader,
error: Option<(u32, XcmError)>,
total_surplus: Weight,
total_refunded: Weight,
error_handler: Xcm<Config::RuntimeCall>,
error_handler_weight: Weight,
appendix: Xcm<Config::RuntimeCall>,
appendix_weight: Weight,
transact_status: MaybeErrorCode,
fees_mode: FeesMode,
fees: AssetsInHolding,
asset_used_in_buy_execution: Option<AssetId>,
message_weight: Weight,
asset_claimer: Option<Location>,
_config: PhantomData<Config>,
}
#[cfg(any(test, feature = "runtime-benchmarks"))]
impl<Config: config::Config> XcmExecutor<Config> {
pub fn holding(&self) -> &AssetsInHolding {
&self.holding
}
pub fn set_holding(&mut self, v: AssetsInHolding) {
self.holding = v
}
pub fn holding_limit(&self) -> &usize {
&self.holding_limit
}
pub fn set_holding_limit(&mut self, v: usize) {
self.holding_limit = v
}
pub fn origin(&self) -> &Option<Location> {
&self.context.origin
}
pub fn set_origin(&mut self, v: Option<Location>) {
self.context.origin = v
}
pub fn original_origin(&self) -> &Location {
&self.original_origin
}
pub fn set_original_origin(&mut self, v: Location) {
self.original_origin = v
}
pub fn trader(&self) -> &Config::Trader {
&self.trader
}
pub fn set_trader(&mut self, v: Config::Trader) {
self.trader = v
}
pub fn error(&self) -> &Option<(u32, XcmError)> {
&self.error
}
pub fn set_error(&mut self, v: Option<(u32, XcmError)>) {
self.error = v
}
pub fn total_surplus(&self) -> &Weight {
&self.total_surplus
}
pub fn set_total_surplus(&mut self, v: Weight) {
self.total_surplus = v
}
pub fn total_refunded(&self) -> &Weight {
&self.total_refunded
}
pub fn set_total_refunded(&mut self, v: Weight) {
self.total_refunded = v
}
pub fn error_handler(&self) -> &Xcm<Config::RuntimeCall> {
&self.error_handler
}
pub fn set_error_handler(&mut self, v: Xcm<Config::RuntimeCall>) {
self.error_handler = v
}
pub fn error_handler_weight(&self) -> &Weight {
&self.error_handler_weight
}
pub fn set_error_handler_weight(&mut self, v: Weight) {
self.error_handler_weight = v
}
pub fn appendix(&self) -> &Xcm<Config::RuntimeCall> {
&self.appendix
}
pub fn set_appendix(&mut self, v: Xcm<Config::RuntimeCall>) {
self.appendix = v
}
pub fn appendix_weight(&self) -> &Weight {
&self.appendix_weight
}
pub fn set_appendix_weight(&mut self, v: Weight) {
self.appendix_weight = v
}
pub fn transact_status(&self) -> &MaybeErrorCode {
&self.transact_status
}
pub fn set_transact_status(&mut self, v: MaybeErrorCode) {
self.transact_status = v
}
pub fn fees_mode(&self) -> &FeesMode {
&self.fees_mode
}
pub fn set_fees_mode(&mut self, v: FeesMode) {
self.fees_mode = v
}
pub fn fees(&self) -> &AssetsInHolding {
&self.fees
}
pub fn set_fees(&mut self, value: AssetsInHolding) {
self.fees = value;
}
pub fn topic(&self) -> &Option<[u8; 32]> {
&self.context.topic
}
pub fn set_topic(&mut self, v: Option<[u8; 32]>) {
self.context.topic = v;
}
pub fn asset_claimer(&self) -> Option<Location> {
self.asset_claimer.clone()
}
pub fn set_message_weight(&mut self, weight: Weight) {
self.message_weight = weight;
}
}
pub struct WeighedMessage<Call>(Weight, Xcm<Call>);
impl<C> PreparedMessage for WeighedMessage<C> {
fn weight_of(&self) -> Weight {
self.0
}
}
#[cfg(any(test, feature = "std"))]
impl<C> WeighedMessage<C> {
pub fn new(weight: Weight, message: Xcm<C>) -> Self {
Self(weight, message)
}
}
impl<Config: config::Config> ExecuteXcm<Config::RuntimeCall> for XcmExecutor<Config> {
type Prepared = WeighedMessage<Config::RuntimeCall>;
fn prepare(
mut message: Xcm<Config::RuntimeCall>,
) -> Result<Self::Prepared, Xcm<Config::RuntimeCall>> {
match Config::Weigher::weight(&mut message) {
Ok(weight) => Ok(WeighedMessage(weight, message)),
Err(_) => Err(message),
}
}
fn execute(
origin: impl Into<Location>,
WeighedMessage(xcm_weight, mut message): WeighedMessage<Config::RuntimeCall>,
id: &mut XcmHash,
weight_credit: Weight,
) -> Outcome {
let origin = origin.into();
tracing::trace!(
target: "xcm::execute",
?origin,
?message,
?weight_credit,
"Executing message",
);
let mut properties = Properties { weight_credit, message_id: None };
if Config::XcmRecorder::should_record() {
Config::XcmRecorder::record(message.clone().into());
}
if let Err(e) = Config::Barrier::should_execute(
&origin,
message.inner_mut(),
xcm_weight,
&mut properties,
) {
tracing::trace!(
target: "xcm::execute",
?origin,
?message,
?properties,
error = ?e,
"Barrier blocked execution",
);
return Outcome::Error { error: XcmError::Barrier }
}
*id = properties.message_id.unwrap_or(*id);
let mut vm = Self::new(origin, *id);
vm.message_weight = xcm_weight;
while !message.0.is_empty() {
let result = vm.process(message);
tracing::trace!(target: "xcm::execute", ?result, "Message executed");
message = if let Err(error) = result {
vm.total_surplus.saturating_accrue(error.weight);
vm.error = Some((error.index, error.xcm_error));
vm.take_error_handler().or_else(|| vm.take_appendix())
} else {
vm.drop_error_handler();
vm.take_appendix()
}
}
vm.post_process(xcm_weight)
}
fn charge_fees(origin: impl Into<Location>, fees: Assets) -> 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)?;
}
Config::FeeManager::handle_fee(fees.into(), None, FeeReason::ChargeFees);
}
Ok(())
}
}
impl<Config: config::Config> XcmAssetTransfers for XcmExecutor<Config> {
type IsReserve = Config::IsReserve;
type IsTeleporter = Config::IsTeleporter;
type AssetTransactor = Config::AssetTransactor;
}
impl<Config: config::Config> FeeManager for XcmExecutor<Config> {
fn is_waived(origin: Option<&Location>, r: FeeReason) -> bool {
Config::FeeManager::is_waived(origin, r)
}
fn handle_fee(fee: Assets, context: Option<&XcmContext>, r: FeeReason) {
Config::FeeManager::handle_fee(fee, context, r)
}
}
#[derive(Debug, PartialEq)]
pub struct ExecutorError {
pub index: u32,
pub xcm_error: XcmError,
pub weight: Weight,
}
#[cfg(feature = "runtime-benchmarks")]
impl From<ExecutorError> for frame_benchmarking::BenchmarkError {
fn from(error: ExecutorError) -> Self {
tracing::error!(
index = ?error.index,
xcm_error = ?error.xcm_error,
weight = ?error.weight,
"XCM ERROR",
);
Self::Stop("xcm executor error: see error logs")
}
}
impl<Config: config::Config> XcmExecutor<Config> {
pub fn new(origin: impl Into<Location>, message_id: XcmHash) -> Self {
let origin = origin.into();
Self {
holding: AssetsInHolding::new(),
holding_limit: Config::MaxAssetsIntoHolding::get() as usize,
context: XcmContext { origin: Some(origin.clone()), message_id, topic: None },
original_origin: origin,
trader: Config::Trader::new(),
error: None,
total_surplus: Weight::zero(),
total_refunded: Weight::zero(),
error_handler: Xcm(vec![]),
error_handler_weight: Weight::zero(),
appendix: Xcm(vec![]),
appendix_weight: Weight::zero(),
transact_status: Default::default(),
fees_mode: FeesMode { jit_withdraw: false },
fees: AssetsInHolding::new(),
asset_used_in_buy_execution: None,
message_weight: Weight::zero(),
asset_claimer: None,
_config: PhantomData,
}
}
pub fn post_process(mut self, xcm_weight: Weight) -> Outcome {
let _ = self.refund_surplus();
drop(self.trader);
let mut weight_used = xcm_weight.saturating_sub(self.total_surplus);
if !self.holding.is_empty() {
tracing::trace!(
target: "xcm::post_process",
holding_register = ?self.holding,
context = ?self.context,
original_origin = ?self.original_origin,
"Trapping assets in holding register",
);
let claimer = if let Some(asset_claimer) = self.asset_claimer.as_ref() {
asset_claimer
} else {
self.context.origin.as_ref().unwrap_or(&self.original_origin)
};
let trap_weight = Config::AssetTrap::drop_assets(claimer, self.holding, &self.context);
weight_used.saturating_accrue(trap_weight);
};
match self.error {
None => Outcome::Complete { used: weight_used },
Some((_i, e)) => {
tracing::trace!(
target: "xcm::post_process",
instruction = ?_i,
error = ?e,
original_origin = ?self.original_origin,
"Execution failed",
);
Outcome::Incomplete { used: weight_used, error: e }
},
}
}
fn origin_ref(&self) -> Option<&Location> {
self.context.origin.as_ref()
}
fn cloned_origin(&self) -> Option<Location> {
self.context.origin.clone()
}
fn send(
&mut self,
dest: Location,
msg: Xcm<()>,
reason: FeeReason,
) -> Result<XcmHash, XcmError> {
tracing::trace!(
target: "xcm::send",
?msg,
destination = ?dest,
reason = ?reason,
"Sending msg",
);
let (ticket, fee) = validate_send::<Config::XcmSender>(dest, msg)?;
self.take_fee(fee, reason)?;
Config::XcmSender::deliver(ticket).map_err(Into::into)
}
fn take_error_handler(&mut self) -> Xcm<Config::RuntimeCall> {
let mut r = Xcm::<Config::RuntimeCall>(vec![]);
core::mem::swap(&mut self.error_handler, &mut r);
self.error_handler_weight = Weight::zero();
r
}
fn drop_error_handler(&mut self) {
self.error_handler = Xcm::<Config::RuntimeCall>(vec![]);
self.total_surplus.saturating_accrue(self.error_handler_weight);
self.error_handler_weight = Weight::zero();
}
fn take_appendix(&mut self) -> Xcm<Config::RuntimeCall> {
let mut r = Xcm::<Config::RuntimeCall>(vec![]);
core::mem::swap(&mut self.appendix, &mut r);
self.appendix_weight = Weight::zero();
r
}
fn ensure_can_subsume_assets(&self, assets_length: usize) -> Result<(), XcmError> {
let worst_case_holding_len = self.holding.len() + assets_length;
tracing::trace!(
target: "xcm::ensure_can_subsume_assets",
?worst_case_holding_len,
holding_limit = ?self.holding_limit,
"Ensuring subsume assets work",
);
ensure!(worst_case_holding_len <= self.holding_limit * 2, XcmError::HoldingWouldOverflow);
Ok(())
}
fn refund_surplus(&mut self) -> Result<(), XcmError> {
let current_surplus = self.total_surplus.saturating_sub(self.total_refunded);
tracing::trace!(
target: "xcm::refund_surplus",
total_surplus = ?self.total_surplus,
total_refunded = ?self.total_refunded,
?current_surplus,
"Refunding surplus",
);
if current_surplus.any_gt(Weight::zero()) {
if let Some(w) = self.trader.refund_weight(current_surplus, &self.context) {
if !self.holding.contains_asset(&(w.id.clone(), 1).into()) &&
self.ensure_can_subsume_assets(1).is_err()
{
let _ = self
.trader
.buy_weight(current_surplus, w.into(), &self.context)
.defensive_proof(
"refund_weight returned an asset capable of buying weight; qed",
);
tracing::error!(
target: "xcm::refund_surplus",
"error: HoldingWouldOverflow",
);
return Err(XcmError::HoldingWouldOverflow)
}
self.total_refunded.saturating_accrue(current_surplus);
self.holding.subsume_assets(w.into());
}
}
if !self.fees.is_empty() {
let leftover_fees = self.fees.saturating_take(Wild(All));
self.holding.subsume_assets(leftover_fees);
}
tracing::trace!(
target: "xcm::refund_surplus",
total_refunded = ?self.total_refunded,
);
Ok(())
}
fn take_fee(&mut self, fees: Assets, reason: FeeReason) -> XcmResult {
if Config::FeeManager::is_waived(self.origin_ref(), reason.clone()) {
return Ok(())
}
tracing::trace!(
target: "xcm::fees",
?fees,
origin_ref = ?self.origin_ref(),
fees_mode = ?self.fees_mode,
?reason,
"Taking fees",
);
let asset_needed_for_fees = match fees.get(0) {
Some(fee) => fee,
None => return Ok(()), };
let asset_to_pay_for_fees =
self.calculate_asset_for_delivery_fees(asset_needed_for_fees.clone());
tracing::trace!(target: "xcm::fees", ?asset_to_pay_for_fees);
let withdrawn_fee_asset: AssetsInHolding = if self.fees_mode.jit_withdraw {
let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
Config::AssetTransactor::withdraw_asset(
&asset_to_pay_for_fees,
origin,
Some(&self.context),
)?;
tracing::trace!(target: "xcm::fees", ?asset_needed_for_fees);
asset_to_pay_for_fees.clone().into()
} else {
let assets_to_pay_delivery_fees: AssetsInHolding = if self.fees.is_empty() {
self.holding
.try_take(asset_to_pay_for_fees.clone().into())
.map_err(|e| {
tracing::error!(target: "xcm::fees", ?e, ?asset_to_pay_for_fees,
"Holding doesn't hold enough for fees");
XcmError::NotHoldingFees
})?
.into()
} else {
self.fees
.try_take(asset_to_pay_for_fees.clone().into())
.map_err(|e| {
tracing::error!(target: "xcm::fees", ?e, ?asset_to_pay_for_fees,
"Fees register doesn't hold enough for fees");
XcmError::NotHoldingFees
})?
.into()
};
tracing::trace!(target: "xcm::fees", ?assets_to_pay_delivery_fees);
let mut iter = assets_to_pay_delivery_fees.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(),
withdrawn_fee_asset.clone().into(),
&asset_needed_for_fees.clone().into(),
false,
)
.map_err(|given_assets| {
tracing::error!(
target: "xcm::fees",
?given_assets, "Swap was deemed necessary but couldn't be done for withdrawn_fee_asset: {:?} and asset_needed_for_fees: {:?}", withdrawn_fee_asset.clone(), asset_needed_for_fees,
);
XcmError::FeesNotMet
})?
.into();
swapped_asset
} else {
withdrawn_fee_asset.into()
};
Config::FeeManager::handle_fee(paid, Some(&self.context), reason);
Ok(())
}
fn calculate_asset_for_delivery_fees(&self, asset_needed_for_fees: Asset) -> Asset {
let Some(asset_wanted_for_fees) =
self.fees.fungible.first_key_value().map(|(id, _)| id).or_else(|| {
self.asset_used_in_buy_execution.as_ref()
})
.filter(|&id| asset_needed_for_fees.id.ne(id))
else {
return asset_needed_for_fees
};
Config::AssetExchanger::quote_exchange_price(
&(asset_wanted_for_fees.clone(), Fungible(0)).into(),
&asset_needed_for_fees.clone().into(),
false, )
.and_then(|necessary_assets| {
necessary_assets.into_inner().into_iter().next()
})
.unwrap_or_else(|| {
tracing::trace!(
target: "xcm::calculate_asset_for_delivery_fees",
?asset_wanted_for_fees, "Could not convert fees",
);
asset_needed_for_fees
})
}
fn to_querier(
local_querier: Option<Location>,
destination: &Location,
) -> Result<Option<Location>, XcmError> {
Ok(match local_querier {
None => None,
Some(q) => Some(
q.reanchored(&destination, &Config::UniversalLocation::get()).map_err(|e| {
tracing::error!(target: "xcm::xcm_executor::to_querier", ?e, ?destination, "Failed to re-anchor local_querier");
XcmError::ReanchorFailed
})?,
),
})
}
fn respond(
&mut self,
local_querier: Option<Location>,
response: Response,
info: QueryResponseInfo,
fee_reason: FeeReason,
) -> Result<XcmHash, XcmError> {
let querier = Self::to_querier(local_querier, &info.destination)?;
let QueryResponseInfo { destination, query_id, max_weight } = info;
let instruction = QueryResponse { query_id, response, max_weight, querier };
let message = Xcm(vec![instruction]);
self.send(destination, message, fee_reason)
}
fn do_reserve_deposit_assets(
assets: AssetsInHolding,
dest: &Location,
remote_xcm: &mut Vec<Instruction<()>>,
context: Option<&XcmContext>,
) -> Result<Assets, XcmError> {
Self::deposit_assets_with_retry(&assets, dest, context)?;
let reanchored_assets = Self::reanchored(assets, dest, None);
remote_xcm.push(ReserveAssetDeposited(reanchored_assets.clone()));
Ok(reanchored_assets)
}
fn do_reserve_withdraw_assets(
assets: AssetsInHolding,
failed_bin: &mut AssetsInHolding,
reserve: &Location,
remote_xcm: &mut Vec<Instruction<()>>,
) -> Result<Assets, XcmError> {
#[cfg(not(any(test, feature = "runtime-benchmarks")))]
for asset in assets.assets_iter() {
ensure!(
Config::IsReserve::contains(&asset, &reserve),
XcmError::UntrustedReserveLocation
);
}
let reanchored_assets = Self::reanchored(assets, reserve, Some(failed_bin));
remote_xcm.push(WithdrawAsset(reanchored_assets.clone()));
Ok(reanchored_assets)
}
fn do_teleport_assets(
assets: AssetsInHolding,
dest: &Location,
remote_xcm: &mut Vec<Instruction<()>>,
context: &XcmContext,
) -> Result<Assets, XcmError> {
for asset in assets.assets_iter() {
#[cfg(not(any(test, feature = "runtime-benchmarks")))]
ensure!(
Config::IsTeleporter::contains(&asset, &dest),
XcmError::UntrustedTeleportLocation
);
Config::AssetTransactor::can_check_out(dest, &asset, context)?;
}
for asset in assets.assets_iter() {
Config::AssetTransactor::check_out(dest, &asset, context);
}
let reanchored_assets = Self::reanchored(assets, dest, None);
remote_xcm.push(ReceiveTeleportedAsset(reanchored_assets.clone()));
Ok(reanchored_assets)
}
fn try_reanchor<T: Reanchorable>(
reanchorable: T,
destination: &Location,
) -> Result<(T, InteriorLocation), XcmError> {
let reanchor_context = Config::UniversalLocation::get();
let reanchored =
reanchorable.reanchored(&destination, &reanchor_context).map_err(|error| {
tracing::error!(target: "xcm::reanchor", ?error, ?destination, ?reanchor_context, "Failed reanchoring with error.");
XcmError::ReanchorFailed
})?;
Ok((reanchored, reanchor_context))
}
fn reanchored(
mut assets: AssetsInHolding,
dest: &Location,
maybe_failed_bin: Option<&mut AssetsInHolding>,
) -> Assets {
let reanchor_context = Config::UniversalLocation::get();
assets.reanchor(dest, &reanchor_context, maybe_failed_bin);
assets.into_assets_iter().collect::<Vec<_>>().into()
}
#[cfg(any(test, feature = "runtime-benchmarks"))]
pub fn bench_process(&mut self, xcm: Xcm<Config::RuntimeCall>) -> Result<(), ExecutorError> {
self.process(xcm)
}
#[cfg(any(test, feature = "runtime-benchmarks"))]
pub fn bench_post_process(self, xcm_weight: Weight) -> Outcome {
self.post_process(xcm_weight)
}
fn process(&mut self, xcm: Xcm<Config::RuntimeCall>) -> Result<(), ExecutorError> {
tracing::trace!(
target: "xcm::process",
origin = ?self.origin_ref(),
total_surplus = ?self.total_surplus,
total_refunded = ?self.total_refunded,
error_handler_weight = ?self.error_handler_weight,
);
let mut result = Ok(());
for (i, mut instr) in xcm.0.into_iter().enumerate() {
match &mut result {
r @ Ok(()) => {
let inst_res = recursion_count::using_once(&mut 1, || {
recursion_count::with(|count| {
if *count > RECURSION_LIMIT {
return Err(XcmError::ExceedsStackLimit)
}
*count = count.saturating_add(1);
Ok(())
})
.unwrap_or(Ok(()))?;
defer! {
recursion_count::with(|count| {
*count = count.saturating_sub(1);
});
}
self.process_instruction(instr)
});
if let Err(e) = inst_res {
tracing::trace!(target: "xcm::execute", "!!! ERROR: {:?}", e);
*r = Err(ExecutorError {
index: i as u32,
xcm_error: e,
weight: Weight::zero(),
});
}
},
Err(ref mut error) =>
if let Ok(x) = Config::Weigher::instr_weight(&mut instr) {
error.weight.saturating_accrue(x)
},
}
}
result
}
fn process_instruction(
&mut self,
instr: Instruction<Config::RuntimeCall>,
) -> Result<(), XcmError> {
tracing::trace!(
target: "xcm::process_instruction",
instruction = ?instr,
"Processing instruction",
);
match instr {
WithdrawAsset(assets) => {
let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
self.ensure_can_subsume_assets(assets.len())?;
Config::TransactionalProcessor::process(|| {
for asset in assets.inner() {
Config::AssetTransactor::withdraw_asset(
asset,
origin,
Some(&self.context),
)?;
}
Ok(())
})
.and_then(|_| {
self.holding.subsume_assets(assets.into());
Ok(())
})
},
ReserveAssetDeposited(assets) => {
let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
self.ensure_can_subsume_assets(assets.len())?;
for asset in assets.inner() {
ensure!(
Config::IsReserve::contains(asset, origin),
XcmError::UntrustedReserveLocation
);
}
self.holding.subsume_assets(assets.into());
Ok(())
},
TransferAsset { assets, beneficiary } => {
Config::TransactionalProcessor::process(|| {
let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
for asset in assets.inner() {
Config::AssetTransactor::transfer_asset(
&asset,
origin,
&beneficiary,
&self.context,
)?;
}
Ok(())
})
},
TransferReserveAsset { mut assets, dest, xcm } => {
Config::TransactionalProcessor::process(|| {
let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
for asset in assets.inner() {
Config::AssetTransactor::transfer_asset(
asset,
origin,
&dest,
&self.context,
)?;
}
let reanchor_context = Config::UniversalLocation::get();
assets
.reanchor(&dest, &reanchor_context)
.map_err(|()| XcmError::LocationFull)?;
let mut message = vec![ReserveAssetDeposited(assets), ClearOrigin];
message.extend(xcm.0.into_iter());
self.send(dest, Xcm(message), FeeReason::TransferReserveAsset)?;
Ok(())
})
},
ReceiveTeleportedAsset(assets) => {
let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
self.ensure_can_subsume_assets(assets.len())?;
Config::TransactionalProcessor::process(|| {
for asset in assets.inner() {
ensure!(
Config::IsTeleporter::contains(asset, origin),
XcmError::UntrustedTeleportLocation
);
Config::AssetTransactor::can_check_in(origin, asset, &self.context)?;
Config::AssetTransactor::check_in(origin, asset, &self.context);
}
Ok(())
})
.and_then(|_| {
self.holding.subsume_assets(assets.into());
Ok(())
})
},
Transact { origin_kind, mut call } => {
let origin = self.cloned_origin().ok_or_else(|| {
tracing::trace!(
target: "xcm::process_instruction::transact",
"No origin provided",
);
XcmError::BadOrigin
})?;
let message_call = call.take_decoded().map_err(|_| {
tracing::trace!(
target: "xcm::process_instruction::transact",
"Failed to decode call",
);
XcmError::FailedToDecode
})?;
tracing::trace!(
target: "xcm::process_instruction::transact",
?call,
"Processing call",
);
if !Config::SafeCallFilter::contains(&message_call) {
tracing::trace!(
target: "xcm::process_instruction::transact",
"Call filtered by `SafeCallFilter`",
);
return Err(XcmError::NoPermission)
}
let dispatch_origin =
Config::OriginConverter::convert_origin(origin.clone(), origin_kind).map_err(
|_| {
tracing::trace!(
target: "xcm::process_instruction::transact",
?origin,
?origin_kind,
"Failed to convert origin to a local origin."
);
XcmError::BadOrigin
},
)?;
tracing::trace!(
target: "xcm::process_instruction::transact",
origin = ?dispatch_origin,
"Dispatching with origin",
);
let weight = message_call.get_dispatch_info().call_weight;
let maybe_actual_weight =
match Config::CallDispatcher::dispatch(message_call, dispatch_origin) {
Ok(post_info) => {
tracing::trace!(
target: "xcm::process_instruction::transact",
?post_info,
"Dispatch successful"
);
self.transact_status = MaybeErrorCode::Success;
post_info.actual_weight
},
Err(error_and_info) => {
tracing::trace!(
target: "xcm::process_instruction::transact",
?error_and_info,
"Dispatch failed"
);
self.transact_status = error_and_info.error.encode().into();
error_and_info.post_info.actual_weight
},
};
let actual_weight = maybe_actual_weight.unwrap_or(weight);
let surplus = weight.saturating_sub(actual_weight);
self.total_surplus.saturating_accrue(surplus);
Ok(())
},
QueryResponse { query_id, response, max_weight, querier } => {
let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
Config::ResponseHandler::on_response(
origin,
query_id,
querier.as_ref(),
response,
max_weight,
&self.context,
);
Ok(())
},
DescendOrigin(who) => self.do_descend_origin(who),
ClearOrigin => self.do_clear_origin(),
ExecuteWithOrigin { descendant_origin, xcm } => {
let previous_origin = self.context.origin.clone();
if let Some(who) = descendant_origin {
self.do_descend_origin(who)?;
} else {
self.do_clear_origin()?;
}
let result = self.process(xcm).map_err(|error| {
tracing::error!(target: "xcm::execute", ?error, actual_origin = ?self.context.origin, original_origin = ?previous_origin, "ExecuteWithOrigin inner xcm failure");
error.xcm_error
});
self.context.origin = previous_origin;
result
},
ReportError(response_info) => {
self.respond(
self.cloned_origin(),
Response::ExecutionResult(self.error),
response_info,
FeeReason::Report,
)?;
Ok(())
},
DepositAsset { assets, beneficiary } => {
let old_holding = self.holding.clone();
let result = Config::TransactionalProcessor::process(|| {
let deposited = self.holding.saturating_take(assets);
Self::deposit_assets_with_retry(&deposited, &beneficiary, Some(&self.context))
});
if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() {
self.holding = old_holding;
}
result
},
DepositReserveAsset { assets, dest, xcm } => {
let old_holding = self.holding.clone();
let result = Config::TransactionalProcessor::process(|| {
let maybe_delivery_fee_from_holding = if self.fees.is_empty() {
self.get_delivery_fee_from_holding(&assets, &dest, &xcm)?
} else {
None
};
let mut message = Vec::with_capacity(xcm.len() + 2);
let deposited = self.holding.saturating_take(assets);
tracing::trace!(target: "xcm::DepositReserveAsset", ?deposited, "Assets except delivery fee");
Self::do_reserve_deposit_assets(
deposited,
&dest,
&mut message,
Some(&self.context),
)?;
message.push(ClearOrigin);
message.extend(xcm.0.into_iter());
if let Some(delivery_fee) = maybe_delivery_fee_from_holding {
self.holding.subsume_assets(delivery_fee);
}
self.send(dest, Xcm(message), FeeReason::DepositReserveAsset)?;
Ok(())
});
if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() {
self.holding = old_holding;
}
result
},
InitiateReserveWithdraw { assets, reserve, xcm } => {
let old_holding = self.holding.clone();
let result = Config::TransactionalProcessor::process(|| {
let assets = self.holding.saturating_take(assets);
let mut message = Vec::with_capacity(xcm.len() + 2);
Self::do_reserve_withdraw_assets(
assets,
&mut self.holding,
&reserve,
&mut message,
)?;
message.push(ClearOrigin);
message.extend(xcm.0.into_iter());
self.send(reserve, Xcm(message), FeeReason::InitiateReserveWithdraw)?;
Ok(())
});
if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() {
self.holding = old_holding;
}
result
},
InitiateTeleport { assets, dest, xcm } => {
let old_holding = self.holding.clone();
let result = Config::TransactionalProcessor::process(|| {
let assets = self.holding.saturating_take(assets);
let mut message = Vec::with_capacity(xcm.len() + 2);
Self::do_teleport_assets(assets, &dest, &mut message, &self.context)?;
message.push(ClearOrigin);
message.extend(xcm.0.into_iter());
self.send(dest.clone(), Xcm(message), FeeReason::InitiateTeleport)?;
Ok(())
});
if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() {
self.holding = old_holding;
}
result
},
InitiateTransfer { destination, remote_fees, preserve_origin, assets, remote_xcm } => {
let old_holding = self.holding.clone();
let result = Config::TransactionalProcessor::process(|| {
let mut message = Vec::with_capacity(assets.len() + remote_xcm.len() + 2);
if let Some(remote_fees) = remote_fees {
let reanchored_fees = match remote_fees {
AssetTransferFilter::Teleport(fees_filter) => {
let teleport_fees = self
.holding
.try_take(fees_filter)
.map_err(|_| XcmError::NotHoldingFees)?;
Self::do_teleport_assets(
teleport_fees,
&destination,
&mut message,
&self.context,
)?
},
AssetTransferFilter::ReserveDeposit(fees_filter) => {
let reserve_deposit_fees = self
.holding
.try_take(fees_filter)
.map_err(|_| XcmError::NotHoldingFees)?;
Self::do_reserve_deposit_assets(
reserve_deposit_fees,
&destination,
&mut message,
Some(&self.context),
)?
},
AssetTransferFilter::ReserveWithdraw(fees_filter) => {
let reserve_withdraw_fees = self
.holding
.try_take(fees_filter)
.map_err(|_| XcmError::NotHoldingFees)?;
Self::do_reserve_withdraw_assets(
reserve_withdraw_fees,
&mut self.holding,
&destination,
&mut message,
)?
},
};
ensure!(reanchored_fees.len() == 1, XcmError::TooManyAssets);
let fees =
reanchored_fees.into_inner().pop().ok_or(XcmError::NotHoldingFees)?;
message.push(PayFees { asset: fees });
} else {
message
.push(UnpaidExecution { weight_limit: Unlimited, check_origin: None });
}
for asset_filter in assets {
match asset_filter {
AssetTransferFilter::Teleport(assets) => Self::do_teleport_assets(
self.holding.saturating_take(assets),
&destination,
&mut message,
&self.context,
)?,
AssetTransferFilter::ReserveDeposit(assets) =>
Self::do_reserve_deposit_assets(
self.holding.saturating_take(assets),
&destination,
&mut message,
Some(&self.context),
)?,
AssetTransferFilter::ReserveWithdraw(assets) =>
Self::do_reserve_withdraw_assets(
self.holding.saturating_take(assets),
&mut self.holding,
&destination,
&mut message,
)?,
};
}
if preserve_origin {
let original_origin = self
.origin_ref()
.cloned()
.and_then(|origin| {
Self::try_reanchor(origin, &destination)
.map(|(reanchored, _)| reanchored)
.ok()
})
.ok_or(XcmError::BadOrigin)?;
message.push(AliasOrigin(original_origin));
} else {
message.push(ClearOrigin);
}
message.extend(remote_xcm.0.into_iter());
self.send(destination, Xcm(message), FeeReason::InitiateTransfer)?;
Ok(())
});
if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() {
self.holding = old_holding;
}
result
},
ReportHolding { response_info, assets } => {
let assets =
Self::reanchored(self.holding.min(&assets), &response_info.destination, None);
self.respond(
self.cloned_origin(),
Response::Assets(assets),
response_info,
FeeReason::Report,
)?;
Ok(())
},
BuyExecution { fees, weight_limit } => {
let Some(weight) = Option::<Weight>::from(weight_limit) else { return Ok(()) };
let old_holding = self.holding.clone();
self.asset_used_in_buy_execution = Some(fees.id.clone());
tracing::trace!(
target: "xcm::executor::BuyExecution",
asset_used_in_buy_execution = ?self.asset_used_in_buy_execution
);
let max_fee =
self.holding.try_take(fees.clone().into()).map_err(|e| {
tracing::error!(target: "xcm::process_instruction::buy_execution", ?e, ?fees,
"Failed to take fees from holding");
XcmError::NotHoldingFees
})?;
let result = Config::TransactionalProcessor::process(|| {
let unspent = self.trader.buy_weight(weight, max_fee, &self.context)?;
self.holding.subsume_assets(unspent);
Ok(())
});
if result.is_err() {
self.holding = old_holding;
}
result
},
PayFees { asset } => {
if self.message_weight == Weight::zero() {
tracing::warn!(
target: "xcm::executor::PayFees",
"Message was not weighed or weight was 0. Nothing will be charged.",
);
return Ok(());
}
let old_holding = self.holding.clone();
tracing::trace!(
target: "xcm::executor::PayFees",
asset_for_fees = ?asset,
message_weight = ?self.message_weight,
);
let max_fee =
self.holding.try_take(asset.into()).map_err(|_| XcmError::NotHoldingFees)?;
let result = Config::TransactionalProcessor::process(|| {
let unspent =
self.trader.buy_weight(self.message_weight, max_fee, &self.context)?;
self.fees.subsume_assets(unspent);
Ok(())
});
if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() {
self.holding = old_holding;
}
result
},
RefundSurplus => self.refund_surplus(),
SetErrorHandler(mut handler) => {
let handler_weight = Config::Weigher::weight(&mut handler)
.map_err(|()| XcmError::WeightNotComputable)?;
self.total_surplus.saturating_accrue(self.error_handler_weight);
self.error_handler = handler;
self.error_handler_weight = handler_weight;
Ok(())
},
SetAppendix(mut appendix) => {
let appendix_weight = Config::Weigher::weight(&mut appendix)
.map_err(|()| XcmError::WeightNotComputable)?;
self.total_surplus.saturating_accrue(self.appendix_weight);
self.appendix = appendix;
self.appendix_weight = appendix_weight;
Ok(())
},
ClearError => {
self.error = None;
Ok(())
},
SetAssetClaimer { location } => {
self.asset_claimer = Some(location);
Ok(())
},
ClaimAsset { assets, ticket } => {
let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
self.ensure_can_subsume_assets(assets.len())?;
let ok = Config::AssetClaims::claim_assets(origin, &ticket, &assets, &self.context);
ensure!(ok, XcmError::UnknownClaim);
self.holding.subsume_assets(assets.into());
Ok(())
},
Trap(code) => Err(XcmError::Trap(code)),
SubscribeVersion { query_id, max_response_weight } => {
let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
ensure!(&self.original_origin == origin, XcmError::BadOrigin);
Config::SubscriptionService::start(
origin,
query_id,
max_response_weight,
&self.context,
)
},
UnsubscribeVersion => {
let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
ensure!(&self.original_origin == origin, XcmError::BadOrigin);
Config::SubscriptionService::stop(origin, &self.context)
},
BurnAsset(assets) => {
self.holding.saturating_take(assets.into());
Ok(())
},
ExpectAsset(assets) =>
self.holding.ensure_contains(&assets).map_err(|e| {
tracing::error!(target: "xcm::process_instruction::expect_asset", ?e, ?assets, "assets not contained in holding");
XcmError::ExpectationFalse
}),
ExpectOrigin(origin) => {
ensure!(self.context.origin == origin, XcmError::ExpectationFalse);
Ok(())
},
ExpectError(error) => {
ensure!(self.error == error, XcmError::ExpectationFalse);
Ok(())
},
ExpectTransactStatus(transact_status) => {
ensure!(self.transact_status == transact_status, XcmError::ExpectationFalse);
Ok(())
},
QueryPallet { module_name, response_info } => {
let pallets = Config::PalletInstancesInfo::infos()
.into_iter()
.filter(|x| x.module_name.as_bytes() == &module_name[..])
.map(|x| {
PalletInfo::new(
x.index as u32,
x.name.as_bytes().into(),
x.module_name.as_bytes().into(),
x.crate_version.major as u32,
x.crate_version.minor as u32,
x.crate_version.patch as u32,
)
})
.collect::<Result<Vec<_>, XcmError>>()?;
let QueryResponseInfo { destination, query_id, max_weight } = response_info;
let response =
Response::PalletsInfo(pallets.try_into().map_err(|_| XcmError::Overflow)?);
let querier = Self::to_querier(self.cloned_origin(), &destination)?;
let instruction = QueryResponse { query_id, response, max_weight, querier };
let message = Xcm(vec![instruction]);
self.send(destination, message, FeeReason::QueryPallet)?;
Ok(())
},
ExpectPallet { index, name, module_name, crate_major, min_crate_minor } => {
let pallet = Config::PalletInstancesInfo::infos()
.into_iter()
.find(|x| x.index == index as usize)
.ok_or(XcmError::PalletNotFound)?;
ensure!(pallet.name.as_bytes() == &name[..], XcmError::NameMismatch);
ensure!(pallet.module_name.as_bytes() == &module_name[..], XcmError::NameMismatch);
let major = pallet.crate_version.major as u32;
ensure!(major == crate_major, XcmError::VersionIncompatible);
let minor = pallet.crate_version.minor as u32;
ensure!(minor >= min_crate_minor, XcmError::VersionIncompatible);
Ok(())
},
ReportTransactStatus(response_info) => {
self.respond(
self.cloned_origin(),
Response::DispatchResult(self.transact_status.clone()),
response_info,
FeeReason::Report,
)?;
Ok(())
},
ClearTransactStatus => {
self.transact_status = Default::default();
Ok(())
},
UniversalOrigin(new_global) => {
let universal_location = Config::UniversalLocation::get();
ensure!(universal_location.first() != Some(&new_global), XcmError::InvalidLocation);
let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?;
let origin_xform = (origin, new_global);
let ok = Config::UniversalAliases::contains(&origin_xform);
ensure!(ok, XcmError::InvalidLocation);
let (_, new_global) = origin_xform;
let new_origin = Junctions::from([new_global]).relative_to(&universal_location);
self.context.origin = Some(new_origin);
Ok(())
},
ExportMessage { network, destination, xcm } => {
let origin = self.context.origin.as_ref().ok_or(XcmError::BadOrigin)?.clone();
let universal_source = Config::UniversalLocation::get()
.within_global(origin)
.map_err(|()| XcmError::Unanchored)?;
let hash = (self.origin_ref(), &destination).using_encoded(blake2_128);
let channel = u32::decode(&mut hash.as_ref()).unwrap_or(0);
let (ticket, fee) = validate_export::<Config::MessageExporter>(
network,
channel,
universal_source,
destination.clone(),
xcm,
)?;
let old_holding = self.holding.clone();
let result = Config::TransactionalProcessor::process(|| {
self.take_fee(fee, FeeReason::Export { network, destination })?;
let _ = Config::MessageExporter::deliver(ticket).defensive_proof(
"`deliver` called immediately after `validate_export`; \
`take_fee` does not affect the validity of the ticket; qed",
);
Ok(())
});
if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() {
self.holding = old_holding;
}
result
},
LockAsset { asset, unlocker } => {
let old_holding = self.holding.clone();
let result = Config::TransactionalProcessor::process(|| {
let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?;
let (remote_asset, context) = Self::try_reanchor(asset.clone(), &unlocker)?;
let lock_ticket =
Config::AssetLocker::prepare_lock(unlocker.clone(), asset, origin.clone())?;
let owner = origin.reanchored(&unlocker, &context).map_err(|e| {
tracing::error!(target: "xcm::xcm_executor::process_instruction", ?e, ?unlocker, ?context, "Failed to re-anchor origin");
XcmError::ReanchorFailed
})?;
let msg = Xcm::<()>(vec![NoteUnlockable { asset: remote_asset, owner }]);
let (ticket, price) = validate_send::<Config::XcmSender>(unlocker, msg)?;
self.take_fee(price, FeeReason::LockAsset)?;
lock_ticket.enact()?;
Config::XcmSender::deliver(ticket)?;
Ok(())
});
if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() {
self.holding = old_holding;
}
result
},
UnlockAsset { asset, target } => {
let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?;
Config::AssetLocker::prepare_unlock(origin, asset, target)?.enact()?;
Ok(())
},
NoteUnlockable { asset, owner } => {
let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?;
Config::AssetLocker::note_unlockable(origin, asset, owner)?;
Ok(())
},
RequestUnlock { asset, locker } => {
let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?;
let remote_asset = Self::try_reanchor(asset.clone(), &locker)?.0;
let remote_target = Self::try_reanchor(origin.clone(), &locker)?.0;
let reduce_ticket = Config::AssetLocker::prepare_reduce_unlockable(
locker.clone(),
asset,
origin.clone(),
)?;
let msg =
Xcm::<()>(vec![UnlockAsset { asset: remote_asset, target: remote_target }]);
let (ticket, price) = validate_send::<Config::XcmSender>(locker, msg)?;
let old_holding = self.holding.clone();
let result = Config::TransactionalProcessor::process(|| {
self.take_fee(price, FeeReason::RequestUnlock)?;
reduce_ticket.enact()?;
Config::XcmSender::deliver(ticket)?;
Ok(())
});
if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() {
self.holding = old_holding;
}
result
},
ExchangeAsset { give, want, maximal } => {
let old_holding = self.holding.clone();
let give = self.holding.saturating_take(give);
let result = Config::TransactionalProcessor::process(|| {
self.ensure_can_subsume_assets(want.len())?;
let exchange_result = Config::AssetExchanger::exchange_asset(
self.origin_ref(),
give,
&want,
maximal,
);
if let Ok(received) = exchange_result {
self.holding.subsume_assets(received.into());
Ok(())
} else {
Err(XcmError::NoDeal)
}
});
if result.is_err() {
self.holding = old_holding;
}
result
},
SetFeesMode { jit_withdraw } => {
self.fees_mode = FeesMode { jit_withdraw };
Ok(())
},
SetTopic(topic) => {
self.context.topic = Some(topic);
Ok(())
},
ClearTopic => {
self.context.topic = None;
Ok(())
},
AliasOrigin(target) => {
let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
if Config::Aliasers::contains(origin, &target) {
self.context.origin = Some(target);
Ok(())
} else {
Err(XcmError::NoPermission)
}
},
UnpaidExecution { check_origin, .. } => {
ensure!(
check_origin.is_none() || self.context.origin == check_origin,
XcmError::BadOrigin
);
Ok(())
},
HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } =>
Config::TransactionalProcessor::process(|| {
Config::HrmpNewChannelOpenRequestHandler::handle(
sender,
max_message_size,
max_capacity,
)
}),
HrmpChannelAccepted { recipient } => Config::TransactionalProcessor::process(|| {
Config::HrmpChannelAcceptedHandler::handle(recipient)
}),
HrmpChannelClosing { initiator, sender, recipient } =>
Config::TransactionalProcessor::process(|| {
Config::HrmpChannelClosingHandler::handle(initiator, sender, recipient)
}),
}
}
fn do_descend_origin(&mut self, who: InteriorLocation) -> XcmResult {
self.context
.origin
.as_mut()
.ok_or(XcmError::BadOrigin)?
.append_with(who)
.map_err(|e| {
tracing::error!(target: "xcm::do_descend_origin", ?e, "Failed to append junctions");
XcmError::LocationFull
})
}
fn do_clear_origin(&mut self) -> XcmResult {
self.context.origin = None;
Ok(())
}
fn deposit_assets_with_retry(
to_deposit: &AssetsInHolding,
beneficiary: &Location,
context: Option<&XcmContext>,
) -> Result<(), XcmError> {
let mut failed_deposits = Vec::with_capacity(to_deposit.len());
let mut deposit_result = Ok(());
for asset in to_deposit.assets_iter() {
deposit_result = Config::AssetTransactor::deposit_asset(&asset, &beneficiary, context);
if deposit_result.is_err() {
failed_deposits.push(asset);
}
}
if failed_deposits.len() == to_deposit.len() {
tracing::debug!(
target: "xcm::execute",
?deposit_result,
"Deposit for each asset failed, returning the last error as there is no point in retrying any of them",
);
return deposit_result;
}
tracing::trace!(target: "xcm::execute", ?failed_deposits, "Deposits to retry");
for asset in failed_deposits {
Config::AssetTransactor::deposit_asset(&asset, &beneficiary, context)?;
}
Ok(())
}
fn get_delivery_fee_from_holding(
&mut self,
assets: &AssetFilter,
destination: &Location,
xcm: &Xcm<()>,
) -> Result<Option<AssetsInHolding>, XcmError> {
let to_weigh = self.holding.saturating_take(assets.clone());
self.holding.subsume_assets(to_weigh.clone());
let to_weigh_reanchored = Self::reanchored(to_weigh, &destination, None);
let mut message_to_weigh = vec![ReserveAssetDeposited(to_weigh_reanchored), ClearOrigin];
message_to_weigh.extend(xcm.0.clone().into_iter());
let (_, fee) =
validate_send::<Config::XcmSender>(destination.clone(), Xcm(message_to_weigh))?;
let maybe_delivery_fee = fee.get(0).map(|asset_needed_for_fees| {
tracing::trace!(
target: "xcm::fees::DepositReserveAsset",
"Asset provided to pay for fees {:?}, asset required for delivery fees: {:?}",
self.asset_used_in_buy_execution, asset_needed_for_fees,
);
let asset_to_pay_for_fees =
self.calculate_asset_for_delivery_fees(asset_needed_for_fees.clone());
let delivery_fee = self.holding.saturating_take(asset_to_pay_for_fees.into());
tracing::trace!(target: "xcm::fees::DepositReserveAsset", ?delivery_fee);
delivery_fee
});
Ok(maybe_delivery_fee)
}
}