referrerpolicy=no-referrer-when-downgrade

staging_xcm_executor/
lib.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17#![cfg_attr(not(feature = "std"), no_std)]
18
19extern crate alloc;
20extern crate core;
21
22use alloc::{vec, vec::Vec};
23use codec::{Decode, Encode};
24use core::{fmt::Debug, marker::PhantomData};
25use frame_support::{
26	dispatch::GetDispatchInfo,
27	ensure,
28	traits::{Contains, ContainsPair, Defensive, Get, PalletsInfoAccess},
29};
30use sp_core::defer;
31use sp_io::hashing::blake2_128;
32use sp_weights::Weight;
33use xcm::{
34	latest::{prelude::*, AssetTransferFilter},
35	RECURSION_LIMIT,
36};
37
38pub mod traits;
39use traits::{
40	validate_export, AssetExchange, AssetLock, CallDispatcher, ClaimAssets, ConvertOrigin,
41	DropAssets, Enact, EventEmitter, ExportXcm, FeeManager, FeeReason, HandleHrmpChannelAccepted,
42	HandleHrmpChannelClosing, HandleHrmpNewChannelOpenRequest, OnResponse, ProcessTransaction,
43	Properties, ShouldExecute, TransactAsset, VersionChangeNotifier, WeightBounds, WeightTrader,
44	XcmAssetTransfers,
45};
46
47pub use traits::RecordXcm;
48
49mod assets;
50pub use assets::AssetsInHolding;
51mod config;
52use crate::assets::BackupAssetsInHolding;
53pub use config::Config;
54
55pub mod test_helpers;
56#[cfg(test)]
57mod tests;
58
59/// A struct to specify how fees are being paid.
60#[derive(Copy, Clone, Debug, PartialEq, Eq)]
61pub struct FeesMode {
62	/// If true, then the fee assets are taken directly from the origin's on-chain account,
63	/// otherwise the fee assets are taken from the holding register.
64	///
65	/// Defaults to false.
66	pub jit_withdraw: bool,
67}
68
69environmental::environmental!(recursion_count: u8);
70
71/// The XCM executor.
72pub struct XcmExecutor<Config: config::Config> {
73	holding: AssetsInHolding,
74	holding_limit: usize,
75	context: XcmContext,
76	original_origin: Location,
77	trader: Config::Trader,
78	/// The most recent error result and instruction index into the fragment in which it occurred,
79	/// if any.
80	error: Option<(u32, XcmError)>,
81	/// The surplus weight, defined as the amount by which `max_weight` is
82	/// an over-estimate of the actual weight consumed. We do it this way to avoid needing the
83	/// execution engine to keep track of all instructions' weights (it only needs to care about
84	/// the weight of dynamically determined instructions such as `Transact`).
85	total_surplus: Weight,
86	total_refunded: Weight,
87	error_handler: Xcm<Config::RuntimeCall>,
88	error_handler_weight: Weight,
89	appendix: Xcm<Config::RuntimeCall>,
90	appendix_weight: Weight,
91	transact_status: MaybeErrorCode,
92	fees_mode: FeesMode,
93	fees: AssetsInHolding,
94	/// Asset provided in last `BuyExecution` instruction (if any) in current XCM program. Same
95	/// asset type will be used for paying any potential delivery fees incurred by the program.
96	asset_used_in_buy_execution: Option<AssetId>,
97	/// Stores the current message's weight.
98	message_weight: Weight,
99	asset_claimer: Option<Location>,
100	already_paid_fees: bool,
101	/// A failed delivery recorded by [`Self::send`] during the current instruction, as
102	/// `(destination, error, message_id)`.
103	///
104	/// The send failure event is not emitted from within [`Self::send`] because that always runs
105	/// inside a transactional scope which is rolled back on failure, together with any event
106	/// deposited inside it. [`Self::process`] emits it once the instruction has returned, outside
107	/// of that scope.
108	pending_send_failure: Option<(Location, SendError, XcmHash)>,
109	_config: PhantomData<Config>,
110}
111
112#[cfg(any(test, feature = "runtime-benchmarks"))]
113impl<Config: config::Config> XcmExecutor<Config> {
114	pub fn holding(&self) -> &AssetsInHolding {
115		&self.holding
116	}
117	pub fn set_holding(&mut self, v: AssetsInHolding) {
118		self.holding = v
119	}
120	pub fn holding_limit(&self) -> &usize {
121		&self.holding_limit
122	}
123	pub fn set_holding_limit(&mut self, v: usize) {
124		self.holding_limit = v
125	}
126	pub fn origin(&self) -> &Option<Location> {
127		&self.context.origin
128	}
129	pub fn set_origin(&mut self, v: Option<Location>) {
130		self.context.origin = v
131	}
132	pub fn original_origin(&self) -> &Location {
133		&self.original_origin
134	}
135	pub fn set_original_origin(&mut self, v: Location) {
136		self.original_origin = v
137	}
138	pub fn trader(&self) -> &Config::Trader {
139		&self.trader
140	}
141	pub fn set_trader(&mut self, v: Config::Trader) {
142		self.trader = v
143	}
144	pub fn error(&self) -> &Option<(u32, XcmError)> {
145		&self.error
146	}
147	pub fn set_error(&mut self, v: Option<(u32, XcmError)>) {
148		self.error = v
149	}
150	pub fn total_surplus(&self) -> &Weight {
151		&self.total_surplus
152	}
153	pub fn set_total_surplus(&mut self, v: Weight) {
154		self.total_surplus = v
155	}
156	pub fn total_refunded(&self) -> &Weight {
157		&self.total_refunded
158	}
159	pub fn set_total_refunded(&mut self, v: Weight) {
160		self.total_refunded = v
161	}
162	pub fn error_handler(&self) -> &Xcm<Config::RuntimeCall> {
163		&self.error_handler
164	}
165	pub fn set_error_handler(&mut self, v: Xcm<Config::RuntimeCall>) {
166		self.error_handler = v
167	}
168	pub fn error_handler_weight(&self) -> &Weight {
169		&self.error_handler_weight
170	}
171	pub fn set_error_handler_weight(&mut self, v: Weight) {
172		self.error_handler_weight = v
173	}
174	pub fn appendix(&self) -> &Xcm<Config::RuntimeCall> {
175		&self.appendix
176	}
177	pub fn set_appendix(&mut self, v: Xcm<Config::RuntimeCall>) {
178		self.appendix = v
179	}
180	pub fn appendix_weight(&self) -> &Weight {
181		&self.appendix_weight
182	}
183	pub fn set_appendix_weight(&mut self, v: Weight) {
184		self.appendix_weight = v
185	}
186	pub fn transact_status(&self) -> &MaybeErrorCode {
187		&self.transact_status
188	}
189	pub fn set_transact_status(&mut self, v: MaybeErrorCode) {
190		self.transact_status = v
191	}
192	pub fn fees_mode(&self) -> &FeesMode {
193		&self.fees_mode
194	}
195	pub fn set_fees_mode(&mut self, v: FeesMode) {
196		self.fees_mode = v
197	}
198	pub fn fees(&self) -> &AssetsInHolding {
199		&self.fees
200	}
201	pub fn set_fees(&mut self, value: AssetsInHolding) {
202		self.fees = value;
203	}
204	pub fn topic(&self) -> &Option<[u8; 32]> {
205		&self.context.topic
206	}
207	pub fn set_topic(&mut self, v: Option<[u8; 32]>) {
208		self.context.topic = v;
209	}
210	pub fn asset_claimer(&self) -> Option<Location> {
211		self.asset_claimer.clone()
212	}
213	pub fn set_message_weight(&mut self, weight: Weight) {
214		self.message_weight = weight;
215	}
216	pub fn already_paid_fees(&self) -> bool {
217		self.already_paid_fees
218	}
219	pub fn asset_used_in_buy_execution(&self) -> &Option<AssetId> {
220		&self.asset_used_in_buy_execution
221	}
222}
223
224pub struct WeighedMessage<Call>(Weight, Xcm<Call>);
225impl<C> PreparedMessage for WeighedMessage<C> {
226	fn weight_of(&self) -> Weight {
227		self.0
228	}
229}
230
231#[cfg(any(test, feature = "std"))]
232impl<C> WeighedMessage<C> {
233	pub fn new(weight: Weight, message: Xcm<C>) -> Self {
234		Self(weight, message)
235	}
236}
237
238impl<Config: config::Config> ExecuteXcm<Config::RuntimeCall> for XcmExecutor<Config> {
239	type Prepared = WeighedMessage<Config::RuntimeCall>;
240	fn prepare(
241		mut message: Xcm<Config::RuntimeCall>,
242		weight_limit: Weight,
243	) -> Result<Self::Prepared, InstructionError> {
244		match Config::Weigher::weight(&mut message, weight_limit) {
245			Ok(weight) => Ok(WeighedMessage(weight, message)),
246			Err(error) => {
247				tracing::debug!(
248					target: "xcm::prepare",
249					?error,
250					?message,
251					"Failed to calculate weight for XCM message; execution aborted"
252				);
253				Err(error)
254			},
255		}
256	}
257	fn execute(
258		origin: impl Into<Location>,
259		WeighedMessage(xcm_weight, mut message): WeighedMessage<Config::RuntimeCall>,
260		id: &mut XcmHash,
261		weight_credit: Weight,
262	) -> Outcome {
263		let origin = origin.into();
264		tracing::trace!(
265			target: "xcm::execute",
266			?origin,
267			?message,
268			?id,
269			?weight_credit,
270			"Executing message",
271		);
272		let mut properties = Properties { weight_credit, message_id: None };
273
274		// We only want to record under certain conditions (mainly only during dry-running),
275		// so as to not degrade regular performance.
276		if Config::XcmRecorder::should_record() {
277			Config::XcmRecorder::record(message.clone().into());
278		}
279
280		if let Err(e) = Config::Barrier::should_execute(
281			&origin,
282			message.inner_mut(),
283			xcm_weight,
284			&mut properties,
285		) {
286			tracing::trace!(
287				target: "xcm::execute",
288				?origin,
289				?message,
290				?properties,
291				error = ?e,
292				"Barrier blocked execution",
293			);
294
295			return Outcome::Incomplete {
296				used: xcm_weight, // Weight consumed before the error
297				error: InstructionError { index: 0, error: XcmError::Barrier }, // The error that occurred
298			};
299		}
300
301		*id = properties.message_id.unwrap_or(*id);
302
303		let mut vm = Self::new(origin, *id);
304		vm.message_weight = xcm_weight;
305
306		while !message.0.is_empty() {
307			let result = vm.process(message);
308			tracing::trace!(target: "xcm::execute", ?result, "Message executed");
309			message = match result {
310				Err(error) => {
311					vm.total_surplus.saturating_accrue(error.weight);
312					vm.error = Some((error.index, error.xcm_error));
313					vm.take_error_handler().or_else(|| vm.take_appendix())
314				},
315				Ok(()) => {
316					vm.drop_error_handler();
317					vm.take_appendix()
318				},
319			}
320		}
321
322		vm.post_process(xcm_weight)
323	}
324
325	fn charge_fees(origin: impl Into<Location>, fees: Assets) -> XcmResult {
326		let origin = origin.into();
327		if !Config::FeeManager::is_waived(Some(&origin), FeeReason::ChargeFees) {
328			let mut charged = AssetsInHolding::new();
329			for asset in fees.inner() {
330				let withdrawn = Config::AssetTransactor::withdraw_asset(&asset, &origin, None)?;
331				charged.subsume_assets(withdrawn);
332			}
333			Config::FeeManager::handle_fee(charged, None, FeeReason::ChargeFees);
334		}
335		Ok(())
336	}
337}
338
339impl<Config: config::Config> XcmAssetTransfers for XcmExecutor<Config> {
340	type IsReserve = Config::IsReserve;
341	type IsTeleporter = Config::IsTeleporter;
342	type AssetTransactor = Config::AssetTransactor;
343}
344
345impl<Config: config::Config> FeeManager for XcmExecutor<Config> {
346	fn is_waived(origin: Option<&Location>, r: FeeReason) -> bool {
347		Config::FeeManager::is_waived(origin, r)
348	}
349
350	fn handle_fee(fee: AssetsInHolding, context: Option<&XcmContext>, r: FeeReason) {
351		Config::FeeManager::handle_fee(fee, context, r)
352	}
353}
354
355#[derive(Debug, PartialEq)]
356pub struct ExecutorError {
357	pub index: u32,
358	pub xcm_error: XcmError,
359	pub weight: Weight,
360}
361
362#[cfg(feature = "runtime-benchmarks")]
363impl From<ExecutorError> for frame_benchmarking::BenchmarkError {
364	fn from(error: ExecutorError) -> Self {
365		tracing::error!(
366			index = ?error.index,
367			xcm_error = ?error.xcm_error,
368			weight = ?error.weight,
369			"XCM ERROR",
370		);
371		Self::Stop("xcm executor error: see error logs")
372	}
373}
374
375impl<Config: config::Config> XcmExecutor<Config> {
376	pub fn new(origin: impl Into<Location>, message_id: XcmHash) -> Self {
377		let origin = origin.into();
378		Self {
379			holding: AssetsInHolding::new(),
380			holding_limit: Config::MaxAssetsIntoHolding::get() as usize,
381			context: XcmContext { origin: Some(origin.clone()), message_id, topic: None },
382			original_origin: origin,
383			trader: Config::Trader::new(),
384			error: None,
385			total_surplus: Weight::zero(),
386			total_refunded: Weight::zero(),
387			error_handler: Xcm(vec![]),
388			error_handler_weight: Weight::zero(),
389			appendix: Xcm(vec![]),
390			appendix_weight: Weight::zero(),
391			transact_status: Default::default(),
392			fees_mode: FeesMode { jit_withdraw: false },
393			fees: AssetsInHolding::new(),
394			asset_used_in_buy_execution: None,
395			message_weight: Weight::zero(),
396			asset_claimer: None,
397			already_paid_fees: false,
398			pending_send_failure: None,
399			_config: PhantomData,
400		}
401	}
402
403	/// Execute any final operations after having executed the XCM message.
404	/// This includes refunding surplus weight, trapping extra holding funds, and returning any
405	/// errors during execution.
406	pub fn post_process(mut self, xcm_weight: Weight) -> Outcome {
407		// We silently drop any error from our attempt to refund the surplus as it's a charitable
408		// thing so best-effort is all we will do.
409		let _ = self.refund_surplus();
410		drop(self.trader);
411
412		let mut weight_used = xcm_weight.saturating_sub(self.total_surplus);
413
414		if !self.holding.is_empty() {
415			tracing::trace!(
416				target: "xcm::post_process",
417				holding_register = ?self.holding,
418				context = ?self.context,
419				original_origin = ?self.original_origin,
420				"Trapping assets in holding register",
421			);
422			let claimer = self
423				.asset_claimer
424				.as_ref()
425				.or(self.context.origin.as_ref())
426				.unwrap_or(&self.original_origin);
427			let trap_weight = Config::AssetTrap::drop_assets(claimer, self.holding, &self.context);
428			weight_used.saturating_accrue(trap_weight);
429		};
430
431		match self.error {
432			None => Outcome::Complete { used: weight_used },
433			// TODO: #2841 #REALWEIGHT We should deduct the cost of any instructions following
434			// the error which didn't end up being executed.
435			Some((index, error)) => {
436				tracing::trace!(
437					target: "xcm::post_process",
438					instruction = ?index,
439					?error,
440					original_origin = ?self.original_origin,
441					"Execution failed",
442				);
443				Outcome::Incomplete {
444					used: weight_used,
445					error: InstructionError { index: index.try_into().unwrap_or(u8::MAX), error },
446				}
447			},
448		}
449	}
450
451	fn origin_ref(&self) -> Option<&Location> {
452		self.context.origin.as_ref()
453	}
454
455	fn cloned_origin(&self) -> Option<Location> {
456		self.context.origin.clone()
457	}
458
459	/// Send an XCM, charging fees from Holding as needed.
460	///
461	/// Note: Must be called within [`Self::transactional_process`]. On a failed fee swap,
462	/// `take_fee` leaves the fee asset in the `fees` register, and on a failed delivery the fee
463	/// stays charged. The caller is responsible for rollback of the registers and storage.
464	///
465	/// A failed delivery is recorded in `pending_send_failure` rather than emitted, so that the
466	/// event is not rolled back with the instruction. [`Self::process`] emits it.
467	fn send(
468		&mut self,
469		dest: Location,
470		msg: Xcm<()>,
471		reason: FeeReason,
472	) -> Result<XcmHash, XcmError> {
473		let mut msg = msg;
474		// Only the last `SetTopic` instruction is considered relevant. If the message does not end
475		// with it, a `topic_or_message_id()` from the context is appended to it. This behaviour is
476		// then consistent with `WithUniqueTopic`.
477		if !matches!(msg.last(), Some(SetTopic(_))) {
478			let topic_id = self.context.topic_or_message_id();
479			msg.0.push(SetTopic(topic_id.into()));
480		}
481		tracing::trace!(
482			target: "xcm::send",
483			?msg,
484			destination = ?dest,
485			reason = ?reason,
486			"Sending msg",
487		);
488		let (ticket, fee) = validate_send::<Config::XcmSender>(dest.clone(), msg)?;
489		self.take_fee(fee, reason)?;
490		match Config::XcmSender::deliver(ticket) {
491			Ok(message_id) => {
492				Config::XcmEventEmitter::emit_sent_event(
493					self.original_origin.clone(),
494					dest,
495					None, /* Avoid logging the full XCM message to prevent inconsistencies and
496					       * reduce storage usage. */
497					message_id,
498				);
499				Ok(message_id)
500			},
501			Err(error) => {
502				tracing::debug!(target: "xcm::send", ?error, "XCM failed to deliver with error");
503				// Emitting the event here would get it rolled back together with the rest of the
504				// failed instruction, so defer it to `process`, which emits it outside the
505				// transactional scope.
506				self.pending_send_failure =
507					Some((dest, error.clone(), self.context.topic_or_message_id()));
508				Err(error.into())
509			},
510		}
511	}
512
513	/// Remove the registered error handler and return it. Do not refund its weight.
514	fn take_error_handler(&mut self) -> Xcm<Config::RuntimeCall> {
515		let mut r = Xcm::<Config::RuntimeCall>(vec![]);
516		core::mem::swap(&mut self.error_handler, &mut r);
517		self.error_handler_weight = Weight::zero();
518		r
519	}
520
521	/// Drop the registered error handler and refund its weight.
522	fn drop_error_handler(&mut self) {
523		self.error_handler = Xcm::<Config::RuntimeCall>(vec![]);
524		self.total_surplus.saturating_accrue(self.error_handler_weight);
525		self.error_handler_weight = Weight::zero();
526	}
527
528	/// Remove the registered appendix and return it.
529	fn take_appendix(&mut self) -> Xcm<Config::RuntimeCall> {
530		let mut r = Xcm::<Config::RuntimeCall>(vec![]);
531		core::mem::swap(&mut self.appendix, &mut r);
532		self.appendix_weight = Weight::zero();
533		r
534	}
535
536	fn ensure_can_subsume_assets(&self, assets_length: usize) -> Result<(), XcmError> {
537		// worst-case, holding.len becomes 2 * holding_limit.
538		// this guarantees that if holding.len() == holding_limit and you have more than
539		// `holding_limit` items (which has a best case outcome of holding.len() == holding_limit),
540		// then the operation is guaranteed to succeed.
541		let worst_case_holding_len = self.holding.len() + assets_length;
542		tracing::trace!(
543			target: "xcm::ensure_can_subsume_assets",
544			?worst_case_holding_len,
545			holding_limit = ?self.holding_limit,
546			"Ensuring subsume assets work",
547		);
548		ensure!(worst_case_holding_len <= self.holding_limit * 2, XcmError::HoldingWouldOverflow);
549		Ok(())
550	}
551
552	/// Refund any unused weight.
553	fn refund_surplus(&mut self) -> Result<(), XcmError> {
554		let current_surplus = self.total_surplus.saturating_sub(self.total_refunded);
555		tracing::trace!(
556			target: "xcm::refund_surplus",
557			total_surplus = ?self.total_surplus,
558			total_refunded = ?self.total_refunded,
559			?current_surplus,
560			"Refunding surplus",
561		);
562		if current_surplus.any_gt(Weight::zero()) {
563			if let Some(refund) = self.trader.refund_weight(current_surplus, &self.context) {
564				// Check if adding the refund would overflow holding. This can happen if the
565				// refund asset is not already in holding and holding is at max capacity.
566				if refund
567					.fungible
568					.first_key_value()
569					.map(|(id, _)| {
570						!self.holding.fungible.contains_key(id) &&
571							self.ensure_can_subsume_assets(1).is_err()
572					})
573					.unwrap_or(false)
574				{
575					// Can't add refund to holding - undo by buying back the weight.
576					// This returns the refund credit to the trader where it will be
577					// handled by OnUnbalanced when the trader is dropped.
578					let _ = self
579						.trader
580						.buy_weight(current_surplus, refund, &self.context)
581						.defensive_proof(
582							"refund_weight returned an asset capable of buying weight; qed",
583						);
584					tracing::error!(
585						target: "xcm::refund_surplus",
586						"error: HoldingWouldOverflow",
587					);
588					return Err(XcmError::HoldingWouldOverflow);
589				}
590				self.total_refunded.saturating_accrue(current_surplus);
591				self.holding.subsume_assets(refund);
592			}
593		}
594		// If there are any leftover `fees`, merge them with `holding`.
595		if !self.fees.is_empty() {
596			let leftover_fees = self.fees.saturating_take(Wild(All));
597			tracing::trace!(
598				target: "xcm::refund_surplus",
599				?leftover_fees,
600			);
601			self.holding.subsume_assets(leftover_fees);
602		}
603		tracing::trace!(
604			target: "xcm::refund_surplus",
605			total_refunded = ?self.total_refunded,
606		);
607		Ok(())
608	}
609
610	/// Takes `fees` from holding or fees registers.
611	/// Note: Should be called under a transactional processor to ensure storage noop on failures.
612	fn take_fee(&mut self, fees: Assets, reason: FeeReason) -> XcmResult {
613		if Config::FeeManager::is_waived(self.origin_ref(), reason.clone()) {
614			return Ok(());
615		}
616		tracing::trace!(
617			target: "xcm::fees",
618			?fees,
619			origin_ref = ?self.origin_ref(),
620			fees_mode = ?self.fees_mode,
621			?reason,
622			"Taking fees",
623		);
624		// We only ever use the first asset from `fees`.
625		let Some(asset_needed_for_fees) = fees.get(0) else {
626			return Ok(()); // No delivery fees need to be paid.
627		};
628		// If `BuyExecution` or `PayFees` was called, we use that asset for delivery fees as well.
629		let asset_to_pay_for_fees =
630			self.calculate_asset_for_delivery_fees(asset_needed_for_fees.clone());
631		tracing::trace!(target: "xcm::fees", ?asset_to_pay_for_fees);
632		// We withdraw or take from holding the asset the user wants to use for fee payment.
633		let withdrawn_fee_asset: AssetsInHolding = if self.fees_mode.jit_withdraw {
634			let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
635			let credit = Config::AssetTransactor::withdraw_asset(
636				&asset_to_pay_for_fees,
637				origin,
638				Some(&self.context),
639			)?;
640			tracing::trace!(target: "xcm::fees", ?asset_needed_for_fees);
641			credit
642		} else {
643			// This condition exists to support `BuyExecution` while the ecosystem
644			// transitions to `PayFees`.
645			let assets_to_pay_delivery_fees: AssetsInHolding = if self.fees.is_empty() {
646				// Means `BuyExecution` was used, we'll find the fees in the `holding` register.
647				self.holding.try_take(asset_to_pay_for_fees.clone().into()).map_err(|e| {
648					tracing::error!(target: "xcm::fees", ?e, ?asset_to_pay_for_fees,
649							"Holding doesn't hold enough for fees");
650					XcmError::NotHoldingFees
651				})?
652			} else {
653				// Means `PayFees` was used, we'll find the fees in the `fees` register.
654				self.fees.try_take(asset_to_pay_for_fees.clone().into()).map_err(|e| {
655					tracing::error!(target: "xcm::fees", ?e, ?asset_to_pay_for_fees,
656							"Fees register doesn't hold enough for fees");
657					XcmError::NotHoldingFees
658				})?
659			};
660			tracing::trace!(target: "xcm::fees", ?assets_to_pay_delivery_fees);
661			assets_to_pay_delivery_fees
662		};
663		// We perform the swap, if needed, to pay fees.
664		let paid = if asset_to_pay_for_fees.id != asset_needed_for_fees.id {
665			Config::AssetExchanger::exchange_asset(
666				self.origin_ref(),
667				withdrawn_fee_asset,
668				&asset_needed_for_fees.clone().into(),
669				false,
670			)
671			.map_err(|given_assets| {
672				tracing::error!(
673					target: "xcm::fees",
674					?given_assets, ?asset_needed_for_fees, "Swap was deemed necessary but couldn't be done:",
675				);
676				self.fees.subsume_assets(given_assets);
677				XcmError::FeesNotMet
678			})?
679		} else {
680			// If the asset wanted to pay for fees is the one that was needed,
681			// we don't need to do any swap.
682			// We just use the assets withdrawn or taken from holding.
683			withdrawn_fee_asset
684		};
685		Config::FeeManager::handle_fee(paid, Some(&self.context), reason);
686		Ok(())
687	}
688
689	/// Calculates the amount of asset used in `PayFees` or `BuyExecution` that would be
690	/// charged for swapping to `asset_needed_for_fees`.
691	///
692	/// The calculation is done by `Config::AssetExchanger`.
693	/// If neither `PayFees` or `BuyExecution` were used, or no swap is required,
694	/// it will just return `asset_needed_for_fees`.
695	fn calculate_asset_for_delivery_fees(&self, asset_needed_for_fees: Asset) -> Asset {
696		let Some(asset_wanted_for_fees) =
697			// we try to swap first asset in the fees register (should only ever be one),
698			self.fees.fungible.first_key_value().map(|(id, _)| id).or_else(|| {
699				// or the one used in BuyExecution
700				self.asset_used_in_buy_execution.as_ref()
701			})
702			// if it is different than what we need
703			.filter(|&id| asset_needed_for_fees.id.ne(id))
704		else {
705			// either nothing to swap or we're already holding the right asset
706			return asset_needed_for_fees
707		};
708		Config::AssetExchanger::quote_exchange_price(
709			&(asset_wanted_for_fees.clone(), Fungible(0)).into(),
710			&asset_needed_for_fees.clone().into(),
711			false, // Minimal.
712		)
713		.and_then(|necessary_assets| {
714			// We only use the first asset for fees.
715			// If this is not enough to swap for the fee asset then it will error later down
716			// the line.
717			necessary_assets.into_inner().into_iter().next()
718		})
719		.unwrap_or_else(|| {
720			// If we can't convert, then we return the original asset.
721			// It will error later in any case.
722			tracing::trace!(
723				target: "xcm::calculate_asset_for_delivery_fees",
724				?asset_wanted_for_fees, "Could not convert fees",
725			);
726			asset_needed_for_fees
727		})
728	}
729
730	/// Calculates what `local_querier` would be from the perspective of `destination`.
731	fn to_querier(
732		local_querier: Option<Location>,
733		destination: &Location,
734	) -> Result<Option<Location>, XcmError> {
735		Ok(match local_querier {
736			None => None,
737			Some(q) => Some(
738				q.reanchored(&destination, &Config::UniversalLocation::get()).map_err(|e| {
739					tracing::error!(target: "xcm::xcm_executor::to_querier", ?e, ?destination, "Failed to re-anchor local_querier");
740					XcmError::ReanchorFailed
741				})?,
742			),
743		})
744	}
745
746	/// Send a bare `QueryResponse` message containing `response` informed by the given `info`.
747	///
748	/// The `local_querier` argument is the querier (if any) specified from the *local* perspective.
749	fn respond(
750		&mut self,
751		local_querier: Option<Location>,
752		response: Response,
753		info: QueryResponseInfo,
754		fee_reason: FeeReason,
755	) -> Result<XcmHash, XcmError> {
756		let querier = Self::to_querier(local_querier, &info.destination)?;
757		let QueryResponseInfo { destination, query_id, max_weight } = info;
758		let instruction = QueryResponse { query_id, response, max_weight, querier };
759		let message = Xcm(vec![instruction]);
760		self.send(destination, message, fee_reason)
761	}
762
763	fn do_reserve_deposit_assets(
764		assets: AssetsInHolding,
765		dest: &Location,
766		remote_xcm: &mut Vec<Instruction<()>>,
767		context: Option<&XcmContext>,
768	) -> Result<Assets, XcmError> {
769		let reanchored_assets = Self::reanchored_assets(&assets, dest);
770		Self::deposit_assets_with_retry(assets, dest, context)?;
771		remote_xcm.push(ReserveAssetDeposited(reanchored_assets.clone()));
772
773		Ok(reanchored_assets)
774	}
775
776	fn do_reserve_withdraw_assets(
777		assets: AssetsInHolding,
778		failed_bin: &mut AssetsInHolding,
779		reserve: &Location,
780		remote_xcm: &mut Vec<Instruction<()>>,
781	) -> Result<Assets, XcmError> {
782		// Must ensure that we recognise the assets as being managed by the destination.
783		#[cfg(not(any(test, feature = "runtime-benchmarks")))]
784		for asset in assets.assets_iter() {
785			ensure!(
786				Config::IsReserve::contains(&asset, &reserve),
787				XcmError::UntrustedReserveLocation
788			);
789		}
790		// Note that here we are able to place any assets which could not be
791		// reanchored back into Holding (failed_bin).
792		let reanchored_assets =
793			assets.reanchor_and_burn_local(reserve, &Config::UniversalLocation::get(), failed_bin);
794		remote_xcm.push(WithdrawAsset(reanchored_assets.clone()));
795
796		Ok(reanchored_assets)
797	}
798
799	fn do_teleport_assets(
800		assets: AssetsInHolding,
801		dest: &Location,
802		remote_xcm: &mut Vec<Instruction<()>>,
803		context: &XcmContext,
804	) -> Result<Assets, XcmError> {
805		let reanchored_assets = Self::reanchored_assets(&assets, dest);
806		for asset in assets.assets_iter() {
807			// Must ensure that we have teleport trust with destination for these assets.
808			#[cfg(not(any(test, feature = "runtime-benchmarks")))]
809			ensure!(
810				Config::IsTeleporter::contains(&asset, &dest),
811				XcmError::UntrustedTeleportLocation
812			);
813			// We should check that the asset can actually be teleported out (for
814			// this to be in error, there would need to be an accounting violation
815			// by ourselves, so it's unlikely, but we don't want to allow that kind
816			// of bug to leak into a trusted chain.
817			Config::AssetTransactor::can_check_out(dest, &asset, context)?;
818		}
819		for asset in assets.assets_iter() {
820			Config::AssetTransactor::check_out(dest, &asset, context);
821		}
822		remote_xcm.push(ReceiveTeleportedAsset(reanchored_assets.clone()));
823
824		Ok(reanchored_assets)
825	}
826
827	fn try_reanchor<T: Reanchorable>(
828		reanchorable: T,
829		destination: &Location,
830	) -> Result<(T, InteriorLocation), XcmError> {
831		let reanchor_context = Config::UniversalLocation::get();
832		let reanchored =
833			reanchorable.reanchored(&destination, &reanchor_context).map_err(|error| {
834				tracing::error!(target: "xcm::reanchor", ?error, ?destination, ?reanchor_context, "Failed reanchoring with error.");
835				XcmError::ReanchorFailed
836			})?;
837		Ok((reanchored, reanchor_context))
838	}
839
840	/// NOTE: Any assets which were unable to be reanchored are introduced into `failed_bin`.
841	fn reanchored_assets(assets: &AssetsInHolding, dest: &Location) -> Assets {
842		assets.reanchored_assets(dest, &Config::UniversalLocation::get())
843	}
844
845	#[cfg(any(test, feature = "runtime-benchmarks"))]
846	pub fn bench_process(&mut self, xcm: Xcm<Config::RuntimeCall>) -> Result<(), ExecutorError> {
847		self.process(xcm)
848	}
849
850	#[cfg(any(test, feature = "runtime-benchmarks"))]
851	pub fn bench_post_process(self, xcm_weight: Weight) -> Outcome {
852		self.post_process(xcm_weight)
853	}
854
855	fn process(&mut self, xcm: Xcm<Config::RuntimeCall>) -> Result<(), ExecutorError> {
856		tracing::trace!(
857			target: "xcm::process",
858			origin = ?self.origin_ref(),
859			total_surplus = ?self.total_surplus,
860			total_refunded = ?self.total_refunded,
861			error_handler_weight = ?self.error_handler_weight,
862		);
863		let mut result = Ok(());
864		for (i, mut instr) in xcm.0.into_iter().enumerate() {
865			match &mut result {
866				r @ Ok(()) => {
867					// Initialize the recursion count only the first time we hit this code in our
868					// potential recursive execution.
869					let inst_res = recursion_count::using_once(&mut 1, || {
870						recursion_count::with(|count| {
871							if *count > RECURSION_LIMIT {
872								return None;
873							}
874							*count = count.saturating_add(1);
875							Some(())
876						})
877						.flatten()
878						.ok_or(XcmError::ExceedsStackLimit)?;
879
880						// Ensure that we always decrement the counter whenever we finish processing
881						// the instruction.
882						defer! {
883							recursion_count::with(|count| {
884								*count = count.saturating_sub(1);
885							});
886						}
887
888						self.process_instruction(instr)
889					});
890					// Emit any send failure of the instruction now that its transactional scope
891					// is closed, so that the event survives the rollback of the instruction.
892					if let Some((dest, error, message_id)) = self.pending_send_failure.take() {
893						Config::XcmEventEmitter::emit_send_failure_event(
894							self.original_origin.clone(),
895							dest,
896							error,
897							message_id,
898						);
899					}
900					if let Err(error) = inst_res {
901						tracing::debug!(
902							target: "xcm::process",
903							?error, "XCM execution failed at instruction index={i}"
904						);
905						Config::XcmEventEmitter::emit_process_failure_event(
906							self.original_origin.clone(),
907							error,
908							self.context.topic_or_message_id(),
909						);
910						*r = Err(ExecutorError {
911							index: i as u32,
912							xcm_error: error,
913							weight: Weight::zero(),
914						});
915					}
916				},
917				Err(ref mut error) => {
918					if let Ok(x) = Config::Weigher::instr_weight(&mut instr) {
919						error.weight.saturating_accrue(x)
920					}
921				},
922			}
923		}
924		result
925	}
926
927	/// Execute `f` inside a transactional context that backs up and restores `holding` and
928	/// `fees` on failure.
929	///
930	/// # Invariants
931	///
932	/// The register backup is only sound together with the storage rollback, and it only covers
933	/// the `holding` and `fees` registers. Code running inside `f` must respect the following:
934	///
935	/// - Every credit that leaves `holding` or `fees` inside `f` must, by the time `f` returns an
936	///   error, either be back in one of the two registers, be resolved into storage, or be dropped
937	///   inside `f`. All three are undone by a rollback in case of errors. A credit parked anywhere
938	///   else survives the rollback and is duplicated by the restored backup.
939	/// - The trader is not backed up. If `f` is using a trader, `Config::Trader::buy_weight` must
940	///   therefore be the last fallible operation of `f` (a failed purchase hands the payment back
941	///   untouched), and `Config::Trader::refund_weight` must never be called inside `f`: a refund
942	///   extracted from the trader cannot be undone when its storage effects are rolled back.
943	/// - Nothing else in the executor state is backed up. Any other register written inside `f`
944	///   must be written after the last fallible operation, or restored through
945	///   [`Self::transactional_process_with_custom_rollback`].
946	fn transactional_process(
947		&mut self,
948		f: impl FnOnce(&mut Self) -> Result<(), XcmError>,
949	) -> Result<(), XcmError> {
950		self.transactional_process_with_custom_rollback(f, |_| {})
951	}
952
953	/// Like [`Self::transactional_process`], but also calls `on_rollback` when the
954	/// transaction is rolled back.
955	///
956	/// NOTE: holding and fees registers are already automatically rolled back. Custom handler
957	/// is for _extra_ rollback logic.
958	fn transactional_process_with_custom_rollback(
959		&mut self,
960		f: impl FnOnce(&mut Self) -> Result<(), XcmError>,
961		on_rollback: impl FnOnce(&mut Self),
962	) -> Result<(), XcmError> {
963		let mut backup_holding = BackupAssetsInHolding::safe_backup(&self.holding);
964		let mut backup_fees = BackupAssetsInHolding::safe_backup(&self.fees);
965		let result = Config::TransactionalProcessor::process(|| f(self));
966		if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() {
967			backup_holding.restore_into(&mut self.holding);
968			backup_fees.restore_into(&mut self.fees);
969			on_rollback(self);
970		}
971		result
972	}
973
974	/// Process a single XCM instruction, mutating the state of the XCM virtual machine.
975	fn process_instruction(
976		&mut self,
977		instr: Instruction<Config::RuntimeCall>,
978	) -> Result<(), XcmError> {
979		tracing::trace!(
980			target: "xcm::process_instruction",
981			instruction = ?instr,
982			"Processing instruction",
983		);
984
985		match instr {
986			WithdrawAsset(assets) => {
987				self.ensure_can_subsume_assets(assets.len())?;
988				Config::TransactionalProcessor::process(|| {
989					let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
990					let mut total_surplus = Weight::zero();
991					let mut withdrawn = AssetsInHolding::new();
992					// Take `assets` from the origin account (on-chain)...
993					for asset in assets.inner() {
994						let (credit, surplus) = Config::AssetTransactor::withdraw_asset_with_surplus(
995							asset,
996							origin,
997							Some(&self.context),
998						)?;
999						withdrawn.subsume_assets(credit);
1000						// If we have some surplus, aggregate it.
1001						total_surplus.saturating_accrue(surplus);
1002					}
1003					// ...and place into holding.
1004					self.holding.subsume_assets(withdrawn);
1005					// Credit the total surplus.
1006					self.total_surplus.saturating_accrue(total_surplus);
1007					Ok(())
1008				})
1009			},
1010			ReserveAssetDeposited(assets) => {
1011				self.ensure_can_subsume_assets(assets.len())?;
1012				Config::TransactionalProcessor::process(|| {
1013					// Check whether we trust origin to be our reserve location for this asset.
1014					let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
1015					// Collect all minted assets first, then add to holding atomically.
1016					// This ensures partial mints don't pollute holding if a later mint fails. If one of them does fail,
1017					// TransactionalProcessor makes sure the imbalance changes do not get committed.
1018					let mut minted_assets = AssetsInHolding::new();
1019					for asset in assets.inner() {
1020						// Must ensure that we recognise the asset as being managed by the origin.
1021						ensure!(
1022							Config::IsReserve::contains(asset, origin),
1023							XcmError::UntrustedReserveLocation
1024						);
1025						Config::AssetTransactor::mint_asset(asset, &self.context)
1026							.map(|minted| minted_assets.subsume_assets(minted))?;
1027					}
1028					self.holding.subsume_assets(minted_assets);
1029					Ok(())
1030				})
1031			},
1032			TransferAsset { assets, beneficiary } => {
1033				Config::TransactionalProcessor::process(|| {
1034					// Take `assets` from the origin account (on-chain) and place into dest account.
1035					let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
1036					let mut total_surplus = Weight::zero();
1037					for asset in assets.inner() {
1038						let (_, surplus) = Config::AssetTransactor::transfer_asset_with_surplus(
1039							&asset,
1040							origin,
1041							&beneficiary,
1042							&self.context,
1043						)?;
1044						// If we have some surplus, aggregate it.
1045						total_surplus.saturating_accrue(surplus);
1046					}
1047					// Credit the total surplus.
1048					self.total_surplus.saturating_accrue(total_surplus);
1049					Ok(())
1050				})
1051			},
1052			TransferReserveAsset { mut assets, dest, xcm } => {
1053				self.transactional_process(|self_ref| {
1054					let origin = self_ref.origin_ref().ok_or(XcmError::BadOrigin)?;
1055					let mut total_surplus = Weight::zero();
1056					// Take `assets` from the origin account (on-chain) and place into dest account.
1057					for asset in assets.inner() {
1058						let (_, surplus) = Config::AssetTransactor::transfer_asset_with_surplus(
1059							asset,
1060							origin,
1061							&dest,
1062							&self_ref.context,
1063						)?;
1064						// If we have some surplus, aggregate it.
1065						total_surplus.saturating_accrue(surplus);
1066					}
1067					let reanchor_context = Config::UniversalLocation::get();
1068					assets
1069						.reanchor(&dest, &reanchor_context)
1070						.map_err(|()| {
1071							tracing::debug!(
1072								target: "xcm::process_instruction::transfer_reserve_asset",
1073								?assets,
1074								?dest,
1075								?reanchor_context,
1076								"Failed to reanchor assets to destination in context"
1077							);
1078							XcmError::LocationFull
1079						})?;
1080					let mut message = vec![ReserveAssetDeposited(assets), ClearOrigin];
1081					message.extend(xcm.0.into_iter());
1082					self_ref.send(dest, Xcm(message), FeeReason::TransferReserveAsset)?;
1083					// Credit the total surplus.
1084					self_ref.total_surplus.saturating_accrue(total_surplus);
1085					Ok(())
1086				})
1087			},
1088			ReceiveTeleportedAsset(assets) => {
1089				self.ensure_can_subsume_assets(assets.len())?;
1090				Config::TransactionalProcessor::process(|| {
1091					let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
1092					let mut minted_assets = AssetsInHolding::new();
1093					// Check whether we trust origin to teleport this asset to us via config trait.
1094					for asset in assets.inner() {
1095						// We only trust the origin to send us assets that they identify as their
1096						// sovereign assets.
1097						ensure!(
1098							Config::IsTeleporter::contains(asset, origin),
1099							XcmError::UntrustedTeleportLocation
1100						);
1101						// We should check that the asset can actually be teleported in (for this to
1102						// be in error, there would need to be an accounting violation by one of the
1103						// trusted chains, so it's unlikely, but we don't want to punish a possibly
1104						// innocent chain/user).
1105						Config::AssetTransactor::can_check_in(origin, asset, &self.context)?;
1106						Config::AssetTransactor::check_in(origin, asset, &self.context);
1107						Config::AssetTransactor::mint_asset(asset, &self.context)
1108							.map(|minted| minted_assets.subsume_assets(minted))?;
1109					}
1110					self.holding.subsume_assets(minted_assets);
1111					Ok(())
1112				})
1113			},
1114			// `fallback_max_weight` is not used in the executor, it's only for conversions.
1115			Transact { origin_kind, call, .. } => {
1116				let origin = self.cloned_origin().ok_or_else(|| {
1117					tracing::trace!(
1118						target: "xcm::process_instruction::transact",
1119						"No origin provided",
1120					);
1121
1122					XcmError::BadOrigin
1123				})?;
1124
1125				let message_call = call.try_into().map_err(|_| {
1126					tracing::trace!(
1127						target: "xcm::process_instruction::transact",
1128						"Failed to decode call",
1129					);
1130
1131					XcmError::FailedToDecode
1132				})?;
1133
1134				tracing::trace!(
1135					target: "xcm::process_instruction::transact",
1136					?message_call,
1137					"Processing call",
1138				);
1139
1140				if !Config::SafeCallFilter::contains(&message_call) {
1141					tracing::trace!(
1142						target: "xcm::process_instruction::transact",
1143						"Call filtered by `SafeCallFilter`",
1144					);
1145
1146					return Err(XcmError::NoPermission)
1147				}
1148
1149				let dispatch_origin =
1150					Config::OriginConverter::convert_origin(origin.clone(), origin_kind).map_err(
1151						|_| {
1152							tracing::trace!(
1153								target: "xcm::process_instruction::transact",
1154								?origin,
1155								?origin_kind,
1156								"Failed to convert origin to a local origin."
1157							);
1158
1159							XcmError::BadOrigin
1160						},
1161					)?;
1162
1163				tracing::trace!(
1164					target: "xcm::process_instruction::transact",
1165					origin = ?dispatch_origin,
1166					call = ?message_call,
1167					"Dispatching call with origin",
1168				);
1169
1170				let weight = message_call.get_dispatch_info().call_weight;
1171				let maybe_actual_weight =
1172					match Config::CallDispatcher::dispatch(message_call, dispatch_origin) {
1173						Ok(post_info) => {
1174							tracing::trace!(
1175								target: "xcm::process_instruction::transact",
1176								?post_info,
1177								"Dispatch successful"
1178							);
1179							self.transact_status = MaybeErrorCode::Success;
1180							post_info.actual_weight
1181						},
1182						Err(error_and_info) => {
1183							tracing::trace!(
1184								target: "xcm::process_instruction::transact",
1185								?error_and_info,
1186								"Dispatch failed"
1187							);
1188
1189							self.transact_status = error_and_info.error.encode().into();
1190							error_and_info.post_info.actual_weight
1191						},
1192					};
1193				let actual_weight = maybe_actual_weight.unwrap_or(weight);
1194				let surplus = weight.saturating_sub(actual_weight);
1195				// If the actual weight of the call was less than the specified weight, we credit it.
1196				//
1197				// We make the adjustment for the total surplus, which is used eventually
1198				// reported back to the caller and this ensures that they account for the total
1199				// weight consumed correctly (potentially allowing them to do more operations in a
1200				// block than they otherwise would).
1201				self.total_surplus.saturating_accrue(surplus);
1202				Ok(())
1203			},
1204			QueryResponse { query_id, response, max_weight, querier } => {
1205				let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
1206				Config::TransactionalProcessor::process(|| {
1207					Config::ResponseHandler::on_response(
1208						origin,
1209						query_id,
1210						querier.as_ref(),
1211						response,
1212						max_weight,
1213						&self.context,
1214					);
1215					Ok(())
1216				})
1217			},
1218			DescendOrigin(who) => self.do_descend_origin(who),
1219			ClearOrigin => self.do_clear_origin(),
1220			ExecuteWithOrigin { .. } => Err(XcmError::Unimplemented),
1221			ReportError(response_info) => {
1222				// Report the given result by sending a QueryResponse XCM to a previously given
1223				// outcome destination if one was registered.
1224				self.transactional_process(|self_ref| {
1225					self_ref.respond(
1226						self_ref.cloned_origin(),
1227						Response::ExecutionResult(self_ref.error),
1228						response_info,
1229						FeeReason::Report,
1230					)?;
1231					Ok(())
1232				})
1233			},
1234			DepositAsset { assets, beneficiary } => {
1235				self.transactional_process(|self_ref| {
1236					let deposited = self_ref.holding.saturating_take(assets);
1237					let surplus = Self::deposit_assets_with_retry(
1238						deposited,
1239						&beneficiary,
1240						Some(&self_ref.context),
1241					)?;
1242					self_ref.total_surplus.saturating_accrue(surplus);
1243					Ok(())
1244				})
1245			},
1246			DepositReserveAsset { assets, dest, xcm } => {
1247				self.transactional_process(|self_ref| {
1248					let mut assets = self_ref.holding.saturating_take(assets);
1249					// When not using `PayFees`, nor `JIT_WITHDRAW`, delivery fees are paid from
1250					// transferred assets.
1251					let maybe_delivery_fee_from_assets = if self_ref.fees.is_empty() && !self_ref.fees_mode.jit_withdraw {
1252						// Deduct and return the part of `assets` that shall be used for delivery fees.
1253						self_ref.take_delivery_fee_from_assets(&mut assets, &dest, FeeReason::DepositReserveAsset, &xcm)?
1254					} else {
1255						None
1256					};
1257					let mut message = Vec::with_capacity(xcm.len() + 2);
1258					tracing::trace!(target: "xcm::DepositReserveAsset", ?assets, "Assets except delivery fee");
1259					Self::do_reserve_deposit_assets(
1260						assets,
1261						&dest,
1262						&mut message,
1263						Some(&self_ref.context),
1264					)?;
1265					// clear origin for subsequent custom instructions
1266					message.push(ClearOrigin);
1267					// append custom instructions
1268					message.extend(xcm.0.into_iter());
1269					if let Some(delivery_fee) = maybe_delivery_fee_from_assets {
1270						// Put back delivery_fee in holding register to be charged by XcmSender.
1271						self_ref.holding.subsume_assets(delivery_fee);
1272					}
1273					self_ref.send(dest, Xcm(message), FeeReason::DepositReserveAsset)?;
1274					Ok(())
1275				})
1276			},
1277			InitiateReserveWithdraw { assets, reserve, xcm } => {
1278				self.transactional_process(|self_ref| {
1279					let mut assets = self_ref.holding.saturating_take(assets);
1280					// When not using `PayFees`, nor `JIT_WITHDRAW`, delivery fees are paid from
1281					// transferred assets.
1282					let maybe_delivery_fee_from_assets = if self_ref.fees.is_empty() && !self_ref.fees_mode.jit_withdraw {
1283						// Deduct and return the part of `assets` that shall be used for delivery fees.
1284						self_ref.take_delivery_fee_from_assets(&mut assets, &reserve, FeeReason::InitiateReserveWithdraw, &xcm)?
1285					} else {
1286						None
1287					};
1288					let mut message = Vec::with_capacity(xcm.len() + 2);
1289					Self::do_reserve_withdraw_assets(
1290						assets,
1291						&mut self_ref.holding,
1292						&reserve,
1293						&mut message,
1294					)?;
1295					// clear origin for subsequent custom instructions
1296					message.push(ClearOrigin);
1297					// append custom instructions
1298					message.extend(xcm.0.into_iter());
1299					if let Some(delivery_fee) = maybe_delivery_fee_from_assets {
1300						// Put back delivery_fee in holding register to be charged by XcmSender.
1301						self_ref.holding.subsume_assets(delivery_fee);
1302					}
1303					self_ref.send(reserve, Xcm(message), FeeReason::InitiateReserveWithdraw)?;
1304					Ok(())
1305				})
1306			},
1307			InitiateTeleport { assets, dest, xcm } => {
1308				self.transactional_process(|self_ref| {
1309					let mut assets = self_ref.holding.saturating_take(assets);
1310					// When not using `PayFees`, nor `JIT_WITHDRAW`, delivery fees are paid from
1311					// transferred assets.
1312					let maybe_delivery_fee_from_assets = if self_ref.fees.is_empty() && !self_ref.fees_mode.jit_withdraw {
1313						// Deduct and return the part of `assets` that shall be used for delivery fees.
1314						self_ref.take_delivery_fee_from_assets(&mut assets, &dest, FeeReason::InitiateTeleport, &xcm)?
1315					} else {
1316						None
1317					};
1318					let mut message = Vec::with_capacity(xcm.len() + 2);
1319					Self::do_teleport_assets(assets, &dest, &mut message, &self_ref.context)?;
1320					// clear origin for subsequent custom instructions
1321					message.push(ClearOrigin);
1322					// append custom instructions
1323					message.extend(xcm.0.into_iter());
1324					if let Some(delivery_fee) = maybe_delivery_fee_from_assets {
1325						// Put back delivery_fee in holding register to be charged by XcmSender.
1326						self_ref.holding.subsume_assets(delivery_fee);
1327					}
1328					self_ref.send(dest.clone(), Xcm(message), FeeReason::InitiateTeleport)?;
1329					Ok(())
1330				})
1331			},
1332			InitiateTransfer { destination, remote_fees, preserve_origin, assets, remote_xcm } => {
1333				self.transactional_process(|self_ref| {
1334					let mut message = Vec::with_capacity(assets.len() + remote_xcm.len() + 2);
1335
1336					// We need to transfer the fees and buy execution on remote chain _BEFORE_
1337					// transferring the other assets. This is required to satisfy the
1338					// `MAX_ASSETS_FOR_BUY_EXECUTION` limit in the `AllowTopLevelPaidExecutionFrom`
1339					// barrier.
1340					let remote_fees_paid = if let Some(remote_fees) = remote_fees {
1341						let reanchored_fees = match remote_fees {
1342							AssetTransferFilter::Teleport(fees_filter) => {
1343								let teleport_fees = self_ref
1344									.holding
1345									.try_take(fees_filter)
1346									.map_err(|error| {
1347										tracing::debug!(
1348											target: "xcm::process_instruction::initiate_transfer", ?error,
1349											"Failed to take specified teleport fees from holding"
1350										);
1351										XcmError::NotHoldingFees
1352									})?;
1353								Self::do_teleport_assets(
1354									teleport_fees,
1355									&destination,
1356									&mut message,
1357									&self_ref.context,
1358								)?
1359							},
1360							AssetTransferFilter::ReserveDeposit(fees_filter) => {
1361								let reserve_deposit_fees = self_ref
1362									.holding
1363									.try_take(fees_filter)
1364									.map_err(|error| {
1365										tracing::debug!(
1366											target: "xcm::process_instruction::initiate_transfer", ?error,
1367											"Failed to take specified reserve deposit fees from holding"
1368										);
1369										XcmError::NotHoldingFees
1370									})?;
1371								Self::do_reserve_deposit_assets(
1372									reserve_deposit_fees,
1373									&destination,
1374									&mut message,
1375									Some(&self_ref.context),
1376								)?
1377							},
1378							AssetTransferFilter::ReserveWithdraw(fees_filter) => {
1379								let reserve_withdraw_fees = self_ref
1380									.holding
1381									.try_take(fees_filter)
1382									.map_err(|error| {
1383										tracing::debug!(
1384											target: "xcm::process_instruction::initiate_transfer", ?error,
1385											"Failed to take specified reserve withdraw fees from holding"
1386										);
1387										XcmError::NotHoldingFees
1388									})?;
1389								Self::do_reserve_withdraw_assets(
1390									reserve_withdraw_fees,
1391									&mut self_ref.holding,
1392									&destination,
1393									&mut message,
1394								)?
1395							},
1396						};
1397						ensure!(reanchored_fees.len() == 1, XcmError::TooManyAssets);
1398						let fees =
1399							reanchored_fees.into_inner().pop().ok_or(XcmError::NotHoldingFees)?;
1400						// move these assets to the fees register for covering execution and paying
1401						// any subsequent fees
1402						message.push(PayFees { asset: fees });
1403						true
1404					} else {
1405						false
1406					};
1407
1408					// add any extra asset transfers
1409					for asset_filter in assets {
1410						match asset_filter {
1411							AssetTransferFilter::Teleport(assets) => Self::do_teleport_assets(
1412								self_ref.holding.saturating_take(assets),
1413								&destination,
1414								&mut message,
1415								&self_ref.context,
1416							)?,
1417							AssetTransferFilter::ReserveDeposit(assets) =>
1418								Self::do_reserve_deposit_assets(
1419									self_ref.holding.saturating_take(assets),
1420									&destination,
1421									&mut message,
1422									Some(&self_ref.context),
1423								)?,
1424							AssetTransferFilter::ReserveWithdraw(assets) =>
1425								Self::do_reserve_withdraw_assets(
1426									self_ref.holding.saturating_take(assets),
1427									&mut self_ref.holding,
1428									&destination,
1429									&mut message,
1430								)?,
1431						};
1432					}
1433
1434					match self_ref
1435						.origin_ref() {
1436						Some(origin) if preserve_origin => {
1437							// We alias the origin if it's not a noop (origin != `Here`).
1438							if *origin != Location::here() {
1439								// preserve current origin for subsequent user-controlled instructions on
1440								// remote chain
1441								let reanchored_origin = Self::try_reanchor(origin.clone(), &destination)?.0;
1442								message.push(AliasOrigin(reanchored_origin));
1443							}
1444							// If origin is Location::here() and we want to preserve it, we don't alter.
1445						}
1446						_ => {
1447							// clear origin for subsequent user-controlled instructions on remote chain
1448							message.push(ClearOrigin);
1449						}
1450					}
1451
1452					// If not intending to pay for fees then we append the `UnpaidExecution`
1453					// _AFTER_ origin altering instructions.
1454					// When origin is not preserved, it's probably going to fail on the receiver.
1455					if !remote_fees_paid {
1456						// We push the UnpaidExecution instruction to notify we do not intend to pay
1457						// for fees.
1458						// The receiving chain must decide based on the origin of the message if they
1459						// accept this.
1460						message
1461							.push(UnpaidExecution { weight_limit: Unlimited, check_origin: None });
1462					}
1463
1464					// append custom instructions
1465					message.extend(remote_xcm.0.into_iter());
1466					// send the onward XCM
1467					self_ref.send(destination, Xcm(message), FeeReason::InitiateTransfer)?;
1468					Ok(())
1469				})
1470			},
1471			ReportHolding { response_info, assets } => {
1472				let context = Config::UniversalLocation::get();
1473				let assets = self.holding.min(&assets)
1474					.into_inner()
1475					.into_iter()
1476					.filter_map(|a| a.reanchored(&response_info.destination, &context).ok())
1477					.collect::<Vec<Asset>>()
1478					.into();
1479				self.transactional_process(|self_ref| {
1480					self_ref.respond(
1481						self_ref.cloned_origin(),
1482						Response::Assets(assets),
1483						response_info,
1484						FeeReason::Report,
1485					)?;
1486					Ok(())
1487				})
1488			},
1489			BuyExecution { fees, weight_limit } => {
1490				// There is no need to buy any weight if `weight_limit` is `Unlimited` since it
1491				// would indicate that `AllowTopLevelPaidExecutionFrom` was unused for execution
1492				// and thus there is some other reason why it has been determined that this XCM
1493				// should be executed.
1494				let Some(weight) = Option::<Weight>::from(weight_limit) else { return Ok(()) };
1495				self.transactional_process(|self_ref| {
1496					// pay for `weight` using up to `fees` of the holding register.
1497					let max_fee =
1498						self_ref.holding.try_take(fees.clone().into()).map_err(|e| {
1499							tracing::error!(target: "xcm::process_instruction::buy_execution", ?e, ?fees,
1500							"Failed to take fees from holding");
1501							XcmError::NotHoldingFees
1502						})?;
1503					let unspent = self_ref.trader.buy_weight(weight, max_fee, &self_ref.context).map_err(|(unspent, e)| {
1504						self_ref.holding.subsume_assets(unspent);
1505						e
1506					})?;
1507					self_ref.holding.subsume_assets(unspent);
1508					// Save the asset being used for execution fees, so we later know what should
1509					// be used for delivery fees. Only recorded once the purchase went through.
1510					self_ref.asset_used_in_buy_execution = Some(fees.id);
1511					tracing::trace!(
1512						target: "xcm::executor::BuyExecution",
1513						asset_used_in_buy_execution = ?self_ref.asset_used_in_buy_execution
1514					);
1515					Ok(())
1516				})
1517			},
1518			PayFees { asset } => {
1519				// If we've already paid for fees, do nothing.
1520				if self.already_paid_fees {
1521					return Ok(());
1522				}
1523				// Make sure `PayFees` won't be processed again.
1524				self.already_paid_fees = true;
1525				// The max we're willing to pay for fees is decided by the `asset` operand.
1526				tracing::trace!(
1527					target: "xcm::executor::PayFees",
1528					asset_for_fees = ?asset,
1529					message_weight = ?self.message_weight,
1530				);
1531				// Pay for execution fees.
1532				self.transactional_process_with_custom_rollback(
1533					|self_ref| {
1534						let max_fee =
1535							self_ref.holding.try_take(asset.into()).map_err(|error| {
1536								tracing::debug!(
1537									target: "xcm::process_instruction::pay_fees", ?error,
1538									"Failed to take fees from holding"
1539								);
1540								XcmError::NotHoldingFees
1541							})?;
1542						let unspent =
1543							self_ref.trader.buy_weight(self_ref.message_weight, max_fee.into(), &self_ref.context).map_err(|(unspent, e)| {
1544								self_ref.fees.subsume_assets(unspent);
1545								e
1546							})?;
1547						// Move unspent to the `fees` register, it can later be moved to holding by calling `RefundSurplus`.
1548						self_ref.fees.subsume_assets(unspent);
1549						Ok(())
1550					},
1551					|self_ref| {
1552						self_ref.already_paid_fees = false;
1553					},
1554				)
1555			},
1556			RefundSurplus => self.refund_surplus(),
1557			SetErrorHandler(mut handler) => {
1558				let handler_weight = Config::Weigher::weight(&mut handler, Weight::MAX)
1559					.map_err(|error| {
1560						tracing::debug!(
1561							target: "xcm::executor::SetErrorHandler",
1562							?error,
1563							?handler,
1564							"Failed to calculate weight"
1565						);
1566						XcmError::WeightNotComputable
1567					})?;
1568				self.total_surplus.saturating_accrue(self.error_handler_weight);
1569				self.error_handler = handler;
1570				self.error_handler_weight = handler_weight;
1571				Ok(())
1572			},
1573			SetAppendix(mut appendix) => {
1574				let appendix_weight = Config::Weigher::weight(&mut appendix, Weight::MAX)
1575					.map_err(|error| {
1576						tracing::debug!(
1577							target: "xcm::executor::SetErrorHandler",
1578							?error,
1579							?appendix,
1580							"Failed to calculate weight"
1581						);
1582						XcmError::WeightNotComputable
1583					})?;
1584				self.total_surplus.saturating_accrue(self.appendix_weight);
1585				self.appendix = appendix;
1586				self.appendix_weight = appendix_weight;
1587				Ok(())
1588			},
1589			ClearError => {
1590				self.error = None;
1591				Ok(())
1592			},
1593			SetHints { hints } => {
1594				for hint in hints.into_iter() {
1595					match hint {
1596						AssetClaimer { location } => {
1597							self.asset_claimer = Some(location)
1598						},
1599					}
1600				}
1601				Ok(())
1602			},
1603			ClaimAsset { assets, ticket } => {
1604				self.ensure_can_subsume_assets(assets.len())?;
1605				self.transactional_process(|self_ref| {
1606					let origin = self_ref.origin_ref().ok_or(XcmError::BadOrigin)?;
1607					let claimed = Config::AssetTrap::claim_assets(
1608						origin,
1609						&ticket,
1610						&assets,
1611						&self_ref.context,
1612					)
1613					.ok_or(XcmError::UnknownClaim)?;
1614					self_ref.holding.subsume_assets(claimed);
1615					Ok(())
1616				})
1617			},
1618			Trap(code) => Err(XcmError::Trap(code)),
1619			SubscribeVersion { query_id, max_response_weight } => {
1620				let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
1621				// We don't allow derivative origins to subscribe since it would otherwise pose a
1622				// DoS risk.
1623				ensure!(&self.original_origin == origin, XcmError::BadOrigin);
1624				Config::TransactionalProcessor::process(|| {
1625					Config::SubscriptionService::start(
1626						origin,
1627						query_id,
1628						max_response_weight,
1629						&self.context,
1630					)
1631				})
1632			},
1633			UnsubscribeVersion => {
1634				let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
1635				ensure!(&self.original_origin == origin, XcmError::BadOrigin);
1636				Config::TransactionalProcessor::process(|| {
1637					Config::SubscriptionService::stop(origin, &self.context)
1638				})
1639			},
1640			BurnAsset(assets) => {
1641				self.holding.saturating_take(assets.into());
1642				Ok(())
1643			},
1644			ExpectAsset(assets) =>
1645				self.holding.ensure_contains(&assets).map_err(|e| {
1646					tracing::error!(target: "xcm::process_instruction::expect_asset", ?e, ?assets, "assets not contained in holding");
1647					XcmError::ExpectationFalse
1648				}),
1649			ExpectOrigin(origin) => {
1650				ensure!(self.context.origin == origin, XcmError::ExpectationFalse);
1651				Ok(())
1652			},
1653			ExpectError(error) => {
1654				ensure!(self.error == error, XcmError::ExpectationFalse);
1655				Ok(())
1656			},
1657			ExpectTransactStatus(transact_status) => {
1658				ensure!(self.transact_status == transact_status, XcmError::ExpectationFalse);
1659				Ok(())
1660			},
1661			QueryPallet { module_name, response_info } => {
1662				let pallets = Config::PalletInstancesInfo::infos()
1663					.into_iter()
1664					.filter(|x| x.module_name.as_bytes() == &module_name[..])
1665					.map(|x| {
1666						PalletInfo::new(
1667							x.index as u32,
1668							x.name.as_bytes().into(),
1669							x.module_name.as_bytes().into(),
1670							x.crate_version.major as u32,
1671							x.crate_version.minor as u32,
1672							x.crate_version.patch as u32,
1673						)
1674					})
1675					.collect::<Result<Vec<_>, XcmError>>()?;
1676				let QueryResponseInfo { destination, query_id, max_weight } = response_info;
1677				let response =
1678					Response::PalletsInfo(pallets.try_into().map_err(|error| {
1679						tracing::debug!(
1680							target: "xcm::process_instruction::query_pallet", ?error,
1681							"Failed to convert pallets to response info"
1682						);
1683						XcmError::Overflow
1684					})?);
1685				let querier = Self::to_querier(self.cloned_origin(), &destination)?;
1686				let instruction = QueryResponse { query_id, response, max_weight, querier };
1687				let message = Xcm(vec![instruction]);
1688				self.transactional_process(|self_ref| {
1689					self_ref.send(destination, message, FeeReason::QueryPallet)?;
1690					Ok(())
1691				})
1692			},
1693			ExpectPallet { index, name, module_name, crate_major, min_crate_minor } => {
1694				let pallet = Config::PalletInstancesInfo::infos()
1695					.into_iter()
1696					.find(|x| x.index == index as usize)
1697					.ok_or(XcmError::PalletNotFound)?;
1698				ensure!(pallet.name.as_bytes() == &name[..], XcmError::NameMismatch);
1699				ensure!(pallet.module_name.as_bytes() == &module_name[..], XcmError::NameMismatch);
1700				let major = pallet.crate_version.major as u32;
1701				ensure!(major == crate_major, XcmError::VersionIncompatible);
1702				let minor = pallet.crate_version.minor as u32;
1703				ensure!(minor >= min_crate_minor, XcmError::VersionIncompatible);
1704				Ok(())
1705			},
1706			ReportTransactStatus(response_info) => {
1707				self.transactional_process(|self_ref| {
1708					self_ref.respond(
1709						self_ref.cloned_origin(),
1710						Response::DispatchResult(self_ref.transact_status.clone()),
1711						response_info,
1712						FeeReason::Report,
1713					)?;
1714					Ok(())
1715				})
1716			},
1717			ClearTransactStatus => {
1718				self.transact_status = Default::default();
1719				Ok(())
1720			},
1721			UniversalOrigin(new_global) => {
1722				let universal_location = Config::UniversalLocation::get();
1723				ensure!(universal_location.first() != Some(&new_global), XcmError::InvalidLocation);
1724				let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?;
1725				let origin_xform = (origin, new_global);
1726				let ok = Config::UniversalAliases::contains(&origin_xform);
1727				ensure!(ok, XcmError::InvalidLocation);
1728				let (_, new_global) = origin_xform;
1729				let new_origin = Junctions::from([new_global]).relative_to(&universal_location);
1730				self.context.origin = Some(new_origin);
1731				Ok(())
1732			},
1733			ExportMessage { network, destination, xcm } => {
1734				// The actual message sent to the bridge for forwarding is prepended with
1735				// `UniversalOrigin` and `DescendOrigin` in order to ensure that the message is
1736				// executed with this Origin.
1737				//
1738				// Prepend the desired message with instructions which effectively rewrite the
1739				// origin.
1740				//
1741				// This only works because the remote chain empowers the bridge
1742				// to speak for the local network.
1743				let origin = self.context.origin.as_ref().ok_or(XcmError::BadOrigin)?.clone();
1744				let universal_source = Config::UniversalLocation::get()
1745					.within_global(origin)
1746					.map_err(|()| {
1747						tracing::debug!(
1748							target: "xcm::process_instruction::export_message",
1749							"Failed to reanchor origin to universal location",
1750						);
1751						XcmError::Unanchored
1752					})?;
1753				let hash = (self.origin_ref(), &destination).using_encoded(blake2_128);
1754				let channel = u32::decode(&mut hash.as_ref()).unwrap_or(0);
1755				// Hash identifies the lane on the exporter which we use. We use the pairwise
1756				// combination of the origin and destination to ensure origin/destination pairs
1757				// will generally have their own lanes.
1758				let (ticket, fee) = validate_export::<Config::MessageExporter>(
1759					network,
1760					channel,
1761					universal_source,
1762					destination.clone(),
1763					xcm,
1764				)?;
1765				self.transactional_process(|self_ref| {
1766					self_ref.take_fee(fee, FeeReason::Export { network, destination })?;
1767					// `deliver` is called immediately after `validate_export` and `take_fee` does
1768					// not affect the validity of the ticket, so this is not expected to fail. If
1769					// it does anyway, the error rolls back the export fee with the rest of the
1770					// instruction instead of charging for a message that was never exported.
1771					Config::MessageExporter::deliver(ticket)?;
1772					Ok(())
1773				})
1774			},
1775			LockAsset { asset, unlocker } => {
1776				self.transactional_process(|self_ref| {
1777					let origin = self_ref.cloned_origin().ok_or(XcmError::BadOrigin)?;
1778					let (remote_asset, context) = Self::try_reanchor(asset.clone(), &unlocker)?;
1779					let lock_ticket =
1780						Config::AssetLocker::prepare_lock(unlocker.clone(), asset, origin.clone())?;
1781					let owner = origin.reanchored(&unlocker, &context).map_err(|e| {
1782						tracing::error!(target: "xcm::xcm_executor::process_instruction", ?e, ?unlocker, ?context, "Failed to re-anchor origin");
1783						XcmError::ReanchorFailed
1784					})?;
1785					let msg = Xcm::<()>(vec![NoteUnlockable { asset: remote_asset, owner }]);
1786					let (ticket, price) = validate_send::<Config::XcmSender>(unlocker, msg)?;
1787					self_ref.take_fee(price, FeeReason::LockAsset)?;
1788					lock_ticket.enact()?;
1789					Config::XcmSender::deliver(ticket)?;
1790					Ok(())
1791				})
1792			},
1793			UnlockAsset { asset, target } => {
1794				let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?;
1795				Config::TransactionalProcessor::process(|| {
1796					Config::AssetLocker::prepare_unlock(origin, asset, target)?.enact()?;
1797					Ok(())
1798				})
1799			},
1800			NoteUnlockable { asset, owner } => {
1801				let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?;
1802				Config::TransactionalProcessor::process(|| {
1803					Config::AssetLocker::note_unlockable(origin, asset, owner)?;
1804					Ok(())
1805				})
1806			},
1807			RequestUnlock { asset, locker } => {
1808				let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?;
1809				let remote_asset = Self::try_reanchor(asset.clone(), &locker)?.0;
1810				let remote_target = Self::try_reanchor(origin.clone(), &locker)?.0;
1811				let reduce_ticket = Config::AssetLocker::prepare_reduce_unlockable(
1812					locker.clone(),
1813					asset,
1814					origin.clone(),
1815				)?;
1816				let msg =
1817					Xcm::<()>(vec![UnlockAsset { asset: remote_asset, target: remote_target }]);
1818				let (ticket, price) = validate_send::<Config::XcmSender>(locker, msg)?;
1819				self.transactional_process(|self_ref| {
1820					self_ref.take_fee(price, FeeReason::RequestUnlock)?;
1821					reduce_ticket.enact()?;
1822					Config::XcmSender::deliver(ticket)?;
1823					Ok(())
1824				})
1825			},
1826			ExchangeAsset { give, want, maximal } => {
1827				self.transactional_process(|self_ref| {
1828					let give = self_ref.holding.saturating_take(give);
1829					self_ref.ensure_can_subsume_assets(want.len())?;
1830					let received = Config::AssetExchanger::exchange_asset(
1831						self_ref.origin_ref(),
1832						give,
1833						&want,
1834						maximal,
1835					).map_err(|unspent| {
1836						self_ref.holding.subsume_assets(unspent);
1837						XcmError::NoDeal
1838					})?;
1839					self_ref.holding.subsume_assets(received);
1840					Ok(())
1841				})
1842			},
1843			SetFeesMode { jit_withdraw } => {
1844				self.fees_mode = FeesMode { jit_withdraw };
1845				Ok(())
1846			},
1847			SetTopic(topic) => {
1848				self.context.topic = Some(topic);
1849				Ok(())
1850			},
1851			ClearTopic => {
1852				self.context.topic = None;
1853				Ok(())
1854			},
1855			AliasOrigin(target) => {
1856				let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
1857				ensure!(Config::Aliasers::contains(origin, &target), XcmError::NoPermission);
1858				self.context.origin = Some(target);
1859				Ok(())
1860			},
1861			UnpaidExecution { check_origin, .. } => {
1862				ensure!(
1863					check_origin.is_none() || self.context.origin == check_origin,
1864					XcmError::BadOrigin
1865				);
1866				Ok(())
1867			},
1868			HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } =>
1869				Config::TransactionalProcessor::process(|| {
1870					Config::HrmpNewChannelOpenRequestHandler::handle(
1871						sender,
1872						max_message_size,
1873						max_capacity,
1874					)
1875				}),
1876			HrmpChannelAccepted { recipient } => Config::TransactionalProcessor::process(|| {
1877				Config::HrmpChannelAcceptedHandler::handle(recipient)
1878			}),
1879			HrmpChannelClosing { initiator, sender, recipient } =>
1880				Config::TransactionalProcessor::process(|| {
1881					Config::HrmpChannelClosingHandler::handle(initiator, sender, recipient)
1882				}),
1883		}
1884	}
1885
1886	fn do_descend_origin(&mut self, who: InteriorLocation) -> XcmResult {
1887		self.context
1888			.origin
1889			.as_mut()
1890			.ok_or(XcmError::BadOrigin)?
1891			.append_with(who)
1892			.map_err(|e| {
1893				tracing::error!(target: "xcm::do_descend_origin", ?e, "Failed to append junctions");
1894				XcmError::LocationFull
1895			})
1896	}
1897
1898	fn do_clear_origin(&mut self) -> XcmResult {
1899		self.context.origin = None;
1900		Ok(())
1901	}
1902
1903	/// Deposit `to_deposit` assets to `beneficiary`, without giving up on the first (transient)
1904	/// error, and retrying once just in case one of the subsequently deposited assets satisfy some
1905	/// requirement.
1906	///
1907	/// Most common transient error is: `beneficiary` account does not yet exist and the first
1908	/// asset(s) in the (sorted) list does not satisfy ED, but a subsequent one in the list does.
1909	///
1910	/// Any per-asset failure on the retry pass propagates as `Err`, and the surrounding
1911	/// `transactional_process` rolls back the whole instruction (storage changes are reverted by
1912	/// `Config::TransactionalProcessor`, and `self.holding` is restored from its
1913	/// pre-instruction backup). Anything left in `self.holding` after the program finishes is
1914	/// then trapped by `post_process` via `Config::AssetTrap::drop_assets`, so funds are never
1915	/// silently lost.
1916	///
1917	/// This function can write into storage and also return an error at the same time, it should
1918	/// always be called within a transactional context.
1919	fn deposit_assets_with_retry(
1920		to_deposit: AssetsInHolding,
1921		beneficiary: &Location,
1922		context: Option<&XcmContext>,
1923	) -> Result<Weight, XcmError> {
1924		let mut total_surplus = Weight::zero();
1925		let mut failed_deposits = AssetsInHolding::new();
1926
1927		// First pass: try to deposit each asset; failures go to retry.
1928		for single in to_deposit.into_per_asset_holdings() {
1929			match Config::AssetTransactor::deposit_asset_with_surplus(single, beneficiary, context)
1930			{
1931				Ok(surplus) => total_surplus.saturating_accrue(surplus),
1932				Err((unspent, _)) => {
1933					// First-pass failure: keep for retry. A subsequent deposit in the same
1934					// pass may create the destination account (by satisfying ED), allowing
1935					// the retry pass to succeed for assets that fall here.
1936					failed_deposits.subsume_assets(unspent);
1937				},
1938			}
1939		}
1940
1941		// Retry previously failed deposits, this time short-circuiting on any error.
1942		for single in failed_deposits.into_per_asset_holdings() {
1943			let surplus =
1944				Config::AssetTransactor::deposit_asset_with_surplus(single, beneficiary, context)
1945					.map_err(|(unspent, error)| {
1946					tracing::debug!(
1947						target: "xcm::deposit_assets_with_retry",
1948						?error,
1949						?unspent,
1950						"Retry-pass deposit failed"
1951					);
1952					error
1953				})?;
1954			total_surplus.saturating_accrue(surplus);
1955		}
1956
1957		Ok(total_surplus)
1958	}
1959
1960	/// Take from transferred `assets` the delivery fee required to send an onward transfer message
1961	/// to `destination`.
1962	///
1963	/// Will be removed once the transition from `BuyExecution` to `PayFees` is complete.
1964	fn take_delivery_fee_from_assets(
1965		&self,
1966		assets: &mut AssetsInHolding,
1967		destination: &Location,
1968		reason: FeeReason,
1969		xcm: &Xcm<()>,
1970	) -> Result<Option<AssetsInHolding>, XcmError> {
1971		let to_weigh_reanchored = Self::reanchored_assets(&assets, destination);
1972		let remote_instruction = match reason {
1973			FeeReason::DepositReserveAsset => ReserveAssetDeposited(to_weigh_reanchored),
1974			FeeReason::InitiateReserveWithdraw => WithdrawAsset(to_weigh_reanchored),
1975			FeeReason::InitiateTeleport => ReceiveTeleportedAsset(to_weigh_reanchored),
1976			_ => {
1977				tracing::debug!(
1978					target: "xcm::take_delivery_fee_from_assets",
1979					"Unexpected delivery fee reason",
1980				);
1981				return Err(XcmError::NotHoldingFees);
1982			},
1983		};
1984		let mut message_to_weigh = Vec::with_capacity(xcm.len() + 2);
1985		message_to_weigh.push(remote_instruction);
1986		message_to_weigh.push(ClearOrigin);
1987		message_to_weigh.extend(xcm.0.clone().into_iter());
1988		let (_, fee) =
1989			validate_send::<Config::XcmSender>(destination.clone(), Xcm(message_to_weigh))?;
1990		let maybe_delivery_fee = fee.get(0).map(|asset_needed_for_fees| {
1991			tracing::trace!(
1992				target: "xcm::fees::take_delivery_fee_from_assets",
1993				"Asset provided to pay for fees {:?}, asset required for delivery fees: {:?}",
1994				self.asset_used_in_buy_execution, asset_needed_for_fees,
1995			);
1996			let asset_to_pay_for_fees =
1997				self.calculate_asset_for_delivery_fees(asset_needed_for_fees.clone());
1998			// set aside fee to be charged by XcmSender
1999			let delivery_fee = assets.saturating_take(asset_to_pay_for_fees.into());
2000			tracing::trace!(target: "xcm::fees::take_delivery_fee_from_assets", ?delivery_fee);
2001			delivery_fee
2002		});
2003		Ok(maybe_delivery_fee)
2004	}
2005}