Files
pezkuwi-sdk/pezkuwi/xcm/xcm-executor/src/lib.rs
T
pezkuwichain 4c8f281051 style: Migrate to stable-only rustfmt configuration
- Remove nightly-only features from .rustfmt.toml and vendor/ss58-registry/rustfmt.toml
- Removed features: imports_granularity, wrap_comments, comment_width,
  reorder_impl_items, spaces_around_ranges, binop_separator,
  match_arm_blocks, trailing_semicolon, trailing_comma
- Format all 898 affected files with stable rustfmt
- Ensures long-term reliability without nightly toolchain dependency
2025-12-23 09:37:11 +03:00

1949 lines
65 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
#![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 pezframe_support::{
dispatch::GetDispatchInfo,
ensure,
traits::{Contains, ContainsPair, Defensive, Get, PalletsInfoAccess},
};
use pezsp_core::defer;
use pezsp_io::hashing::blake2_128;
use pezsp_weights::Weight;
use xcm::latest::{prelude::*, AssetTransferFilter};
pub mod traits;
use traits::{
validate_export, AssetExchange, AssetLock, CallDispatcher, ClaimAssets, ConvertOrigin,
DropAssets, Enact, EventEmitter, 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;
/// A struct to specify how fees are being paid.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct FeesMode {
/// If true, then the fee assets are taken directly from the origin's on-chain account,
/// otherwise the fee assets are taken from the holding register.
///
/// Defaults to false.
pub jit_withdraw: bool,
}
/// The maximum recursion depth allowed when executing nested XCM instructions.
///
/// Exceeding this limit results in `XcmError::ExceedsStackLimit` or
/// `ProcessMessageError::StackLimitReached`.
///
/// Also used in the `DenyRecursively` barrier.
pub const RECURSION_LIMIT: u8 = 10;
environmental::environmental!(recursion_count: u8);
/// The XCM executor.
pub struct XcmExecutor<Config: config::Config> {
holding: AssetsInHolding,
holding_limit: usize,
context: XcmContext,
original_origin: Location,
trader: Config::Trader,
/// The most recent error result and instruction index into the fragment in which it occurred,
/// if any.
error: Option<(u32, XcmError)>,
/// The surplus weight, defined as the amount by which `max_weight` is
/// an over-estimate of the actual weight consumed. We do it this way to avoid needing the
/// execution engine to keep track of all instructions' weights (it only needs to care about
/// the weight of dynamically determined instructions such as `Transact`).
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 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_in_buy_execution: Option<AssetId>,
/// Stores the current message's weight.
message_weight: Weight,
asset_claimer: Option<Location>,
already_paid_fees: bool,
_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 fn already_paid_fees(&self) -> bool {
self.already_paid_fees
}
}
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>,
weight_limit: Weight,
) -> Result<Self::Prepared, InstructionError> {
match Config::Weigher::weight(&mut message, weight_limit) {
Ok(weight) => Ok(WeighedMessage(weight, message)),
Err(error) => {
tracing::debug!(
target: "xcm::prepare",
?error,
?message,
"Failed to calculate weight for XCM message; execution aborted"
);
Err(error)
},
}
}
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,
?id,
?weight_credit,
"Executing message",
);
let mut properties = Properties { weight_credit, message_id: None };
// We only want to record under certain conditions (mainly only during dry-running),
// so as to not degrade regular performance.
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::Incomplete {
used: xcm_weight, // Weight consumed before the error
error: InstructionError { index: 0, error: XcmError::Barrier }, // The error that occurred
};
}
*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 pezframe_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,
already_paid_fees: false,
_config: PhantomData,
}
}
/// Execute any final operations after having executed the XCM message.
/// This includes refunding surplus weight, trapping extra holding funds, and returning any
/// errors during execution.
pub fn post_process(mut self, xcm_weight: Weight) -> Outcome {
// We silently drop any error from our attempt to refund the surplus as it's a charitable
// thing so best-effort is all we will do.
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 },
// TODO: #2841 #REALWEIGHT We should deduct the cost of any instructions following
// the error which didn't end up being executed.
Some((index, error)) => {
tracing::trace!(
target: "xcm::post_process",
instruction = ?index,
?error,
original_origin = ?self.original_origin,
"Execution failed",
);
Outcome::Incomplete {
used: weight_used,
error: InstructionError { index: index.try_into().unwrap_or(u8::MAX), error },
}
},
}
}
fn origin_ref(&self) -> Option<&Location> {
self.context.origin.as_ref()
}
fn cloned_origin(&self) -> Option<Location> {
self.context.origin.clone()
}
/// Send an XCM, charging fees from Holding as needed.
fn send(
&mut self,
dest: Location,
msg: Xcm<()>,
reason: FeeReason,
) -> Result<XcmHash, XcmError> {
let mut msg = msg;
// Only the last `SetTopic` instruction is considered relevant. If the message does not end
// with it, a `topic_or_message_id()` from the context is appended to it. This behaviour is
// then consistent with `WithUniqueTopic`.
if !matches!(msg.last(), Some(SetTopic(_))) {
let topic_id = self.context.topic_or_message_id();
msg.0.push(SetTopic(topic_id.into()));
}
tracing::trace!(
target: "xcm::send",
?msg,
destination = ?dest,
reason = ?reason,
"Sending msg",
);
let (ticket, fee) = validate_send::<Config::XcmSender>(dest.clone(), msg)?;
self.take_fee(fee, reason)?;
match Config::XcmSender::deliver(ticket) {
Ok(message_id) => {
Config::XcmEventEmitter::emit_sent_event(
self.original_origin.clone(),
dest,
None, /* Avoid logging the full XCM message to prevent inconsistencies and
* reduce storage usage. */
message_id,
);
Ok(message_id)
},
Err(error) => {
tracing::debug!(target: "xcm::send", ?error, "XCM failed to deliver with error");
Config::XcmEventEmitter::emit_send_failure_event(
self.original_origin.clone(),
dest,
error.clone(),
self.context.topic_or_message_id(),
);
Err(error.into())
},
}
}
/// Remove the registered error handler and return it. Do not refund its weight.
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
}
/// Drop the registered error handler and refund its weight.
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();
}
/// Remove the registered appendix and return it.
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> {
// worst-case, holding.len becomes 2 * holding_limit.
// this guarantees that if holding.len() == holding_limit and you have more than
// `holding_limit` items (which has a best case outcome of holding.len() == holding_limit),
// then the operation is guaranteed to succeed.
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(())
}
/// Refund any unused weight.
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 there are any leftover `fees`, merge them with `holding`.
if !self.fees.is_empty() {
let leftover_fees = self.fees.saturating_take(Wild(All));
tracing::trace!(
target: "xcm::refund_surplus",
?leftover_fees,
);
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",
);
// We only ever use the first asset from `fees`.
let asset_needed_for_fees = match fees.get(0) {
Some(fee) => fee,
None => return Ok(()), // No delivery fees need to be paid.
};
// If `BuyExecution` or `PayFees` was called, we use that asset for delivery fees as well.
let asset_to_pay_for_fees =
self.calculate_asset_for_delivery_fees(asset_needed_for_fees.clone());
tracing::trace!(target: "xcm::fees", ?asset_to_pay_for_fees);
// We withdraw or take from holding the asset the user wants to use for fee payment.
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 {
// This condition exists to support `BuyExecution` while the ecosystem
// transitions to `PayFees`.
let assets_to_pay_delivery_fees: AssetsInHolding = if self.fees.is_empty() {
// Means `BuyExecution` was used, we'll find the fees in the `holding` register.
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 {
// Means `PayFees` was used, we'll find the fees in the `fees` register.
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()
};
// We perform the swap, if needed, to pay fees.
let paid = if asset_to_pay_for_fees.id != asset_needed_for_fees.id {
let swapped_asset: Assets = Config::AssetExchanger::exchange_asset(
self.origin_ref(),
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 {
// 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(())
}
/// Calculates the amount of asset used in `PayFees` or `BuyExecution` that would be
/// charged for swapping to `asset_needed_for_fees`.
///
/// The calculation is done by `Config::AssetExchanger`.
/// If neither `PayFees` or `BuyExecution` were used, or no swap is required,
/// it will just return `asset_needed_for_fees`.
fn calculate_asset_for_delivery_fees(&self, asset_needed_for_fees: Asset) -> Asset {
let Some(asset_wanted_for_fees) =
// we try to swap first asset in the fees register (should only ever be one),
self.fees.fungible.first_key_value().map(|(id, _)| id).or_else(|| {
// or the one used in BuyExecution
self.asset_used_in_buy_execution.as_ref()
})
// if it is different than what we need
.filter(|&id| asset_needed_for_fees.id.ne(id))
else {
// either nothing to swap or we're already holding the right asset
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, // Minimal.
)
.and_then(|necessary_assets| {
// We only use the first asset for fees.
// If this is not enough to swap for the fee asset then it will error later down
// the line.
necessary_assets.into_inner().into_iter().next()
})
.unwrap_or_else(|| {
// If we can't convert, then we return the original asset.
// It will error later in any case.
tracing::trace!(
target: "xcm::calculate_asset_for_delivery_fees",
?asset_wanted_for_fees, "Could not convert fees",
);
asset_needed_for_fees
})
}
/// Calculates what `local_querier` would be from the perspective of `destination`.
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
})?,
),
})
}
/// Send a bare `QueryResponse` message containing `response` informed by the given `info`.
///
/// The `local_querier` argument is the querier (if any) specified from the *local* perspective.
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)?;
// Note that we pass `None` as `maybe_failed_bin` and drop any assets which
// cannot be reanchored, because we have already called `deposit_asset` on
// all assets.
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> {
// Must ensure that we recognise the assets as being managed by the destination.
#[cfg(not(any(test, feature = "runtime-benchmarks")))]
for asset in assets.assets_iter() {
ensure!(
Config::IsReserve::contains(&asset, &reserve),
XcmError::UntrustedReserveLocation
);
}
// Note that here we are able to place any assets which could not be
// reanchored back into Holding.
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() {
// Must ensure that we have teleport trust with destination for these assets.
#[cfg(not(any(test, feature = "runtime-benchmarks")))]
ensure!(
Config::IsTeleporter::contains(&asset, &dest),
XcmError::UntrustedTeleportLocation
);
// We should check that the asset can actually be teleported out (for
// this to be in error, there would need to be an accounting violation
// by ourselves, so it's unlikely, but we don't want to allow that kind
// of bug to leak into a trusted chain.
Config::AssetTransactor::can_check_out(dest, &asset, context)?;
}
for asset in assets.assets_iter() {
Config::AssetTransactor::check_out(dest, &asset, context);
}
// Note that we pass `None` as `maybe_failed_bin` and drop any assets which
// cannot be reanchored, because we have already checked all assets out.
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))
}
/// NOTE: Any assets which were unable to be reanchored are introduced into `failed_bin`.
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(()) => {
// Initialize the recursion count only the first time we hit this code in our
// potential recursive execution.
let inst_res = recursion_count::using_once(&mut 1, || {
recursion_count::with(|count| {
if *count > RECURSION_LIMIT {
return None;
}
*count = count.saturating_add(1);
Some(())
})
.flatten()
.ok_or(XcmError::ExceedsStackLimit)?;
// Ensure that we always decrement the counter whenever we finish processing
// the instruction.
defer! {
recursion_count::with(|count| {
*count = count.saturating_sub(1);
});
}
self.process_instruction(instr)
});
if let Err(error) = inst_res {
tracing::debug!(
target: "xcm::process",
?error, "XCM execution failed at instruction index={i}"
);
Config::XcmEventEmitter::emit_process_failure_event(
self.original_origin.clone(),
error,
self.context.topic_or_message_id(),
);
*r = Err(ExecutorError {
index: i as u32,
xcm_error: error,
weight: Weight::zero(),
});
}
},
Err(ref mut error) => {
if let Ok(x) = Config::Weigher::instr_weight(&mut instr) {
error.weight.saturating_accrue(x)
}
},
}
}
result
}
/// Process a single XCM instruction, mutating the state of the XCM virtual machine.
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())?;
let mut total_surplus = Weight::zero();
Config::TransactionalProcessor::process(|| {
// Take `assets` from the origin account (on-chain)...
for asset in assets.inner() {
let (_, surplus) = Config::AssetTransactor::withdraw_asset_with_surplus(
asset,
origin,
Some(&self.context),
)?;
// If we have some surplus, aggregate it.
total_surplus.saturating_accrue(surplus);
}
Ok(())
})
.and_then(|_| {
// ...and place into holding.
self.holding.subsume_assets(assets.into());
// Credit the total surplus.
self.total_surplus.saturating_accrue(total_surplus);
Ok(())
})
},
ReserveAssetDeposited(assets) => {
// check whether we trust origin to be our reserve location for this asset.
let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
self.ensure_can_subsume_assets(assets.len())?;
for asset in assets.inner() {
// Must ensure that we recognise the asset as being managed by the origin.
ensure!(
Config::IsReserve::contains(asset, origin),
XcmError::UntrustedReserveLocation
);
}
self.holding.subsume_assets(assets.into());
Ok(())
},
TransferAsset { assets, beneficiary } => {
Config::TransactionalProcessor::process(|| {
// Take `assets` from the origin account (on-chain) and place into dest account.
let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
let mut total_surplus = Weight::zero();
for asset in assets.inner() {
let (_, surplus) = Config::AssetTransactor::transfer_asset_with_surplus(
&asset,
origin,
&beneficiary,
&self.context,
)?;
// If we have some surplus, aggregate it.
total_surplus.saturating_accrue(surplus);
}
// Credit the total surplus.
self.total_surplus.saturating_accrue(total_surplus);
Ok(())
})
},
TransferReserveAsset { mut assets, dest, xcm } => {
Config::TransactionalProcessor::process(|| {
let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
let mut total_surplus = Weight::zero();
// Take `assets` from the origin account (on-chain) and place into dest account.
for asset in assets.inner() {
let (_, surplus) = Config::AssetTransactor::transfer_asset_with_surplus(
asset,
origin,
&dest,
&self.context,
)?;
// If we have some surplus, aggregate it.
total_surplus.saturating_accrue(surplus);
}
let reanchor_context = Config::UniversalLocation::get();
assets
.reanchor(&dest, &reanchor_context)
.map_err(|()| {
tracing::debug!(
target: "xcm::process_instruction::transfer_reserve_asset",
?assets,
?dest,
?reanchor_context,
"Failed to reanchor assets to destination in context"
);
XcmError::LocationFull
})?;
let mut message = vec![ReserveAssetDeposited(assets), ClearOrigin];
message.extend(xcm.0.into_iter());
self.send(dest, Xcm(message), FeeReason::TransferReserveAsset)?;
// Credit the total surplus.
self.total_surplus.saturating_accrue(total_surplus);
Ok(())
})
},
ReceiveTeleportedAsset(assets) => {
let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
self.ensure_can_subsume_assets(assets.len())?;
Config::TransactionalProcessor::process(|| {
// check whether we trust origin to teleport this asset to us via config trait.
for asset in assets.inner() {
// We only trust the origin to send us assets that they identify as their
// sovereign assets.
ensure!(
Config::IsTeleporter::contains(asset, origin),
XcmError::UntrustedTeleportLocation
);
// We should check that the asset can actually be teleported in (for this to
// be in error, there would need to be an accounting violation by one of the
// trusted chains, so it's unlikely, but we don't want to punish a possibly
// innocent chain/user).
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(())
})
},
// `fallback_max_weight` is not used in the executor, it's only for conversions.
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,
call = ?message_call,
"Dispatching call 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);
// If the actual weight of the call was less than the specified weight, we credit it.
//
// We make the adjustment for the total surplus, which is used eventually
// reported back to the caller and this ensures that they account for the total
// weight consumed correctly (potentially allowing them to do more operations in a
// block than they otherwise would).
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 { .. } => Err(XcmError::Unimplemented),
ReportError(response_info) => {
// Report the given result by sending a QueryResponse XCM to a previously given
// outcome destination if one was registered.
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);
let surplus = Self::deposit_assets_with_retry(&deposited, &beneficiary, Some(&self.context))?;
self.total_surplus.saturating_accrue(surplus);
Ok(())
});
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 mut assets = self.holding.saturating_take(assets);
// When not using `PayFees`, nor `JIT_WITHDRAW`, delivery fees are paid from
// transferred assets.
let maybe_delivery_fee_from_assets = if self.fees.is_empty() && !self.fees_mode.jit_withdraw {
// Deduct and return the part of `assets` that shall be used for delivery fees.
self.take_delivery_fee_from_assets(&mut assets, &dest, FeeReason::DepositReserveAsset, &xcm)?
} else {
None
};
let mut message = Vec::with_capacity(xcm.len() + 2);
tracing::trace!(target: "xcm::DepositReserveAsset", ?assets, "Assets except delivery fee");
Self::do_reserve_deposit_assets(
assets,
&dest,
&mut message,
Some(&self.context),
)?;
// clear origin for subsequent custom instructions
message.push(ClearOrigin);
// append custom instructions
message.extend(xcm.0.into_iter());
if let Some(delivery_fee) = maybe_delivery_fee_from_assets {
// Put back delivery_fee in holding register to be charged by XcmSender.
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 mut assets = self.holding.saturating_take(assets);
// When not using `PayFees`, nor `JIT_WITHDRAW`, delivery fees are paid from
// transferred assets.
let maybe_delivery_fee_from_assets = if self.fees.is_empty() && !self.fees_mode.jit_withdraw {
// Deduct and return the part of `assets` that shall be used for delivery fees.
self.take_delivery_fee_from_assets(&mut assets, &reserve, FeeReason::InitiateReserveWithdraw, &xcm)?
} else {
None
};
let mut message = Vec::with_capacity(xcm.len() + 2);
Self::do_reserve_withdraw_assets(
assets,
&mut self.holding,
&reserve,
&mut message,
)?;
// clear origin for subsequent custom instructions
message.push(ClearOrigin);
// append custom instructions
message.extend(xcm.0.into_iter());
if let Some(delivery_fee) = maybe_delivery_fee_from_assets {
// Put back delivery_fee in holding register to be charged by XcmSender.
self.holding.subsume_assets(delivery_fee);
}
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 mut assets = self.holding.saturating_take(assets);
// When not using `PayFees`, nor `JIT_WITHDRAW`, delivery fees are paid from
// transferred assets.
let maybe_delivery_fee_from_assets = if self.fees.is_empty() && !self.fees_mode.jit_withdraw {
// Deduct and return the part of `assets` that shall be used for delivery fees.
self.take_delivery_fee_from_assets(&mut assets, &dest, FeeReason::InitiateTeleport, &xcm)?
} else {
None
};
let mut message = Vec::with_capacity(xcm.len() + 2);
Self::do_teleport_assets(assets, &dest, &mut message, &self.context)?;
// clear origin for subsequent custom instructions
message.push(ClearOrigin);
// append custom instructions
message.extend(xcm.0.into_iter());
if let Some(delivery_fee) = maybe_delivery_fee_from_assets {
// Put back delivery_fee in holding register to be charged by XcmSender.
self.holding.subsume_assets(delivery_fee);
}
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);
// We need to transfer the fees and buy execution on remote chain _BEFORE_
// transferring the other assets. This is required to satisfy the
// `MAX_ASSETS_FOR_BUY_EXECUTION` limit in the `AllowTopLevelPaidExecutionFrom`
// barrier.
let remote_fees_paid = 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(|error| {
tracing::debug!(
target: "xcm::process_instruction::initiate_transfer", ?error,
"Failed to take specified teleport fees from holding"
);
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(|error| {
tracing::debug!(
target: "xcm::process_instruction::initiate_transfer", ?error,
"Failed to take specified reserve deposit fees from holding"
);
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(|error| {
tracing::debug!(
target: "xcm::process_instruction::initiate_transfer", ?error,
"Failed to take specified reserve withdraw fees from holding"
);
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)?;
// move these assets to the fees register for covering execution and paying
// any subsequent fees
message.push(PayFees { asset: fees });
true
} else {
false
};
// add any extra asset transfers
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 {
// We alias the origin if it's not a noop (origin != `Here`).
if let Some(original_origin) = self
.origin_ref()
.filter(|origin| *origin != &Location::here())
.cloned()
{
// preserve current origin for subsequent user-controlled instructions on
// remote chain
let reanchored_origin = Self::try_reanchor(original_origin, &destination)?.0;
message.push(AliasOrigin(reanchored_origin));
}
} else {
// clear origin for subsequent user-controlled instructions on remote chain
message.push(ClearOrigin);
}
// If not intending to pay for fees then we append the `UnpaidExecution`
// _AFTER_ origin altering instructions.
// When origin is not preserved, it's probably going to fail on the receiver.
if !remote_fees_paid {
// We push the UnpaidExecution instruction to notify we do not intend to pay
// for fees.
// The receiving chain must decide based on the origin of the message if they
// accept this.
message
.push(UnpaidExecution { weight_limit: Unlimited, check_origin: None });
}
// append custom instructions
message.extend(remote_xcm.0.into_iter());
// send the onward XCM
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 } => {
// Note that we pass `None` as `maybe_failed_bin` since no assets were ever removed
// from Holding.
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 } => {
// There is no need to buy any weight if `weight_limit` is `Unlimited` since it
// would indicate that `AllowTopLevelPaidExecutionFrom` was unused for execution
// and thus there is some other reason why it has been determined that this XCM
// should be executed.
let Some(weight) = Option::<Weight>::from(weight_limit) else { return Ok(()) };
let old_holding = self.holding.clone();
// Save the asset being used for execution fees, so we later know what should be
// used for delivery fees.
self.asset_used_in_buy_execution = Some(fees.id.clone());
tracing::trace!(
target: "xcm::executor::BuyExecution",
asset_used_in_buy_execution = ?self.asset_used_in_buy_execution
);
// pay for `weight` using up to `fees` of the holding register.
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 we've already paid for fees, do nothing.
if self.already_paid_fees {
return Ok(());
}
// Make sure `PayFees` won't be processed again.
self.already_paid_fees = true;
// Record old holding in case we need to rollback.
let old_holding = self.holding.clone();
// The max we're willing to pay for fees is decided by the `asset` operand.
tracing::trace!(
target: "xcm::executor::PayFees",
asset_for_fees = ?asset,
message_weight = ?self.message_weight,
);
// Pay for execution fees.
let result = Config::TransactionalProcessor::process(|| {
let max_fee =
self.holding.try_take(asset.into()).map_err(|error| {
tracing::debug!(
target: "xcm::process_instruction::pay_fees", ?error,
"Failed to take fees from holding"
);
XcmError::NotHoldingFees
})?;
let unspent =
self.trader.buy_weight(self.message_weight, max_fee, &self.context)?;
// Move unspent to the `fees` register, it can later be moved to holding
// by calling `RefundSurplus`.
self.fees.subsume_assets(unspent);
Ok(())
});
if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() {
// Rollback on error.
self.holding = old_holding;
self.already_paid_fees = false;
}
result
},
RefundSurplus => self.refund_surplus(),
SetErrorHandler(mut handler) => {
let handler_weight = Config::Weigher::weight(&mut handler, Weight::MAX)
.map_err(|error| {
tracing::debug!(
target: "xcm::executor::SetErrorHandler",
?error,
?handler,
"Failed to calculate weight"
);
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, Weight::MAX)
.map_err(|error| {
tracing::debug!(
target: "xcm::executor::SetErrorHandler",
?error,
?appendix,
"Failed to calculate weight"
);
XcmError::WeightNotComputable
})?;
self.total_surplus.saturating_accrue(self.appendix_weight);
self.appendix = appendix;
self.appendix_weight = appendix_weight;
Ok(())
},
ClearError => {
self.error = None;
Ok(())
},
SetHints { hints } => {
for hint in hints.into_iter() {
match hint {
AssetClaimer { 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)?;
// We don't allow derivative origins to subscribe since it would otherwise pose a
// DoS risk.
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(|error| {
tracing::debug!(
target: "xcm::process_instruction::query_pallet", ?error,
"Failed to convert pallets to response info"
);
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 pezpallet = Config::PalletInstancesInfo::infos()
.into_iter()
.find(|x| x.index == index as usize)
.ok_or(XcmError::PalletNotFound)?;
ensure!(pezpallet.name.as_bytes() == &name[..], XcmError::NameMismatch);
ensure!(pezpallet.module_name.as_bytes() == &module_name[..], XcmError::NameMismatch);
let major = pezpallet.crate_version.major as u32;
ensure!(major == crate_major, XcmError::VersionIncompatible);
let minor = pezpallet.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 } => {
// The actual message sent to the bridge for forwarding is prepended with
// `UniversalOrigin` and `DescendOrigin` in order to ensure that the message is
// executed with this Origin.
//
// Prepend the desired message with instructions which effectively rewrite the
// origin.
//
// This only works because the remote chain empowers the bridge
// to speak for the local network.
let origin = self.context.origin.as_ref().ok_or(XcmError::BadOrigin)?.clone();
let universal_source = Config::UniversalLocation::get()
.within_global(origin)
.map_err(|()| {
tracing::debug!(
target: "xcm::process_instruction::export_message",
"Failed to reanchor origin to universal location",
);
XcmError::Unanchored
})?;
let hash = (self.origin_ref(), &destination).using_encoded(blake2_128);
let channel = u32::decode(&mut hash.as_ref()).unwrap_or(0);
// Hash identifies the lane on the exporter which we use. We use the pairwise
// combination of the origin and destination to ensure origin/destination pairs
// will generally have their own lanes.
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(())
}
/// Deposit `to_deposit` assets to `beneficiary`, without giving up on the first (transient)
/// error, and retrying once just in case one of the subsequently deposited assets satisfy some
/// requirement.
///
/// Most common transient error is: `beneficiary` account does not yet exist and the first
/// asset(s) in the (sorted) list does not satisfy ED, but a subsequent one in the list does.
///
/// Deposits also proceed without aborting on “below minimum” (dust) errors. This ensures
/// that a batch of assets containing some legitimately depositable amounts will succeed
/// even if some “dust” deposits fall below the chains configured minimum balance.
///
/// This function can write into storage and also return an error at the same time, it should
/// always be called within a transactional context.
fn deposit_assets_with_retry(
to_deposit: &AssetsInHolding,
beneficiary: &Location,
context: Option<&XcmContext>,
) -> Result<Weight, XcmError> {
let mut total_surplus = Weight::zero();
let mut failed_deposits = Vec::with_capacity(to_deposit.len());
for asset in to_deposit.assets_iter() {
match Config::AssetTransactor::deposit_asset_with_surplus(&asset, &beneficiary, context)
{
Ok(surplus) => {
total_surplus.saturating_accrue(surplus);
},
Err(_) => {
// if deposit failed for asset, mark it for retry.
failed_deposits.push(asset);
},
}
}
tracing::trace!(
target: "xcm::deposit_assets_with_retry",
?failed_deposits,
"Firstpass failures, about to retry"
);
// retry previously failed deposits, this time short-circuiting on any error.
for asset in failed_deposits {
match Config::AssetTransactor::deposit_asset_with_surplus(&asset, &beneficiary, context)
{
Ok(surplus) => {
total_surplus.saturating_accrue(surplus);
},
Err(error) => {
// Ignore dust deposit errors.
if !matches!(
error,
XcmError::FailedToTransactAsset(string)
if *string == *<&'static str>::from(pezsp_runtime::TokenError::BelowMinimum)
) {
return Err(error);
}
},
};
}
Ok(total_surplus)
}
/// Take from transferred `assets` the delivery fee required to send an onward transfer message
/// to `destination`.
///
/// Will be removed once the transition from `BuyExecution` to `PayFees` is complete.
fn take_delivery_fee_from_assets(
&self,
assets: &mut AssetsInHolding,
destination: &Location,
reason: FeeReason,
xcm: &Xcm<()>,
) -> Result<Option<AssetsInHolding>, XcmError> {
let to_weigh = assets.clone();
let to_weigh_reanchored = Self::reanchored(to_weigh, &destination, None);
let remote_instruction = match reason {
FeeReason::DepositReserveAsset => ReserveAssetDeposited(to_weigh_reanchored),
FeeReason::InitiateReserveWithdraw => WithdrawAsset(to_weigh_reanchored),
FeeReason::InitiateTeleport => ReceiveTeleportedAsset(to_weigh_reanchored),
_ => {
tracing::debug!(
target: "xcm::take_delivery_fee_from_assets",
"Unexpected delivery fee reason",
);
return Err(XcmError::NotHoldingFees);
},
};
let mut message_to_weigh = Vec::with_capacity(xcm.len() + 2);
message_to_weigh.push(remote_instruction);
message_to_weigh.push(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::take_delivery_fee_from_assets",
"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());
// set aside fee to be charged by XcmSender
let delivery_fee = assets.saturating_take(asset_to_pay_for_fees.into());
tracing::trace!(target: "xcm::fees::take_delivery_fee_from_assets", ?delivery_fee);
delivery_fee
});
Ok(maybe_delivery_fee)
}
}