// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see .
#![cfg_attr(not(feature = "std"), no_std)]
use frame_support::{
dispatch::GetDispatchInfo,
ensure,
traits::{Contains, ContainsPair, Get, PalletsInfoAccess},
};
use parity_scale_codec::{Decode, Encode};
use sp_core::defer;
use sp_io::hashing::blake2_128;
use sp_std::{fmt::Debug, marker::PhantomData, prelude::*};
use sp_weights::Weight;
use xcm::latest::prelude::*;
pub mod traits;
use traits::{
validate_export, AssetExchange, AssetLock, CallDispatcher, ClaimAssets, ConvertOrigin,
DropAssets, Enact, ExportXcm, FeeManager, FeeReason, OnResponse, Properties, ShouldExecute,
TransactAsset, VersionChangeNotifier, WeightBounds, WeightTrader, XcmAssetTransfers,
};
mod assets;
pub use assets::AssetsInHolding;
mod config;
pub use config::Config;
/// 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,
}
const RECURSION_LIMIT: u8 = 10;
environmental::environmental!(recursion_count: u8);
/// The XCM executor.
pub struct XcmExecutor {
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,
error_handler_weight: Weight,
appendix: Xcm,
appendix_weight: Weight,
transact_status: MaybeErrorCode,
fees_mode: FeesMode,
_config: PhantomData,
}
#[cfg(feature = "runtime-benchmarks")]
impl XcmExecutor {
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 {
&self.context.origin
}
pub fn set_origin(&mut self, v: Option) {
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 {
&self.error_handler
}
pub fn set_error_handler(&mut self, v: Xcm) {
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 {
&self.appendix
}
pub fn set_appendix(&mut self, v: Xcm) {
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 topic(&self) -> &Option<[u8; 32]> {
&self.context.topic
}
pub fn set_topic(&mut self, v: Option<[u8; 32]>) {
self.context.topic = v;
}
}
pub struct WeighedMessage(Weight, Xcm);
impl PreparedMessage for WeighedMessage {
fn weight_of(&self) -> Weight {
self.0
}
}
impl ExecuteXcm for XcmExecutor {
type Prepared = WeighedMessage;
fn prepare(
mut message: Xcm,
) -> Result> {
match Config::Weigher::weight(&mut message) {
Ok(weight) => Ok(WeighedMessage(weight, message)),
Err(_) => Err(message),
}
}
fn execute(
origin: impl Into,
WeighedMessage(xcm_weight, mut message): WeighedMessage,
id: &mut XcmHash,
weight_credit: Weight,
) -> Outcome {
let origin = origin.into();
log::trace!(
target: "xcm::execute",
"origin: {origin:?}, message: {message:?}, weight_credit: {weight_credit:?}",
);
let mut properties = Properties { weight_credit, message_id: None };
if let Err(e) = Config::Barrier::should_execute(
&origin,
message.inner_mut(),
xcm_weight,
&mut properties,
) {
log::trace!(
target: "xcm::execute",
"Barrier blocked execution! Error: {e:?}. \
(origin: {origin:?}, message: {message:?}, properties: {properties:?})",
);
return Outcome::Error { error: XcmError::Barrier }
}
*id = properties.message_id.unwrap_or(*id);
let mut vm = Self::new(origin, *id);
while !message.0.is_empty() {
let result = vm.process(message);
log::trace!(target: "xcm::execute", "result: {result:?}");
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, 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, None, FeeReason::ChargeFees);
}
Ok(())
}
}
impl XcmAssetTransfers for XcmExecutor {
type IsReserve = Config::IsReserve;
type IsTeleporter = Config::IsTeleporter;
type AssetTransactor = Config::AssetTransactor;
}
#[derive(Debug)]
pub struct ExecutorError {
pub index: u32,
pub xcm_error: XcmError,
pub weight: Weight,
}
#[cfg(feature = "runtime-benchmarks")]
impl From for frame_benchmarking::BenchmarkError {
fn from(error: ExecutorError) -> Self {
log::error!(
"XCM ERROR >> Index: {:?}, Error: {:?}, Weight: {:?}",
error.index,
error.xcm_error,
error.weight
);
Self::Stop("xcm executor error: see error logs")
}
}
impl XcmExecutor {
pub fn new(origin: impl Into, 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 },
_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() {
log::trace!(
target: "xcm::post_process",
"Trapping assets in holding register: {:?}, context: {:?} (original_origin: {:?})",
self.holding, self.context, self.original_origin,
);
let effective_origin = self.context.origin.as_ref().unwrap_or(&self.original_origin);
let trap_weight =
Config::AssetTrap::drop_assets(effective_origin, 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((_i, e)) => {
log::trace!(target: "xcm::post_process", "Execution errored at {:?}: {:?} (original_origin: {:?})", _i, e, self.original_origin);
Outcome::Incomplete { used: weight_used, error: e }
},
}
}
fn origin_ref(&self) -> Option<&Location> {
self.context.origin.as_ref()
}
fn cloned_origin(&self) -> Option {
self.context.origin.clone()
}
/// Send an XCM, charging fees from Holding as needed.
fn send(
&mut self,
dest: Location,
msg: Xcm<()>,
reason: FeeReason,
) -> Result {
let (ticket, fee) = validate_send::(dest, msg)?;
self.take_fee(fee, reason)?;
Config::XcmSender::deliver(ticket).map_err(Into::into)
}
/// Remove the registered error handler and return it. Do not refund its weight.
fn take_error_handler(&mut self) -> Xcm {
let mut r = Xcm::(vec![]);
sp_std::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::(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 {
let mut r = Xcm::(vec![]);
sp_std::mem::swap(&mut self.appendix, &mut r);
self.appendix_weight = Weight::zero();
r
}
fn subsume_asset(&mut self, asset: Asset) -> Result<(), XcmError> {
// worst-case, holding.len becomes 2 * holding_limit.
ensure!(self.holding.len() < self.holding_limit * 2, XcmError::HoldingWouldOverflow);
self.holding.subsume(asset);
Ok(())
}
fn subsume_assets(&mut self, assets: AssetsInHolding) -> Result<(), XcmError> {
// worst-case, holding.len becomes 2 * holding_limit.
// this guarantees that if holding.len() == holding_limit and you have holding_limit more
// items (which has a best case outcome of holding.len() == holding_limit), then you'll
// be guaranteed of making the operation.
let worst_case_holding_len = self.holding.len() + assets.len();
ensure!(worst_case_holding_len <= self.holding_limit * 2, XcmError::HoldingWouldOverflow);
self.holding.subsume_assets(assets);
Ok(())
}
/// Refund any unused weight.
fn refund_surplus(&mut self) -> Result<(), XcmError> {
let current_surplus = self.total_surplus.saturating_sub(self.total_refunded);
if current_surplus.any_gt(Weight::zero()) {
self.total_refunded.saturating_accrue(current_surplus);
if let Some(w) = self.trader.refund_weight(current_surplus, &self.context) {
self.subsume_asset(w)?;
}
}
Ok(())
}
fn take_fee(&mut self, fee: Assets, reason: FeeReason) -> XcmResult {
if Config::FeeManager::is_waived(self.origin_ref(), reason.clone()) {
return Ok(())
}
log::trace!(
target: "xcm::fees",
"taking fee: {:?} from origin_ref: {:?} in fees_mode: {:?} for a reason: {:?}",
fee,
self.origin_ref(),
self.fees_mode,
reason,
);
let paid = if self.fees_mode.jit_withdraw {
let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
for asset in fee.inner() {
Config::AssetTransactor::withdraw_asset(&asset, origin, Some(&self.context))?;
}
fee
} else {
self.holding.try_take(fee.into()).map_err(|_| XcmError::NotHoldingFees)?.into()
};
Config::FeeManager::handle_fee(paid, Some(&self.context), reason);
Ok(())
}
/// Calculates what `local_querier` would be from the perspective of `destination`.
fn to_querier(
local_querier: Option,
destination: &Location,
) -> Result