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