From c3a575da9ecafa6cfd69af48de378d61a60b0d68 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Wed, 15 Jan 2025 14:03:03 +1300 Subject: [PATCH] ExecuteInstruction --- polkadot/xcm/src/traits.rs | 34 + polkadot/xcm/src/v5/asset.rs | 2 +- .../xcm-executor/src/instructions/assets.rs | 525 +++++ .../xcm-executor/src/instructions/controls.rs | 100 + .../xcm-executor/src/instructions/expect.rs | 72 + .../xcm/xcm-executor/src/instructions/fees.rs | 110 ++ .../xcm/xcm-executor/src/instructions/misc.rs | 164 ++ .../xcm/xcm-executor/src/instructions/mod.rs | 34 +- .../src/instructions/notifications.rs | 54 + .../xcm-executor/src/instructions/origin.rs | 87 + .../xcm-executor/src/instructions/query.rs | 67 + .../xcm-executor/src/instructions/report.rs | 65 + .../xcm-executor/src/instructions/versions.rs | 45 + polkadot/xcm/xcm-executor/src/lib.rs | 1744 +++++++++-------- 14 files changed, 2230 insertions(+), 873 deletions(-) create mode 100644 polkadot/xcm/xcm-executor/src/instructions/assets.rs create mode 100644 polkadot/xcm/xcm-executor/src/instructions/controls.rs create mode 100644 polkadot/xcm/xcm-executor/src/instructions/expect.rs create mode 100644 polkadot/xcm/xcm-executor/src/instructions/fees.rs create mode 100644 polkadot/xcm/xcm-executor/src/instructions/misc.rs create mode 100644 polkadot/xcm/xcm-executor/src/instructions/notifications.rs create mode 100644 polkadot/xcm/xcm-executor/src/instructions/origin.rs create mode 100644 polkadot/xcm/xcm-executor/src/instructions/query.rs create mode 100644 polkadot/xcm/xcm-executor/src/instructions/report.rs create mode 100644 polkadot/xcm/xcm-executor/src/instructions/versions.rs diff --git a/polkadot/xcm/src/traits.rs b/polkadot/xcm/src/traits.rs index 7d7cb219eda1..b8bdba45972d 100644 --- a/polkadot/xcm/src/traits.rs +++ b/polkadot/xcm/src/traits.rs @@ -163,6 +163,40 @@ impl Reanchorable for Location { } } +impl Reanchorable for Asset { + type Error = (); + + /// Mutate the location of the asset identifier if concrete, giving it the same location + /// relative to a `target` context. The local context is provided as `context`. + fn reanchor(&mut self, target: &Location, context: &InteriorLocation) -> result::Result<(), ()> { + self.id.reanchor(target, context) + } + + /// Mutate the location of the asset identifier if concrete, giving it the same location + /// relative to a `target` context. The local context is provided as `context`. + fn reanchored(mut self, target: &Location, context: &InteriorLocation) -> result::Result { + self.id.reanchor(target, context)?; + Ok(self) + } +} + +impl Reanchorable for Assets { + type Error = (); + + fn reanchor(&mut self, target: &Location, context: &InteriorLocation) -> result::Result<(), ()> { + self.0.iter_mut().try_for_each(|i| i.reanchor(target, context))?; + self.0.sort(); + Ok(()) + } + + fn reanchored(mut self, target: &Location, context: &InteriorLocation) -> result::Result { + match self.reanchor(target, context) { + Ok(()) => Ok(self), + Err(()) => Err(()), + } + } +} + /// Result value when attempting to send an XCM message. pub type SendResult = result::Result<(T, Assets), SendError>; diff --git a/polkadot/xcm/src/v5/asset.rs b/polkadot/xcm/src/v5/asset.rs index d0d9a7cedff0..958e881adc3f 100644 --- a/polkadot/xcm/src/v5/asset.rs +++ b/polkadot/xcm/src/v5/asset.rs @@ -542,7 +542,7 @@ impl TryFrom for Asset { serde::Serialize, serde::Deserialize, )] -pub struct Assets(Vec); +pub struct Assets(pub(crate) Vec); /// Maximum number of items we expect in a single `Assets` value. Note this is not (yet) /// enforced, and just serves to provide a sensible `max_encoded_len` for `Assets`. diff --git a/polkadot/xcm/xcm-executor/src/instructions/assets.rs b/polkadot/xcm/xcm-executor/src/instructions/assets.rs new file mode 100644 index 000000000000..58aa77461af2 --- /dev/null +++ b/polkadot/xcm/xcm-executor/src/instructions/assets.rs @@ -0,0 +1,525 @@ +// 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 . + +use super::ExecuteInstruction; +use crate::traits::{ + AssetExchange, AssetLock, ClaimAssets, Enact, FeeReason, + ProcessTransaction, TransactAsset, +}; +use crate::{config, validate_send, AssetTransferFilter, WeightLimit, XcmExecutor}; +use frame_support::{ + ensure, + traits::{ContainsPair, Get}, +}; +use xcm::{ + latest::{instructions::*, Error as XcmError, Reanchorable, Xcm}, + traits::{IntoInstruction, SendXcm}, +}; + +impl ExecuteInstruction for WithdrawAsset { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let assets = self.0; + let origin = executor.origin_ref().ok_or(XcmError::BadOrigin)?; + executor.ensure_can_subsume_assets(assets.len())?; + Config::TransactionalProcessor::process(|| { + // Take `assets` from the origin account (on-chain)... + for asset in assets.inner() { + Config::AssetTransactor::withdraw_asset(asset, origin, Some(&executor.context))?; + } + Ok(()) + }) + .and_then(|_| { + // ...and place into holding. + executor.holding.subsume_assets(assets.into()); + Ok(()) + }) + } +} + +impl ExecuteInstruction for ReserveAssetDeposited { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let assets = self.0; + // check whether we trust origin to be our reserve location for this asset. + let origin = executor.origin_ref().ok_or(XcmError::BadOrigin)?; + executor.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); + } + executor.holding.subsume_assets(assets.into()); + Ok(()) + } +} + +impl ExecuteInstruction for TransferAsset { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let TransferAsset { assets, beneficiary } = self; + Config::TransactionalProcessor::process(|| { + // Take `assets` from the origin account (on-chain) and place into dest account. + let origin = executor.origin_ref().ok_or(XcmError::BadOrigin)?; + for asset in assets.inner() { + Config::AssetTransactor::transfer_asset( + &asset, + origin, + &beneficiary, + &executor.context, + )?; + } + Ok(()) + }) + } +} + +impl ExecuteInstruction for TransferReserveAsset { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let TransferReserveAsset { mut assets, dest, xcm } = self; + Config::TransactionalProcessor::process(|| { + let origin = executor.origin_ref().ok_or(XcmError::BadOrigin)?; + // Take `assets` from the origin account (on-chain) and place into dest account. + for asset in assets.inner() { + Config::AssetTransactor::transfer_asset(asset, origin, &dest, &executor.context)?; + } + let reanchor_context = Config::UniversalLocation::get(); + assets.reanchor(&dest, &reanchor_context).map_err(|()| XcmError::LocationFull)?; + let mut message = vec![ + ReserveAssetDeposited(assets).into_instruction(), + ClearOrigin.into_instruction(), + ]; + message.extend(xcm.0.into_iter()); + executor.send(dest, Xcm::new(message), FeeReason::TransferReserveAsset)?; + Ok(()) + }) + } +} + +impl ExecuteInstruction for ReceiveTeleportedAsset { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let assets = self.0; + let origin = executor.origin_ref().ok_or(XcmError::BadOrigin)?; + executor.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, &executor.context)?; + Config::AssetTransactor::check_in(origin, asset, &executor.context); + } + Ok(()) + }) + .and_then(|_| { + executor.holding.subsume_assets(assets.into()); + Ok(()) + }) + } +} + +impl ExecuteInstruction for DepositAsset { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let DepositAsset { assets, beneficiary } = self; + let old_holding = executor.holding.clone(); + let result = Config::TransactionalProcessor::process(|| { + let deposited = executor.holding.saturating_take(assets); + XcmExecutor::::deposit_assets_with_retry( + &deposited, + &beneficiary, + Some(&executor.context), + ) + }); + if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { + executor.holding = old_holding; + } + result + } +} + +impl ExecuteInstruction for DepositReserveAsset { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let DepositReserveAsset { assets, dest, xcm } = self; + let old_holding = executor.holding.clone(); + let result = Config::TransactionalProcessor::process(|| { + let mut assets = executor.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 executor.fees.is_empty() && !executor.fees_mode.jit_withdraw { + // Deduct and return the part of `assets` that shall be used for delivery fees. + executor.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"); + XcmExecutor::::do_reserve_deposit_assets( + assets, + &dest, + &mut message, + Some(&executor.context), + )?; + // clear origin for subsequent custom instructions + message.push(ClearOrigin.into_instruction()); + // 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. + executor.holding.subsume_assets(delivery_fee); + } + executor.send(dest, Xcm::new(message), FeeReason::DepositReserveAsset)?; + Ok(()) + }); + if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { + executor.holding = old_holding; + } + result + } +} + +impl ExecuteInstruction for InitiateReserveWithdraw { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let InitiateReserveWithdraw { assets, reserve, xcm } = self; + let old_holding = executor.holding.clone(); + let result = Config::TransactionalProcessor::process(|| { + let mut assets = executor.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 executor.fees.is_empty() && !executor.fees_mode.jit_withdraw { + // Deduct and return the part of `assets` that shall be used for delivery fees. + executor.take_delivery_fee_from_assets( + &mut assets, + &reserve, + FeeReason::InitiateReserveWithdraw, + &xcm, + )? + } else { + None + }; + let mut message = Vec::with_capacity(xcm.len() + 2); + XcmExecutor::::do_reserve_withdraw_assets( + assets, + &mut executor.holding, + &reserve, + &mut message, + )?; + // clear origin for subsequent custom instructions + message.push(ClearOrigin.into_instruction()); + // 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. + executor.holding.subsume_assets(delivery_fee); + } + executor.send(reserve, Xcm::new(message), FeeReason::InitiateReserveWithdraw)?; + Ok(()) + }); + if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { + executor.holding = old_holding; + } + result + } +} + +impl ExecuteInstruction for InitiateTeleport { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let InitiateTeleport { assets, dest, xcm } = self; + let old_holding = executor.holding.clone(); + let result = Config::TransactionalProcessor::process(|| { + let mut assets = executor.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 executor.fees.is_empty() && !executor.fees_mode.jit_withdraw { + // Deduct and return the part of `assets` that shall be used for delivery fees. + executor.take_delivery_fee_from_assets( + &mut assets, + &dest, + FeeReason::InitiateTeleport, + &xcm, + )? + } else { + None + }; + let mut message = Vec::with_capacity(xcm.len() + 2); + XcmExecutor::::do_teleport_assets(assets, &dest, &mut message, &executor.context)?; + // clear origin for subsequent custom instructions + message.push(ClearOrigin.into_instruction()); + // 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. + executor.holding.subsume_assets(delivery_fee); + } + executor.send(dest.clone(), Xcm::new(message), FeeReason::InitiateTeleport)?; + Ok(()) + }); + if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { + executor.holding = old_holding; + } + result + } +} + +impl ExecuteInstruction for InitiateTransfer { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let InitiateTransfer { destination, remote_fees, preserve_origin, assets, remote_xcm } = + self; + let old_holding = executor.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. + if let Some(remote_fees) = remote_fees { + let reanchored_fees = match remote_fees { + AssetTransferFilter::Teleport(fees_filter) => { + let teleport_fees = executor + .holding + .try_take(fees_filter) + .map_err(|_| XcmError::NotHoldingFees)?; + XcmExecutor::::do_teleport_assets( + teleport_fees, + &destination, + &mut message, + &executor.context, + )? + }, + AssetTransferFilter::ReserveDeposit(fees_filter) => { + let reserve_deposit_fees = executor + .holding + .try_take(fees_filter) + .map_err(|_| XcmError::NotHoldingFees)?; + XcmExecutor::::do_reserve_deposit_assets( + reserve_deposit_fees, + &destination, + &mut message, + Some(&executor.context), + )? + }, + AssetTransferFilter::ReserveWithdraw(fees_filter) => { + let reserve_withdraw_fees = executor + .holding + .try_take(fees_filter) + .map_err(|_| XcmError::NotHoldingFees)?; + XcmExecutor::::do_reserve_withdraw_assets( + reserve_withdraw_fees, + &mut executor.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 }.into_instruction()); + } else { + // unpaid execution + message.push( + UnpaidExecution { weight_limit: WeightLimit::Unlimited, check_origin: None } + .into_instruction(), + ); + } + + // add any extra asset transfers + for asset_filter in assets { + match asset_filter { + AssetTransferFilter::Teleport(assets) => { + XcmExecutor::::do_teleport_assets( + executor.holding.saturating_take(assets), + &destination, + &mut message, + &executor.context, + )? + }, + AssetTransferFilter::ReserveDeposit(assets) => { + XcmExecutor::::do_reserve_deposit_assets( + executor.holding.saturating_take(assets), + &destination, + &mut message, + Some(&executor.context), + )? + }, + AssetTransferFilter::ReserveWithdraw(assets) => { + XcmExecutor::::do_reserve_withdraw_assets( + executor.holding.saturating_take(assets), + &mut executor.holding, + &destination, + &mut message, + )? + }, + }; + } + if preserve_origin { + // preserve current origin for subsequent user-controlled instructions on + // remote chain + let original_origin = executor + .origin_ref() + .cloned() + .and_then(|origin| { + XcmExecutor::::try_reanchor(origin, &destination) + .map(|(reanchored, _)| reanchored) + .ok() + }) + .ok_or(XcmError::BadOrigin)?; + message.push(AliasOrigin(original_origin).into_instruction()); + } else { + // clear origin for subsequent user-controlled instructions on remote chain + message.push(ClearOrigin.into_instruction()); + } + // append custom instructions + message.extend(remote_xcm.0.into_iter()); + // send the onward XCM + executor.send(destination, Xcm::new(message), FeeReason::InitiateTransfer)?; + Ok(()) + }); + if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { + executor.holding = old_holding; + } + result + } +} + +impl ExecuteInstruction for ClaimAsset { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let ClaimAsset { assets, ticket } = self; + let origin = executor.origin_ref().ok_or(XcmError::BadOrigin)?; + executor.ensure_can_subsume_assets(assets.len())?; + let ok = Config::AssetClaims::claim_assets(origin, &ticket, &assets, &executor.context); + ensure!(ok, XcmError::UnknownClaim); + executor.holding.subsume_assets(assets.into()); + Ok(()) + } +} + +impl ExecuteInstruction for BurnAsset { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let BurnAsset(assets) = self; + executor.holding.saturating_take(assets.into()); + Ok(()) + } +} + +impl ExecuteInstruction for LockAsset { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let LockAsset { asset, unlocker } = self; + let old_holding = executor.holding.clone(); + let result = Config::TransactionalProcessor::process(|| { + let origin = executor.cloned_origin().ok_or(XcmError::BadOrigin)?; + let (remote_asset, context) = XcmExecutor::::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::<()>::new(vec![ + NoteUnlockable { asset: remote_asset, owner }.into_instruction() + ]); + let (ticket, price) = validate_send::(unlocker, msg)?; + executor.take_fee(price, FeeReason::LockAsset)?; + lock_ticket.enact()?; + Config::XcmSender::deliver(ticket)?; + Ok(()) + }); + if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { + executor.holding = old_holding; + } + result + } +} + +impl ExecuteInstruction for UnlockAsset { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let UnlockAsset { asset, target } = self; + let origin = executor.cloned_origin().ok_or(XcmError::BadOrigin)?; + Config::AssetLocker::prepare_unlock(origin, asset, target)?.enact()?; + Ok(()) + } +} + +impl ExecuteInstruction for NoteUnlockable { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let NoteUnlockable { asset, owner } = self; + let origin = executor.cloned_origin().ok_or(XcmError::BadOrigin)?; + Config::AssetLocker::note_unlockable(origin, asset, owner)?; + Ok(()) + } +} + +impl ExecuteInstruction for RequestUnlock { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let RequestUnlock { asset, locker } = self; + let origin = executor.cloned_origin().ok_or(XcmError::BadOrigin)?; + let remote_asset = XcmExecutor::::try_reanchor(asset.clone(), &locker)?.0; + let remote_target = XcmExecutor::::try_reanchor(origin.clone(), &locker)?.0; + let reduce_ticket = + Config::AssetLocker::prepare_reduce_unlockable(locker.clone(), asset, origin.clone())?; + let msg = Xcm::<()>::new(vec![ + UnlockAsset { asset: remote_asset, target: remote_target }.into_instruction() + ]); + let (ticket, price) = validate_send::(locker, msg)?; + let old_holding = executor.holding.clone(); + let result = Config::TransactionalProcessor::process(|| { + executor.take_fee(price, FeeReason::RequestUnlock)?; + reduce_ticket.enact()?; + Config::XcmSender::deliver(ticket)?; + Ok(()) + }); + if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { + executor.holding = old_holding; + } + result + } +} + +impl ExecuteInstruction for ExchangeAsset { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let ExchangeAsset { give, want, maximal } = self; + let old_holding = executor.holding.clone(); + let give = executor.holding.saturating_take(give); + let result = Config::TransactionalProcessor::process(|| { + executor.ensure_can_subsume_assets(want.len())?; + let exchange_result = + Config::AssetExchanger::exchange_asset(executor.origin_ref(), give, &want, maximal); + if let Ok(received) = exchange_result { + executor.holding.subsume_assets(received.into()); + Ok(()) + } else { + Err(XcmError::NoDeal) + } + }); + if result.is_err() { + executor.holding = old_holding; + } + result + } +} diff --git a/polkadot/xcm/xcm-executor/src/instructions/controls.rs b/polkadot/xcm/xcm-executor/src/instructions/controls.rs new file mode 100644 index 000000000000..10bb337981a0 --- /dev/null +++ b/polkadot/xcm/xcm-executor/src/instructions/controls.rs @@ -0,0 +1,100 @@ +// 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 . + +use super::ExecuteInstruction; +use crate::{config, traits::WeightBounds, FeesMode, Hint::AssetClaimer, XcmExecutor}; +use xcm::latest::instructions::*; +use xcm::latest::Error as XcmError; + +impl ExecuteInstruction for SetErrorHandler { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let mut handler = self.0; + let handler_weight = + Config::Weigher::weight(&mut handler).map_err(|()| XcmError::WeightNotComputable)?; + executor.total_surplus.saturating_accrue(executor.error_handler_weight); + executor.error_handler = handler; + executor.error_handler_weight = handler_weight; + Ok(()) + } +} + +impl ExecuteInstruction for SetAppendix { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let mut appendix = self.0; + let appendix_weight = + Config::Weigher::weight(&mut appendix).map_err(|()| XcmError::WeightNotComputable)?; + executor.total_surplus.saturating_accrue(executor.appendix_weight); + executor.appendix = appendix; + executor.appendix_weight = appendix_weight; + Ok(()) + } +} + +impl ExecuteInstruction for ClearError { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + executor.error = None; + Ok(()) + } +} + +impl ExecuteInstruction for SetHints { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let SetHints { hints } = self; + for hint in hints.into_iter() { + match hint { + AssetClaimer { location } => executor.asset_claimer = Some(location), + } + } + Ok(()) + } +} + +impl ExecuteInstruction for Trap { + fn execute(self, _executor: &mut XcmExecutor) -> Result<(), XcmError> { + let Trap(code) = self; + Err(XcmError::Trap(code)) + } +} + +impl ExecuteInstruction for ClearTransactStatus { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + executor.transact_status = Default::default(); + Ok(()) + } +} + +impl ExecuteInstruction for SetFeesMode { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let SetFeesMode { jit_withdraw } = self; + executor.fees_mode = FeesMode { jit_withdraw }; + Ok(()) + } +} + +impl ExecuteInstruction for SetTopic { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let SetTopic(topic) = self; + executor.context.topic = Some(topic); + Ok(()) + } +} + +impl ExecuteInstruction for ClearTopic { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + executor.context.topic = None; + Ok(()) + } +} diff --git a/polkadot/xcm/xcm-executor/src/instructions/expect.rs b/polkadot/xcm/xcm-executor/src/instructions/expect.rs new file mode 100644 index 000000000000..c5d343d91d2f --- /dev/null +++ b/polkadot/xcm/xcm-executor/src/instructions/expect.rs @@ -0,0 +1,72 @@ +// 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 . + +use super::ExecuteInstruction; +use crate::{config, XcmExecutor}; +use xcm::latest::instructions::*; +use xcm::latest::Error as XcmError; +use frame_support::{ensure, traits::PalletsInfoAccess}; + +impl ExecuteInstruction for ExpectAsset { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let ExpectAsset(assets) = self; + executor.holding.ensure_contains(&assets).map_err(|e| { + tracing::error!(target: "xcm::process_instruction::expect_asset", ?e, ?assets, "assets not contained in holding"); + XcmError::ExpectationFalse + }) + } +} + +impl ExecuteInstruction for ExpectOrigin { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let ExpectOrigin(origin) = self; + ensure!(executor.context.origin == origin, XcmError::ExpectationFalse); + Ok(()) + } +} + +impl ExecuteInstruction for ExpectError { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let ExpectError(error) = self; + ensure!(executor.error == error, XcmError::ExpectationFalse); + Ok(()) + } +} + +impl ExecuteInstruction for ExpectTransactStatus { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let ExpectTransactStatus(transact_status) = self; + ensure!(executor.transact_status == transact_status, XcmError::ExpectationFalse); + Ok(()) + } +} + +impl ExecuteInstruction for ExpectPallet { + fn execute(self, _executor: &mut XcmExecutor) -> Result<(), XcmError> { + let ExpectPallet { index, name, module_name, crate_major, min_crate_minor } = self; + 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(()) + } +} diff --git a/polkadot/xcm/xcm-executor/src/instructions/fees.rs b/polkadot/xcm/xcm-executor/src/instructions/fees.rs new file mode 100644 index 000000000000..02f42f77d9f6 --- /dev/null +++ b/polkadot/xcm/xcm-executor/src/instructions/fees.rs @@ -0,0 +1,110 @@ +// 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 . + +use super::ExecuteInstruction; +use crate::{config, XcmExecutor, Weight, traits::{ProcessTransaction, WeightTrader}}; +use xcm::latest::instructions::*; +use xcm::latest::Error as XcmError; +use frame_support::ensure; + +impl ExecuteInstruction for BuyExecution { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let BuyExecution { fees, weight_limit } = self; + // 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::::from(weight_limit) else { return Ok(()) }; + let old_holding = executor.holding.clone(); + // Save the asset being used for execution fees, so we later know what should be + // used for delivery fees. + executor.asset_used_in_buy_execution = Some(fees.id.clone()); + tracing::trace!( + target: "xcm::executor::BuyExecution", + asset_used_in_buy_execution = ?executor.asset_used_in_buy_execution + ); + // pay for `weight` using up to `fees` of the holding register. + let max_fee = + executor.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 = executor.trader.buy_weight(weight, max_fee, &executor.context)?; + executor.holding.subsume_assets(unspent); + Ok(()) + }); + if result.is_err() { + executor.holding = old_holding; + } + result + } +} + +impl ExecuteInstruction for PayFees { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let PayFees { asset } = self; + // Message was not weighed, there is nothing to pay. + if executor.message_weight == Weight::zero() { + tracing::warn!( + target: "xcm::executor::PayFees", + "Message was not weighed or weight was 0. Nothing will be charged.", + ); + return Ok(()); + } + // Record old holding in case we need to rollback. + let old_holding = executor.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 = ?executor.message_weight, + ); + let max_fee = + executor.holding.try_take(asset.into()).map_err(|_| XcmError::NotHoldingFees)?; + // Pay for execution fees. + let result = Config::TransactionalProcessor::process(|| { + let unspent = + executor.trader.buy_weight(executor.message_weight, max_fee, &executor.context)?; + // Move unspent to the `fees` register. + executor.fees.subsume_assets(unspent); + Ok(()) + }); + if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { + // Rollback. + executor.holding = old_holding; + } + result + } +} + +impl ExecuteInstruction for RefundSurplus { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + executor.refund_surplus() + } +} + +impl ExecuteInstruction for UnpaidExecution { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let UnpaidExecution { check_origin, .. } = self; + ensure!( + check_origin.is_none() || executor.context.origin == check_origin, + XcmError::BadOrigin + ); + Ok(()) + } +} diff --git a/polkadot/xcm/xcm-executor/src/instructions/misc.rs b/polkadot/xcm/xcm-executor/src/instructions/misc.rs new file mode 100644 index 000000000000..47a6d2464a4b --- /dev/null +++ b/polkadot/xcm/xcm-executor/src/instructions/misc.rs @@ -0,0 +1,164 @@ +// 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 . + +use super::ExecuteInstruction; +use crate::traits::{ + validate_export, CallDispatcher, ConvertOrigin, ExportXcm, FeeReason, ProcessTransaction, +}; +use crate::{config, MaybeErrorCode, XcmExecutor}; +use codec::{Decode, Encode}; +use frame_support::{ + dispatch::GetDispatchInfo, + traits::{Contains, Defensive, Get}, +}; +use sp_io::hashing::blake2_128; +use xcm::latest::{instructions::*, Error as XcmError}; + +impl ExecuteInstruction for Transact { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let Transact { origin_kind, mut call, .. } = self; + // We assume that the Relay-chain is allowed to use transact on this parachain. + let origin = executor.cloned_origin().ok_or_else(|| { + tracing::trace!( + target: "xcm::process_instruction::transact", + "No origin provided", + ); + + XcmError::BadOrigin + })?; + + // TODO: #2841 #TRANSACTFILTER allow the trait to issue filters for the relay-chain + 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" + ); + executor.transact_status = MaybeErrorCode::Success; + post_info.actual_weight + }, + Err(error_and_info) => { + tracing::trace!( + target: "xcm::process_instruction::transact", + ?error_and_info, + "Dispatch failed" + ); + + executor.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). + executor.total_surplus.saturating_accrue(surplus); + Ok(()) + } +} + +impl ExecuteInstruction for ExportMessage { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let ExportMessage { network, destination, xcm } = self; + // 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 = executor.context.origin.as_ref().ok_or(XcmError::BadOrigin)?.clone(); + let universal_source = Config::UniversalLocation::get() + .within_global(origin) + .map_err(|()| XcmError::Unanchored)?; + let hash = (executor.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::( + network, + channel, + universal_source, + destination.clone(), + xcm, + )?; + let old_holding = executor.holding.clone(); + let result = Config::TransactionalProcessor::process(|| { + executor.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() { + executor.holding = old_holding; + } + result + } +} diff --git a/polkadot/xcm/xcm-executor/src/instructions/mod.rs b/polkadot/xcm/xcm-executor/src/instructions/mod.rs index 81bdba1faea1..48f544fad5e4 100644 --- a/polkadot/xcm/xcm-executor/src/instructions/mod.rs +++ b/polkadot/xcm/xcm-executor/src/instructions/mod.rs @@ -15,8 +15,38 @@ // along with Polkadot. If not, see . use crate::{config, XcmExecutor}; -use xcm::latest::Error as XcmError; +use xcm::{ + apply_instructions, + latest::{Error as XcmError, Instruction}, +}; + +mod assets; +mod controls; +mod expect; +mod fees; +mod misc; +mod notifications; +mod origin; +mod query; +mod report; +mod versions; pub trait ExecuteInstruction { - fn execute(&self, executor: &mut XcmExecutor) -> Result<(), XcmError>; + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError>; +} + +macro_rules! impl_execute_instruction { + ($name:ident, $( $instr:ident $( < $instr_generic:ty > )? ),*) => { + impl ExecuteInstruction for $name<::RuntimeCall> { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + match self { + $( + Self::$instr(x) => x.execute(executor), + )* + } + } + } + }; } + +apply_instructions!(impl_execute_instruction, Instruction); diff --git a/polkadot/xcm/xcm-executor/src/instructions/notifications.rs b/polkadot/xcm/xcm-executor/src/instructions/notifications.rs new file mode 100644 index 000000000000..fb40acb837c6 --- /dev/null +++ b/polkadot/xcm/xcm-executor/src/instructions/notifications.rs @@ -0,0 +1,54 @@ +// 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 . + +use super::ExecuteInstruction; +use crate::{ + config, + traits::{ + HandleHrmpChannelAccepted, HandleHrmpChannelClosing, HandleHrmpNewChannelOpenRequest, + ProcessTransaction, + }, + XcmExecutor, +}; +use xcm::latest::instructions::*; +use xcm::latest::Error as XcmError; + +impl ExecuteInstruction for HrmpNewChannelOpenRequest { + fn execute(self, _executor: &mut XcmExecutor) -> Result<(), XcmError> { + let HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } = self; + Config::TransactionalProcessor::process(|| { + Config::HrmpNewChannelOpenRequestHandler::handle(sender, max_message_size, max_capacity) + }) + } +} + +impl ExecuteInstruction for HrmpChannelAccepted { + fn execute(self, _executor: &mut XcmExecutor) -> Result<(), XcmError> { + let HrmpChannelAccepted { recipient } = self; + Config::TransactionalProcessor::process(|| { + Config::HrmpChannelAcceptedHandler::handle(recipient) + }) + } +} + +impl ExecuteInstruction for HrmpChannelClosing { + fn execute(self, _executor: &mut XcmExecutor) -> Result<(), XcmError> { + let HrmpChannelClosing { initiator, sender, recipient } = self; + Config::TransactionalProcessor::process(|| { + Config::HrmpChannelClosingHandler::handle(initiator, sender, recipient) + }) + } +} diff --git a/polkadot/xcm/xcm-executor/src/instructions/origin.rs b/polkadot/xcm/xcm-executor/src/instructions/origin.rs new file mode 100644 index 000000000000..99bf5e43530d --- /dev/null +++ b/polkadot/xcm/xcm-executor/src/instructions/origin.rs @@ -0,0 +1,87 @@ +// 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 . + +use super::ExecuteInstruction; +use crate::{config, Junctions, XcmExecutor}; +use frame_support::{ + ensure, + traits::{Contains, ContainsPair, Get}, +}; +use xcm::latest::instructions::*; +use xcm::latest::Error as XcmError; + +impl ExecuteInstruction for DescendOrigin { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + executor.do_descend_origin(self.0) + } +} + +impl ExecuteInstruction for ClearOrigin { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + executor.do_clear_origin() + } +} + +impl ExecuteInstruction for ExecuteWithOrigin { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let ExecuteWithOrigin { descendant_origin, xcm } = self; + let previous_origin = executor.context.origin.clone(); + + // Set new temporary origin. + if let Some(who) = descendant_origin { + executor.do_descend_origin(who)?; + } else { + executor.do_clear_origin()?; + } + // Process instructions. + let result = executor.process(xcm).map_err(|error| { + tracing::error!(target: "xcm::execute", ?error, actual_origin = ?executor.context.origin, original_origin = ?previous_origin, "ExecuteWithOrigin inner xcm failure"); + error.xcm_error + }); + // Reset origin to previous one. + executor.context.origin = previous_origin; + result + } +} + +impl ExecuteInstruction for UniversalOrigin { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let UniversalOrigin(new_global) = self; + let universal_location = Config::UniversalLocation::get(); + ensure!(universal_location.first() != Some(&new_global), XcmError::InvalidLocation); + let origin = executor.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); + executor.context.origin = Some(new_origin); + Ok(()) + } +} + +impl ExecuteInstruction for AliasOrigin { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let AliasOrigin(target) = self; + let origin = executor.origin_ref().ok_or(XcmError::BadOrigin)?; + if Config::Aliasers::contains(origin, &target) { + executor.context.origin = Some(target); + Ok(()) + } else { + Err(XcmError::NoPermission) + } + } +} diff --git a/polkadot/xcm/xcm-executor/src/instructions/query.rs b/polkadot/xcm/xcm-executor/src/instructions/query.rs new file mode 100644 index 000000000000..901782c35391 --- /dev/null +++ b/polkadot/xcm/xcm-executor/src/instructions/query.rs @@ -0,0 +1,67 @@ +// 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 . + +use super::ExecuteInstruction; +use crate::{ + config, traits::OnResponse, FeeReason, PalletInfo, QueryResponseInfo, Response, XcmExecutor, +}; +use xcm::latest::instructions::*; +use xcm::latest::{Error as XcmError, Xcm}; +use frame_support::traits::PalletsInfoAccess; +use xcm::traits::IntoInstruction; + +impl ExecuteInstruction for QueryResponse { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let QueryResponse { query_id, response, max_weight, querier, .. } = self; + let origin = executor.origin_ref().ok_or(XcmError::BadOrigin)?; + Config::ResponseHandler::on_response( + origin, + query_id, + querier.as_ref(), + response, + max_weight, + &executor.context, + ); + Ok(()) + } +} + +impl ExecuteInstruction for QueryPallet { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let QueryPallet { module_name, response_info } = self; + 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::, XcmError>>()?; + let QueryResponseInfo { destination, query_id, max_weight } = response_info; + let response = Response::PalletsInfo(pallets.try_into().map_err(|_| XcmError::Overflow)?); + let querier = XcmExecutor::::to_querier(executor.cloned_origin(), &destination)?; + let instruction = QueryResponse { query_id, response, max_weight, querier }.into_instruction(); + let message = Xcm::new(vec![instruction]); + executor.send(destination, message, FeeReason::QueryPallet)?; + Ok(()) + } +} diff --git a/polkadot/xcm/xcm-executor/src/instructions/report.rs b/polkadot/xcm/xcm-executor/src/instructions/report.rs new file mode 100644 index 000000000000..18c9fa748764 --- /dev/null +++ b/polkadot/xcm/xcm-executor/src/instructions/report.rs @@ -0,0 +1,65 @@ +// 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 . + +use super::ExecuteInstruction; +use crate::{config, XcmExecutor, FeeReason, Response}; +use xcm::latest::instructions::*; +use xcm::latest::Error as XcmError; + +impl ExecuteInstruction for ReportError { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let response_info = self.0; + // Report the given result by sending a QueryResponse XCM to a previously given + // outcome destination if one was registered. + executor.respond( + executor.cloned_origin(), + Response::ExecutionResult(executor.error), + response_info, + FeeReason::Report, + )?; + Ok(()) + } +} + +impl ExecuteInstruction for ReportHolding { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let ReportHolding { response_info, assets } = self; + // Note that we pass `None` as `maybe_failed_bin` since no assets were ever removed + // from Holding. + let assets = + XcmExecutor::::reanchored(executor.holding.min(&assets), &response_info.destination, None); + executor.respond( + executor.cloned_origin(), + Response::Assets(assets), + response_info, + FeeReason::Report, + )?; + Ok(()) + } +} + +impl ExecuteInstruction for ReportTransactStatus { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let ReportTransactStatus(response_info) = self; + executor.respond( + executor.cloned_origin(), + Response::DispatchResult(executor.transact_status.clone()), + response_info, + FeeReason::Report, + )?; + Ok(()) + } +} diff --git a/polkadot/xcm/xcm-executor/src/instructions/versions.rs b/polkadot/xcm/xcm-executor/src/instructions/versions.rs new file mode 100644 index 000000000000..b4dd8a028836 --- /dev/null +++ b/polkadot/xcm/xcm-executor/src/instructions/versions.rs @@ -0,0 +1,45 @@ +// 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 . + +use super::ExecuteInstruction; +use crate::{config, XcmExecutor, traits::VersionChangeNotifier}; +use xcm::latest::instructions::*; +use xcm::latest::Error as XcmError; +use frame_support::ensure; + +impl ExecuteInstruction for SubscribeVersion { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let SubscribeVersion { query_id, max_response_weight } = self; + let origin = executor.origin_ref().ok_or(XcmError::BadOrigin)?; + // We don't allow derivative origins to subscribe since it would otherwise pose a + // DoS risk. + ensure!(&executor.original_origin == origin, XcmError::BadOrigin); + Config::SubscriptionService::start( + origin, + query_id, + max_response_weight, + &executor.context, + ) + } +} + +impl ExecuteInstruction for UnsubscribeVersion { + fn execute(self, executor: &mut XcmExecutor) -> Result<(), XcmError> { + let origin = executor.origin_ref().ok_or(XcmError::BadOrigin)?; + ensure!(&executor.original_origin == origin, XcmError::BadOrigin); + Config::SubscriptionService::stop(origin, &executor.context) + } +} diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index d0edc1561465..dfc70622d56d 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -19,26 +19,23 @@ 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}, + traits::{ContainsPair, Defensive, Get}, }; use sp_core::defer; -use sp_io::hashing::blake2_128; use sp_weights::Weight; -use xcm::{latest::{prelude::*, AssetTransferFilter, ExecuteXcm, SendXcm}, traits::IntoInstruction}; use xcm::traits::validate_send; +use xcm::{ + latest::{prelude::*, AssetTransferFilter, ExecuteXcm, SendXcm}, + traits::IntoInstruction, +}; 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, + AssetExchange, DropAssets, FeeManager, FeeReason, Properties, ShouldExecute, TransactAsset, + WeightBounds, WeightTrader, XcmAssetTransfers, }; pub use traits::RecordXcm; @@ -49,6 +46,7 @@ mod config; pub use config::Config; mod instructions; +pub use instructions::ExecuteInstruction; #[cfg(test)] mod tests; @@ -69,33 +67,33 @@ environmental::environmental!(recursion_count: u8); /// The XCM executor. pub struct XcmExecutor { - holding: AssetsInHolding, - holding_limit: usize, - context: XcmContext, - original_origin: Location, - trader: Config::Trader, + pub(crate) holding: AssetsInHolding, + pub(crate) holding_limit: usize, + pub(crate) context: XcmContext, + pub(crate) original_origin: Location, + pub(crate) trader: Config::Trader, /// The most recent error result and instruction index into the fragment in which it occurred, /// if any. - error: Option<(u32, XcmError)>, + pub(crate) 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, - fees: AssetsInHolding, + pub(crate) total_surplus: Weight, + pub(crate) total_refunded: Weight, + pub(crate) error_handler: Xcm, + pub(crate) error_handler_weight: Weight, + pub(crate) appendix: Xcm, + pub(crate) appendix_weight: Weight, + pub(crate) transact_status: MaybeErrorCode, + pub(crate) fees_mode: FeesMode, + pub(crate) 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, + pub(crate) asset_used_in_buy_execution: Option, /// Stores the current message's weight. - message_weight: Weight, - asset_claimer: Option, + pub(crate) message_weight: Weight, + pub(crate) asset_claimer: Option, _config: PhantomData, } @@ -845,845 +843,845 @@ impl XcmExecutor { "Processing instruction", ); - match instr { - _ => todo!() - // WithdrawAsset(assets) => { - // let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; - // self.ensure_can_subsume_assets(assets.len())?; - // Config::TransactionalProcessor::process(|| { - // // Take `assets` from the origin account (on-chain)... - // for asset in assets.inner() { - // Config::AssetTransactor::withdraw_asset( - // asset, - // origin, - // Some(&self.context), - // )?; - // } - // Ok(()) - // }) - // .and_then(|_| { - // // ...and place into holding. - // self.holding.subsume_assets(assets.into()); - // 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)?; - // 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)?; - // // Take `assets` from the origin account (on-chain) and place into dest account. - // 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::new(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(|| { - // // 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, .. } => { - // // We assume that the Relay-chain is allowed to use transact on this parachain. - // let origin = self.cloned_origin().ok_or_else(|| { - // tracing::trace!( - // target: "xcm::process_instruction::transact", - // "No origin provided", - // ); - - // XcmError::BadOrigin - // })?; - - // // TODO: #2841 #TRANSACTFILTER allow the trait to issue filters for the relay-chain - // 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); - // // 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 { descendant_origin, xcm } => { - // let previous_origin = self.context.origin.clone(); - - // // Set new temporary origin. - // if let Some(who) = descendant_origin { - // self.do_descend_origin(who)?; - // } else { - // self.do_clear_origin()?; - // } - // // Process instructions. - // 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 - // }); - // // Reset origin to previous one. - // self.context.origin = previous_origin; - // result - // }, - // 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); - // 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 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::new(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::new(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::new(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. - // 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)?; - // // move these assets to the fees register for covering execution and paying - // // any subsequent fees - // message.push(PayFees { asset: fees }); - // } else { - // // unpaid execution - // message - // .push(UnpaidExecution { weight_limit: Unlimited, check_origin: None }); - // } - - // // 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 { - // // preserve current origin for subsequent user-controlled instructions on - // // remote chain - // 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 { - // // clear origin for subsequent user-controlled instructions on remote chain - // message.push(ClearOrigin); - // } - // // append custom instructions - // message.extend(remote_xcm.0.into_iter()); - // // send the onward XCM - // self.send(destination, Xcm::new(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::::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 } => { - // // Message was not weighed, there is nothing to pay. - // 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(()); - // } - // // 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, - // ); - // let max_fee = - // self.holding.try_take(asset.into()).map_err(|_| XcmError::NotHoldingFees)?; - // // Pay for execution fees. - // let result = Config::TransactionalProcessor::process(|| { - // let unspent = - // self.trader.buy_weight(self.message_weight, max_fee, &self.context)?; - // // Move unspent to the `fees` register. - // self.fees.subsume_assets(unspent); - // Ok(()) - // }); - // if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { - // // Rollback. - // 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(()) - // }, - // 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::, 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::new(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 } => { - // // 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(|()| 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::( - // 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::<()>::new(vec![NoteUnlockable { asset: remote_asset, owner }]); - // let (ticket, price) = validate_send::(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::<()>::new(vec![UnlockAsset { asset: remote_asset, target: remote_target }]); - // let (ticket, price) = validate_send::(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) - // }), - } + instr.execute(self) + // match instr { + // WithdrawAsset(assets) => { + // let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; + // self.ensure_can_subsume_assets(assets.len())?; + // Config::TransactionalProcessor::process(|| { + // // Take `assets` from the origin account (on-chain)... + // for asset in assets.inner() { + // Config::AssetTransactor::withdraw_asset( + // asset, + // origin, + // Some(&self.context), + // )?; + // } + // Ok(()) + // }) + // .and_then(|_| { + // // ...and place into holding. + // self.holding.subsume_assets(assets.into()); + // 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)?; + // 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)?; + // // Take `assets` from the origin account (on-chain) and place into dest account. + // 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::new(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(|| { + // // 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, .. } => { + // // We assume that the Relay-chain is allowed to use transact on this parachain. + // let origin = self.cloned_origin().ok_or_else(|| { + // tracing::trace!( + // target: "xcm::process_instruction::transact", + // "No origin provided", + // ); + + // XcmError::BadOrigin + // })?; + + // // TODO: #2841 #TRANSACTFILTER allow the trait to issue filters for the relay-chain + // 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); + // // 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 { descendant_origin, xcm } => { + // let previous_origin = self.context.origin.clone(); + + // // Set new temporary origin. + // if let Some(who) = descendant_origin { + // self.do_descend_origin(who)?; + // } else { + // self.do_clear_origin()?; + // } + // // Process instructions. + // 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 + // }); + // // Reset origin to previous one. + // self.context.origin = previous_origin; + // result + // }, + // 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); + // 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 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::new(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::new(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::new(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. + // 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)?; + // // move these assets to the fees register for covering execution and paying + // // any subsequent fees + // message.push(PayFees { asset: fees }); + // } else { + // // unpaid execution + // message + // .push(UnpaidExecution { weight_limit: Unlimited, check_origin: None }); + // } + + // // 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 { + // // preserve current origin for subsequent user-controlled instructions on + // // remote chain + // 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 { + // // clear origin for subsequent user-controlled instructions on remote chain + // message.push(ClearOrigin); + // } + // // append custom instructions + // message.extend(remote_xcm.0.into_iter()); + // // send the onward XCM + // self.send(destination, Xcm::new(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::::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 } => { + // // Message was not weighed, there is nothing to pay. + // 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(()); + // } + // // 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, + // ); + // let max_fee = + // self.holding.try_take(asset.into()).map_err(|_| XcmError::NotHoldingFees)?; + // // Pay for execution fees. + // let result = Config::TransactionalProcessor::process(|| { + // let unspent = + // self.trader.buy_weight(self.message_weight, max_fee, &self.context)?; + // // Move unspent to the `fees` register. + // self.fees.subsume_assets(unspent); + // Ok(()) + // }); + // if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { + // // Rollback. + // 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(()) + // }, + // 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::, 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::new(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 } => { + // // 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(|()| 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::( + // 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::<()>::new(vec![NoteUnlockable { asset: remote_asset, owner }]); + // let (ticket, price) = validate_send::(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::<()>::new(vec![UnlockAsset { asset: remote_asset, target: remote_target }]); + // let (ticket, price) = validate_send::(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 { @@ -1758,9 +1756,15 @@ impl XcmExecutor { 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).into_instruction(), - FeeReason::InitiateReserveWithdraw => WithdrawAsset(to_weigh_reanchored).into_instruction(), - FeeReason::InitiateTeleport => ReceiveTeleportedAsset(to_weigh_reanchored).into_instruction(), + FeeReason::DepositReserveAsset => { + ReserveAssetDeposited(to_weigh_reanchored).into_instruction() + }, + FeeReason::InitiateReserveWithdraw => { + WithdrawAsset(to_weigh_reanchored).into_instruction() + }, + FeeReason::InitiateTeleport => { + ReceiveTeleportedAsset(to_weigh_reanchored).into_instruction() + }, _ => { tracing::debug!( target: "xcm::take_delivery_fee_from_assets",